From 283cbe4555e1d8e794600ba4564fc03f1e95e9f3 Mon Sep 17 00:00:00 2001 From: dridev Date: Sat, 2 May 2026 16:41:35 -0300 Subject: [PATCH 01/16] first implementation of history --- .../Storage/History.cs | 15 +++++++++++ .../Storage/HistoryItem.cs | 25 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs new file mode 100644 index 00000000000..c58f35fbf6c --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace Flow.Launcher.Plugin.Calculator.Storage; + +internal class History +{ + public List Items { get; set; } = []; + + public void AddOrUpdate(Result result, Func action) + { + var item = new HistoryItem(result, action); + Items.Add(item); + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs new file mode 100644 index 00000000000..3b417e5ebba --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs @@ -0,0 +1,25 @@ +using System; + +namespace Flow.Launcher.Plugin.Calculator.Storage; + +internal class HistoryItem : Result +{ + public string Query { get; set; } + public DateTime CalculatedAt { get; set; } + + public HistoryItem(Result result, Func action) + { + 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; + //Query = result.Ori + Query = "1+2"; + Action = action; + } +} From f4ac49779737a3781ae5c4e976433761e0b24b4a Mon Sep 17 00:00:00 2001 From: dridev Date: Sat, 2 May 2026 16:42:56 -0300 Subject: [PATCH 02/16] HasCalculationOperator method to just add calc in history if have a operator in query --- .../Flow.Launcher.Plugin.Calculator/Main.cs | 133 ++++++++++++++---- 1 file changed, 107 insertions(+), 26 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index 1b9a38e1819..ee19ede4522 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -5,6 +5,7 @@ using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Windows.Controls; +using Flow.Launcher.Plugin.Calculator.Storage; using Flow.Launcher.Plugin.Calculator.ViewModels; using Flow.Launcher.Plugin.Calculator.Views; using Mages.Core; @@ -26,6 +27,10 @@ public class Main : IPlugin, IPluginI18n, ISettingProvider private const string IcoPath = "Images/calculator.png"; private static readonly List EmptyResults = []; + // Adicionar no historico após um calculo + // Adicionar no historico após pressionar enter (action trigger) + // Apenas adicionar no historico se a config estiver ativa + private static readonly History History = new (); internal static PluginInitContext Context { get; private set; } = null!; private Settings _settings; @@ -57,6 +62,7 @@ public List Query(Query query) { var search = query.Search; bool isFunctionPresent = FunctionRegex.IsMatch(search); + bool hasCalculationOperator = HasCalculationOperator(search); // Mages is case sensitive, so we need to convert all function names to lower case. search = FunctionRegex.Replace(search, m => m.Value.ToLowerInvariant()); @@ -64,7 +70,6 @@ public List Query(Query query) var decimalSep = GetDecimalSeparator(); var groupSep = GetGroupSeparator(decimalSep); var expression = NumberRegex.Replace(search, m => NormalizeNumber(m.Value, isFunctionPresent, decimalSep, groupSep)); - // WORKAROUND START: The 'pow' function in Mages v3.0.0 is broken. // https://github.com/FlorianRappl/Mages/issues/132 // We bypass it by rewriting any pow(x,y) expression to the equivalent (x^y) expression @@ -131,31 +136,30 @@ public List Query(Query query) decimal roundedResult = Math.Round(Convert.ToDecimal(result), _settings.MaxDecimalPlaces, MidpointRounding.AwayFromZero); string newResult = FormatResult(roundedResult); - return - [ - new Result - { - Title = newResult, - IcoPath = IcoPath, - Score = 300, - // Check context nullability for unit testing - SubTitle = Context == null ? string.Empty : Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard(), - CopyText = newResult, - Action = c => - { - try - { - Context.API.CopyToClipboard(newResult); - return true; - } - catch (ExternalException) - { - Context.API.ShowMsgBox(Localize.flowlauncher_plugin_calculator_failed_to_copy()); - return false; - } - } - } - ]; + var results = new List(); + var action = CreateAction(newResult); + var resultObject = new Result + { + Title = newResult, + IcoPath = IcoPath, + Score = 300, + // Check context nullability for unit testing + SubTitle = Context == null + ? string.Empty + : Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard(), + CopyText = newResult, + Action = action + }; + + if (hasCalculationOperator) + { + History.AddOrUpdate(resultObject, action); + } + results.Add(resultObject); + results.AddRange(History.Items); + + return results; + } } catch (Exception) @@ -175,6 +179,83 @@ public List Query(Query query) return EmptyResults; } + + private Func CreateAction(string newResult) + { + return (_) => + { + try + { + Context.API.CopyToClipboard(newResult); + + return true; + } + catch (ExternalException) + { + Context.API.ShowMsgBox( + Localize.flowlauncher_plugin_calculator_failed_to_copy() + ); + return false; + } + }; + } + private static bool HasCalculationOperator(string input) + { + for (var i = 0; i < input.Length; i++) + { + if (input[i] is not ('+' or '-' or '*' or '/' or '^' or '%')) + { + continue; + } + + var previousIndex = PreviousNonWhitespaceIndex(input, i - 1); + var nextIndex = NextNonWhitespaceIndex(input, i + 1); + + if (previousIndex == -1 || nextIndex == -1) + { + continue; + } + + if (IsOperandCharacter(input[previousIndex]) && IsOperandCharacter(input[nextIndex])) + { + return true; + } + } + + return false; + } + + private static int PreviousNonWhitespaceIndex(string input, int startIndex) + { + for (var i = startIndex; i >= 0; i--) + { + if (!char.IsWhiteSpace(input[i])) + { + return i; + } + } + + return -1; + } + + private static int NextNonWhitespaceIndex(string input, int startIndex) + { + for (var i = startIndex; i < input.Length; i++) + { + if (!char.IsWhiteSpace(input[i])) + { + return i; + } + } + + return -1; + } + + private static bool IsOperandCharacter(char c) + { + return char.IsLetterOrDigit(c) || c is '.' or ',' or ')' or ']' or '\'' or '\u00A0' or '\u202F'; + } + private static string PowMatchEvaluator(Match m) { // m.Groups[1].Value will be `(...)` with parens From 05eae5825a4d18010a9e1cd547b215527b52e2c8 Mon Sep 17 00:00:00 2001 From: dridev Date: Sat, 2 May 2026 19:25:53 -0300 Subject: [PATCH 03/16] setting to enable or disable history --- .../Flow.Launcher.Plugin.Calculator/Languages/en.xaml | 3 ++- Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs | 2 ++ .../Views/CalculatorSettings.xaml | 11 +++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml index 5f9a3ca5a6c..22e4d22dcea 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml @@ -17,4 +17,5 @@ Show thousands separator in results Copy failed, please try later Show error message when calculation fails - \ No newline at end of file + Enable History + diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs index 69cd17ed2da..eb1032d2dc1 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs @@ -9,4 +9,6 @@ public class Settings public bool ShowErrorMessage { get; set; } = false; public bool UseThousandsSeparator { get; set; } = true; + + public bool EnableHistory { get; set; } = false; } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml index 82f8eec60f3..70e5c126c6f 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml @@ -17,6 +17,7 @@ + @@ -79,5 +80,15 @@ VerticalAlignment="Center" Content="{DynamicResource flowlauncher_plugin_calculator_show_error_message}" IsChecked="{Binding Settings.ShowErrorMessage, Mode=TwoWay}" /> + + From e4f5dee9a19f7e7aeccb94333cb38b71e337f7ac Mon Sep 17 00:00:00 2001 From: dridev Date: Sat, 2 May 2026 20:51:15 -0300 Subject: [PATCH 04/16] badge in history item and fix some bugs --- .../Images/history.png | Bin 0 -> 3300 bytes .../Languages/en.xaml | 1 + .../Flow.Launcher.Plugin.Calculator/Main.cs | 88 ++++-------------- .../Storage/History.cs | 25 ++++- .../Storage/HistoryItem.cs | 32 ++++--- 5 files changed, 60 insertions(+), 86 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Calculator/Images/history.png diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Images/history.png b/Plugins/Flow.Launcher.Plugin.Calculator/Images/history.png new file mode 100644 index 0000000000000000000000000000000000000000..b298f8c82842569c2988214ca6963d6143b235ad GIT binary patch literal 3300 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 N5VD;UsnEth_CI81bol@P literal 0 HcmV?d00001 diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml index 22e4d22dcea..1205f09d3d7 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml @@ -18,4 +18,5 @@ Copy failed, please try later Show error message when calculation fails Enable History + Calculated at {0} diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index ee19ede4522..fbf048991ef 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -25,12 +25,10 @@ public class Main : IPlugin, IPluginI18n, ISettingProvider private const string Comma = ","; private const string Dot = "."; private const string IcoPath = "Images/calculator.png"; + private static readonly List EmptyResults = []; - // Adicionar no historico após um calculo - // Adicionar no historico após pressionar enter (action trigger) - // Apenas adicionar no historico se a config estiver ativa - private static readonly History History = new (); + private History History { get; set; } = null!; internal static PluginInitContext Context { get; private set; } = null!; private Settings _settings; @@ -40,6 +38,7 @@ public void Init(PluginInitContext context) { Context = context; _settings = context.API.LoadSettingJsonStorage(); + History = context.API.LoadSettingJsonStorage(); _viewModel = new SettingsViewModel(_settings); MagesEngine = new Engine(new Configuration @@ -62,8 +61,7 @@ public List Query(Query query) { var search = query.Search; bool isFunctionPresent = FunctionRegex.IsMatch(search); - bool hasCalculationOperator = HasCalculationOperator(search); - + bool isValidResult = true; // Mages is case sensitive, so we need to convert all function names to lower case. search = FunctionRegex.Replace(search, m => m.Value.ToLowerInvariant()); @@ -124,11 +122,13 @@ public List Query(Query query) if (result.ToString() == "NaN") { result = Localize.flowlauncher_plugin_calculator_not_a_number(); + isValidResult = false; } if (result is Function) { result = Localize.flowlauncher_plugin_calculator_expression_not_complete(); + isValidResult = false; } if (!string.IsNullOrEmpty(result.ToString())) @@ -137,7 +137,7 @@ public List Query(Query query) string newResult = FormatResult(roundedResult); var results = new List(); - var action = CreateAction(newResult); + var action = CreateClipboardAction(newResult); var resultObject = new Result { Title = newResult, @@ -151,14 +151,18 @@ public List Query(Query query) Action = action }; - if (hasCalculationOperator) + + if (isValidResult) { - History.AddOrUpdate(resultObject, action); + History.AddOrUpdate(resultObject, expression, action); } - results.Add(resultObject); - results.AddRange(History.Items); - return results; + results.Add(resultObject); + var historyItems = _settings.EnableHistory + ? History.Items.Where(x => x.Query != expression) + .OrderByDescending(x => x.CalculatedAt).ToList() + : []; + return results.Concat(historyItems).ToList(); } } @@ -180,7 +184,7 @@ public List Query(Query query) } - private Func CreateAction(string newResult) + private Func CreateClipboardAction(string newResult) { return (_) => { @@ -199,63 +203,7 @@ private Func CreateAction(string newResult) } }; } - private static bool HasCalculationOperator(string input) - { - for (var i = 0; i < input.Length; i++) - { - if (input[i] is not ('+' or '-' or '*' or '/' or '^' or '%')) - { - continue; - } - - var previousIndex = PreviousNonWhitespaceIndex(input, i - 1); - var nextIndex = NextNonWhitespaceIndex(input, i + 1); - - if (previousIndex == -1 || nextIndex == -1) - { - continue; - } - - if (IsOperandCharacter(input[previousIndex]) && IsOperandCharacter(input[nextIndex])) - { - return true; - } - } - - return false; - } - - private static int PreviousNonWhitespaceIndex(string input, int startIndex) - { - for (var i = startIndex; i >= 0; i--) - { - if (!char.IsWhiteSpace(input[i])) - { - return i; - } - } - - return -1; - } - - private static int NextNonWhitespaceIndex(string input, int startIndex) - { - for (var i = startIndex; i < input.Length; i++) - { - if (!char.IsWhiteSpace(input[i])) - { - return i; - } - } - - return -1; - } - - private static bool IsOperandCharacter(char c) - { - return char.IsLetterOrDigit(c) || c is '.' or ',' or ')' or ']' or '\'' or '\u00A0' or '\u202F'; - } - + private static string PowMatchEvaluator(Match m) { // m.Groups[1].Value will be `(...)` with parens diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs index c58f35fbf6c..60a79947921 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs @@ -1,15 +1,34 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; namespace Flow.Launcher.Plugin.Calculator.Storage; -internal class History +public class History { + [JsonInclude] public List Items { get; set; } = []; - public void AddOrUpdate(Result result, Func action) + private const int MaxItems = 5; + + public void AddOrUpdate(Result result, string expression, Func action) { - var item = new HistoryItem(result, action); + var currentItem = Items.FirstOrDefault(x => x.Query == expression); + if (currentItem != null) + { + currentItem.CalculatedAt = DateTime.Now; + return; + } + var item = new HistoryItem(result, expression, action); + Items.Add(item); + UpdateItems(); + } + + public void UpdateItems() + { + Items = Items.OrderByDescending(x => x.CalculatedAt).ToList(); + Items = Items.Take(MaxItems).ToList(); } } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs index 3b417e5ebba..e7e00cd70e3 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs @@ -1,25 +1,31 @@ using System; +using System.Text.Json.Serialization; namespace Flow.Launcher.Plugin.Calculator.Storage; -internal class HistoryItem : Result +public class HistoryItem : Result { - public string Query { get; set; } + public string Query { get; set; } = string.Empty; public DateTime CalculatedAt { get; set; } - public HistoryItem(Result result, Func action) + [JsonIgnore] + private const string BadgeIconPath = "Images/history.png"; + public HistoryItem() { - Title = result.Title; - SubTitle = result.SubTitle; - //PluginID = result.PluginID; - //Query = result.OriginQuery.TrimmedQuery; - //OriginQuery = result.OriginQuery; - RecordKey = result.RecordKey; + } + + public HistoryItem(Result result, string expression, Func action) + { + CalculatedAt = DateTime.Now; + Title = $"{expression} = {result.Title}"; + SubTitle = Localize.flowlauncher_plugin_calculator_history_subtitle(CalculatedAt); + Score = 300; IcoPath = result.IcoPath; - PluginDirectory = result.PluginDirectory; - Glyph = result.Glyph; - //Query = result.Ori - Query = "1+2"; + Query = expression; + BadgeIcoPath = BadgeIconPath; + ShowBadge = true; + CopyText = result.Title; Action = action; } + } From dc2dfb7f167cf1a57f0ce7804c9975acb79eab12 Mon Sep 17 00:00:00 2001 From: dridev Date: Sat, 2 May 2026 20:51:29 -0300 Subject: [PATCH 05/16] calculator tests with history --- Flow.Launcher.Test/Plugins/CalculatorTest.cs | 44 +++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Test/Plugins/CalculatorTest.cs b/Flow.Launcher.Test/Plugins/CalculatorTest.cs index 4e40d3645b0..d8f752bfb2d 100644 --- a/Flow.Launcher.Test/Plugins/CalculatorTest.cs +++ b/Flow.Launcher.Test/Plugins/CalculatorTest.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Reflection; using Flow.Launcher.Plugin.Calculator; +using Flow.Launcher.Plugin.Calculator.Storage; using Mages.Core; using NUnit.Framework; using NUnit.Framework.Legacy; @@ -17,7 +18,8 @@ public class CalculatorPluginTest DecimalSeparator = DecimalSeparator.UseSystemLocale, MaxDecimalPlaces = 10, ShowErrorMessage = false, // Make sure we return the empty results when error occurs - UseThousandsSeparator = true // Default value + UseThousandsSeparator = true, // Default value + EnableHistory = true, }; private readonly Engine _engine = new(new Configuration { @@ -42,6 +44,12 @@ public CalculatorPluginTest() engineField.SetValue(null, _engine); } + [SetUp] + public void SetUp() + { + GetHistory().Items.Clear(); + } + [Test] public void ThousandsSeparatorTest_Enabled() { @@ -80,6 +88,27 @@ public void ThousandsSeparatorTest_LargeNumber() ClassicAssert.AreEqual("1234567", result); } + [Test] + public void CalculationHistory_IsStoredWhenEnabled() + { + _settings.EnableHistory = true; + + _plugin.Query(new Plugin.Query { Search = "1+1" }); + + ClassicAssert.AreEqual(1, GetHistory().Items.Count); + ClassicAssert.AreEqual("1+1", GetHistory().Items[0].Query); + } + + [Test] + public void CalculationHistory_IsNotStoredWhenDisabled() + { + _settings.EnableHistory = false; + + _plugin.Query(new Plugin.Query { Search = "1+1" }); + + ClassicAssert.AreEqual(0, GetHistory().Items.Count); + } + // Basic operations [TestCase(@"1+1", "2")] [TestCase(@"2-1", "1")] @@ -130,5 +159,18 @@ private string GetCalculationResult(string expression) }); return results.Count > 0 ? results[0].Title : string.Empty; } + + private static History GetHistory() + { + var historyField = typeof(Main).GetField("History", BindingFlags.NonPublic | BindingFlags.Static); + Assert.That(historyField, Is.Not.Null, + "Could not find static field 'History' on Flow.Launcher.Plugin.Calculator.Main"); + + var history = historyField!.GetValue(null) as History; + Assert.That(history, Is.Not.Null, + "Static field 'History' on Flow.Launcher.Plugin.Calculator.Main' was not a calculator History instance"); + + return history!; + } } } From a7c1f3311c3c90b6647488f1231067d420011ba1 Mon Sep 17 00:00:00 2001 From: dridev Date: Wed, 6 May 2026 17:38:31 -0300 Subject: [PATCH 06/16] validation to add or not result in history --- Plugins/Flow.Launcher.Plugin.Calculator/Main.cs | 16 +++++++++++----- .../Storage/History.cs | 12 ++++++------ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index fbf048991ef..5a304f0265b 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -61,13 +61,19 @@ public List Query(Query query) { var search = query.Search; bool isFunctionPresent = FunctionRegex.IsMatch(search); - bool isValidResult = true; + bool isValidResultToAddHistory = true; // Mages is case sensitive, so we need to convert all function names to lower case. search = FunctionRegex.Replace(search, m => m.Value.ToLowerInvariant()); var decimalSep = GetDecimalSeparator(); var groupSep = GetGroupSeparator(decimalSep); var expression = NumberRegex.Replace(search, m => NormalizeNumber(m.Value, isFunctionPresent, decimalSep, groupSep)); + + // If only 1 + if (expression.Length == 1) + { + isValidResultToAddHistory = false; + } // WORKAROUND START: The 'pow' function in Mages v3.0.0 is broken. // https://github.com/FlorianRappl/Mages/issues/132 // We bypass it by rewriting any pow(x,y) expression to the equivalent (x^y) expression @@ -122,20 +128,20 @@ public List Query(Query query) if (result.ToString() == "NaN") { result = Localize.flowlauncher_plugin_calculator_not_a_number(); - isValidResult = false; + isValidResultToAddHistory = false; } if (result is Function) { result = Localize.flowlauncher_plugin_calculator_expression_not_complete(); - isValidResult = false; + isValidResultToAddHistory = false; } if (!string.IsNullOrEmpty(result.ToString())) { decimal roundedResult = Math.Round(Convert.ToDecimal(result), _settings.MaxDecimalPlaces, MidpointRounding.AwayFromZero); string newResult = FormatResult(roundedResult); - + var results = new List(); var action = CreateClipboardAction(newResult); var resultObject = new Result @@ -152,7 +158,7 @@ public List Query(Query query) }; - if (isValidResult) + if (isValidResultToAddHistory) { History.AddOrUpdate(resultObject, expression, action); } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs index 60a79947921..0873ba8afbb 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs @@ -20,15 +20,15 @@ public void AddOrUpdate(Result result, string expression, Func= MaxItems) + { + Items.RemoveAt(0); + } + Items.Add(item); - UpdateItems(); } - public void UpdateItems() - { - Items = Items.OrderByDescending(x => x.CalculatedAt).ToList(); - Items = Items.Take(MaxItems).ToList(); - } } From df72b992ca639930de63f04b7f5a845d96d08b1b Mon Sep 17 00:00:00 2001 From: dridev Date: Sun, 10 May 2026 15:35:59 -0300 Subject: [PATCH 07/16] debounce logic to add result in history --- .../Flow.Launcher.Plugin.Calculator/Main.cs | 4 +- .../Storage/History.cs | 71 ++++++++++++++++--- .../Storage/HistoryItem.cs | 19 ++++- 3 files changed, 79 insertions(+), 15 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index 5a304f0265b..4600a882a6b 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -69,7 +69,6 @@ public List Query(Query query) var groupSep = GetGroupSeparator(decimalSep); var expression = NumberRegex.Replace(search, m => NormalizeNumber(m.Value, isFunctionPresent, decimalSep, groupSep)); - // If only 1 if (expression.Length == 1) { isValidResultToAddHistory = false; @@ -165,8 +164,7 @@ public List Query(Query query) results.Add(resultObject); var historyItems = _settings.EnableHistory - ? History.Items.Where(x => x.Query != expression) - .OrderByDescending(x => x.CalculatedAt).ToList() + ? History.GetItemsExcluding(expression) : []; return results.Concat(historyItems).ToList(); diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs index 0873ba8afbb..c9c5c91b928 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs @@ -2,33 +2,84 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; +using System.Threading; namespace Flow.Launcher.Plugin.Calculator.Storage; public class History { + private const int MaxItems = 5; + private const int DebounceDelayMs = 800; + [JsonInclude] public List Items { get; set; } = []; - private const int MaxItems = 5; + [JsonIgnore] + private readonly Lock _syncRoot = new(); + + [JsonIgnore] + private Timer _debounceTimer; + + [JsonIgnore] + private PendingHistoryItem _pendingItem; public void AddOrUpdate(Result result, string expression, Func action) { - var currentItem = Items.FirstOrDefault(x => x.Query == expression); - if (currentItem != null) + lock (_syncRoot) { - currentItem.CalculatedAt = DateTime.Now; - return; - } + _pendingItem = new PendingHistoryItem(result, expression, action); - var item = new HistoryItem(result, expression, action); + if (_debounceTimer == null) + { + _debounceTimer = new Timer(_ => FlushPendingItem(), null, DebounceDelayMs, Timeout.Infinite); + } + else + { + _debounceTimer.Change(DebounceDelayMs, Timeout.Infinite); + } + } + } - if (Items.Count >= MaxItems) + public List GetItemsExcluding(string expression) + { + lock (_syncRoot) { - Items.RemoveAt(0); + return Items + .Where(x => x.Query != expression) + .OrderByDescending(x => x.CalculatedAt) + .ToList(); } + } + + private void FlushPendingItem() + { + lock (_syncRoot) + { + if (_pendingItem == null) + { + return; + } - Items.Add(item); + var currentItem = Items.FirstOrDefault(x => x.Query == _pendingItem.Expression); + if (currentItem != null) + { + currentItem.Refresh(_pendingItem.Result, _pendingItem.Action); + } + else + { + var item = new HistoryItem(_pendingItem.Result, _pendingItem.Expression, _pendingItem.Action); + + if (Items.Count >= MaxItems) + { + Items.RemoveAt(0); + } + + Items.Add(item); + } + + _pendingItem = null; + } } + private sealed record PendingHistoryItem(Result Result, string Expression, Func Action); } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs index e7e00cd70e3..fc09a3191e2 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs @@ -17,8 +17,8 @@ public HistoryItem() public HistoryItem(Result result, string expression, Func action) { CalculatedAt = DateTime.Now; - Title = $"{expression} = {result.Title}"; - SubTitle = Localize.flowlauncher_plugin_calculator_history_subtitle(CalculatedAt); + Title = expression; + SubTitle = CreateSubTitle(result.Title); Score = 300; IcoPath = result.IcoPath; Query = expression; @@ -28,4 +28,19 @@ public HistoryItem(Result result, string expression, Func a Action = action; } + public void Refresh(Result result, Func action) + { + CalculatedAt = DateTime.Now; + SubTitle = CreateSubTitle(result.Title); + CopyText = result.Title; + Action = action; + } + + + private string CreateSubTitle(string value) + { + return + $"{value} - {Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard()}" + + $"\n{Localize.flowlauncher_plugin_calculator_history_subtitle(CalculatedAt)}"; + } } From 4fc3016f93f6ae626b1fb2108fe6d7bf807a6379 Mon Sep 17 00:00:00 2001 From: dridev Date: Sun, 10 May 2026 18:02:47 -0300 Subject: [PATCH 08/16] code quality --- Plugins/Flow.Launcher.Plugin.Calculator/Main.cs | 6 +----- Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs | 6 +++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index 4600a882a6b..32a48474f73 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -69,10 +69,6 @@ public List Query(Query query) var groupSep = GetGroupSeparator(decimalSep); var expression = NumberRegex.Replace(search, m => NormalizeNumber(m.Value, isFunctionPresent, decimalSep, groupSep)); - if (expression.Length == 1) - { - isValidResultToAddHistory = false; - } // WORKAROUND START: The 'pow' function in Mages v3.0.0 is broken. // https://github.com/FlorianRappl/Mages/issues/132 // We bypass it by rewriting any pow(x,y) expression to the equivalent (x^y) expression @@ -159,7 +155,7 @@ public List Query(Query query) if (isValidResultToAddHistory) { - History.AddOrUpdate(resultObject, expression, action); + History.AddOrUpdate(new History.PendingHistoryItem(resultObject, expression, action)); } results.Add(resultObject); diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs index c9c5c91b928..77db13bb03e 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs @@ -23,11 +23,11 @@ public class History [JsonIgnore] private PendingHistoryItem _pendingItem; - public void AddOrUpdate(Result result, string expression, Func action) + public void AddOrUpdate(PendingHistoryItem pendingItem) { lock (_syncRoot) { - _pendingItem = new PendingHistoryItem(result, expression, action); + _pendingItem = pendingItem; if (_debounceTimer == null) { @@ -81,5 +81,5 @@ private void FlushPendingItem() } } - private sealed record PendingHistoryItem(Result Result, string Expression, Func Action); + public sealed record PendingHistoryItem(Result Result, string Expression, Func Action); } From 6d93ce008e738b77ffeb2e4abda2dfd2fcdd6942 Mon Sep 17 00:00:00 2001 From: dridev Date: Sun, 10 May 2026 19:36:44 -0300 Subject: [PATCH 09/16] fix unit tests and code quality --- Flow.Launcher.Test/Plugins/CalculatorTest.cs | 49 ++++++++++++++----- .../Flow.Launcher.Plugin.Calculator/Main.cs | 18 ++++++- .../Storage/History.cs | 9 ++-- .../Storage/HistoryItem.cs | 37 ++++++-------- .../Storage/PendingHistoryItem.cs | 12 +++++ 5 files changed, 85 insertions(+), 40 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Calculator/Storage/PendingHistoryItem.cs diff --git a/Flow.Launcher.Test/Plugins/CalculatorTest.cs b/Flow.Launcher.Test/Plugins/CalculatorTest.cs index d8f752bfb2d..3f0dc53d113 100644 --- a/Flow.Launcher.Test/Plugins/CalculatorTest.cs +++ b/Flow.Launcher.Test/Plugins/CalculatorTest.cs @@ -42,12 +42,14 @@ public CalculatorPluginTest() if (engineField == null) Assert.Fail("Could not find static field 'MagesEngine' on Flow.Launcher.Plugin.Calculator.Main"); engineField.SetValue(null, _engine); + + SetHistory(new History()); } [SetUp] public void SetUp() { - GetHistory().Items.Clear(); + SetHistory(new History()); } [Test] @@ -94,19 +96,26 @@ public void CalculationHistory_IsStoredWhenEnabled() _settings.EnableHistory = true; _plugin.Query(new Plugin.Query { Search = "1+1" }); + WaitForHistoryDebounce(); ClassicAssert.AreEqual(1, GetHistory().Items.Count); ClassicAssert.AreEqual("1+1", GetHistory().Items[0].Query); + ClassicAssert.AreEqual("2", GetHistory().Items[0].CopyText); + ClassicAssert.IsNotEmpty(GetHistory().Items[0].SubTitle); } [Test] - public void CalculationHistory_IsNotStoredWhenDisabled() + public void CalculationHistory_IsNotReturnedWhenDisabled() { - _settings.EnableHistory = false; - + _settings.EnableHistory = true; _plugin.Query(new Plugin.Query { Search = "1+1" }); + WaitForHistoryDebounce(); - ClassicAssert.AreEqual(0, GetHistory().Items.Count); + _settings.EnableHistory = false; + var results = _plugin.Query(new Plugin.Query { Search = "2+2" }); + + ClassicAssert.AreEqual(1, results.Count); + ClassicAssert.AreEqual("4", results[0].Title); } // Basic operations @@ -160,17 +169,35 @@ private string GetCalculationResult(string expression) return results.Count > 0 ? results[0].Title : string.Empty; } - private static History GetHistory() + private void WaitForHistoryDebounce() + { + var flushPendingItemMethod = typeof(History).GetMethod("FlushPendingItem", BindingFlags.NonPublic | BindingFlags.Instance); + Assert.That(flushPendingItemMethod, Is.Not.Null, + "Could not find method 'FlushPendingItem' on Flow.Launcher.Plugin.Calculator.Storage.History"); + + flushPendingItemMethod!.Invoke(GetHistory(), null); + } + + private History GetHistory() { - var historyField = typeof(Main).GetField("History", BindingFlags.NonPublic | BindingFlags.Static); - Assert.That(historyField, Is.Not.Null, - "Could not find static field 'History' on Flow.Launcher.Plugin.Calculator.Main"); + var historyProperty = typeof(Main).GetProperty("History", BindingFlags.NonPublic | BindingFlags.Instance); + Assert.That(historyProperty, Is.Not.Null, + "Could not find property 'History' on Flow.Launcher.Plugin.Calculator.Main"); - var history = historyField!.GetValue(null) as History; + var history = historyProperty!.GetValue(_plugin) as History; Assert.That(history, Is.Not.Null, - "Static field 'History' on Flow.Launcher.Plugin.Calculator.Main' was not a calculator History instance"); + "Property 'History' on Flow.Launcher.Plugin.Calculator.Main' was not a calculator History instance"); return history!; } + + private void SetHistory(History history) + { + var historyProperty = typeof(Main).GetProperty("History", BindingFlags.NonPublic | BindingFlags.Instance); + Assert.That(historyProperty, Is.Not.Null, + "Could not find property 'History' on Flow.Launcher.Plugin.Calculator.Main"); + + historyProperty!.SetValue(_plugin, history); + } } } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index 32a48474f73..12ae5bf5190 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -155,7 +155,8 @@ public List Query(Query query) if (isValidResultToAddHistory) { - History.AddOrUpdate(new History.PendingHistoryItem(resultObject, expression, action)); + var item = CreatePendingHistoryItem(resultObject, newResult, expression, action); + History.AddOrUpdate(item); } results.Add(resultObject); @@ -183,6 +184,21 @@ public List Query(Query query) return EmptyResults; } + private PendingHistoryItem CreatePendingHistoryItem(Result result, string calcResult, string expression, + Func action) + { + var calculatedAt = DateTime.Now; + var copyToClipboard = Context == null + ? "Copy this number to the clipboard" + : Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard(); + var historySubtitle = Context == null + ? string.Format(CultureInfo.CurrentCulture, "Calculated at {0}", calculatedAt) + : Localize.flowlauncher_plugin_calculator_history_subtitle(calculatedAt); + var subtitle = + $"{calcResult} - {copyToClipboard}" + + $"\n{historySubtitle}"; + return new PendingHistoryItem(result, expression, action, subtitle, calculatedAt); + } private Func CreateClipboardAction(string newResult) { diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs index 77db13bb03e..5c40e142947 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; using System.Threading; @@ -63,11 +62,11 @@ private void FlushPendingItem() var currentItem = Items.FirstOrDefault(x => x.Query == _pendingItem.Expression); if (currentItem != null) { - currentItem.Refresh(_pendingItem.Result, _pendingItem.Action); + currentItem.Refresh(_pendingItem); } else { - var item = new HistoryItem(_pendingItem.Result, _pendingItem.Expression, _pendingItem.Action); + var item = new HistoryItem(_pendingItem); if (Items.Count >= MaxItems) { @@ -80,6 +79,4 @@ private void FlushPendingItem() _pendingItem = null; } } - - public sealed record PendingHistoryItem(Result Result, string Expression, Func Action); } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs index fc09a3191e2..ad8a81b8d43 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs @@ -8,39 +8,32 @@ public class HistoryItem : Result public string Query { get; set; } = string.Empty; public DateTime CalculatedAt { get; set; } - [JsonIgnore] - private const string BadgeIconPath = "Images/history.png"; + [JsonIgnore] private const string BadgeIconPath = "Images/history.png"; + public HistoryItem() { } - public HistoryItem(Result result, string expression, Func action) + public HistoryItem(PendingHistoryItem item) { - CalculatedAt = DateTime.Now; - Title = expression; - SubTitle = CreateSubTitle(result.Title); + CalculatedAt = item.CalculatedAt; + Title = item.Expression; + SubTitle = item.SubTitle; Score = 300; - IcoPath = result.IcoPath; - Query = expression; + IcoPath = item.Result.IcoPath; + Query = item.Expression; BadgeIcoPath = BadgeIconPath; ShowBadge = true; - CopyText = result.Title; - Action = action; + CopyText = item.Result.Title; + Action = item.Action; } - public void Refresh(Result result, Func action) + public void Refresh(PendingHistoryItem item) { - CalculatedAt = DateTime.Now; - SubTitle = CreateSubTitle(result.Title); - CopyText = result.Title; - Action = action; + CalculatedAt = item.CalculatedAt; + SubTitle = item.SubTitle; + CopyText = item.Result.Title; + Action = item.Action; } - - private string CreateSubTitle(string value) - { - return - $"{value} - {Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard()}" + - $"\n{Localize.flowlauncher_plugin_calculator_history_subtitle(CalculatedAt)}"; - } } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/PendingHistoryItem.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/PendingHistoryItem.cs new file mode 100644 index 00000000000..569ebe3e4f9 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/PendingHistoryItem.cs @@ -0,0 +1,12 @@ +using System; + +namespace Flow.Launcher.Plugin.Calculator.Storage; + +public sealed record PendingHistoryItem + ( + Result Result, + string Expression, + Func Action, + string SubTitle, + DateTime CalculatedAt + ); From ed6cccf65195765a9db3070a4cd9025d46cb6c48 Mon Sep 17 00:00:00 2001 From: dridev Date: Sat, 4 Jul 2026 17:24:26 -0300 Subject: [PATCH 10/16] order to remove the last item added --- Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs index 5c40e142947..ed7963b1be2 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs @@ -70,6 +70,7 @@ private void FlushPendingItem() if (Items.Count >= MaxItems) { + Items = Items.OrderByDescending(x => x.CalculatedAt).ToList(); Items.RemoveAt(0); } From 83efd29076c8a9faeeba53eb7c442643abdf682d Mon Sep 17 00:00:00 2001 From: dridev Date: Sat, 4 Jul 2026 17:51:18 -0300 Subject: [PATCH 11/16] new modes to save history --- .../HistoryCreationMode.cs | 14 +++++++ .../Languages/en.xaml | 4 ++ .../Languages/pt-br.xaml | 8 +++- .../Settings.cs | 4 +- .../ViewModels/SettingsViewModel.cs | 34 ++++++++++++++- .../Views/CalculatorSettings.xaml | 42 ++++++++++++++++++- 6 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Calculator/HistoryCreationMode.cs diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/HistoryCreationMode.cs b/Plugins/Flow.Launcher.Plugin.Calculator/HistoryCreationMode.cs new file mode 100644 index 00000000000..6dfe9a7399a --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Calculator/HistoryCreationMode.cs @@ -0,0 +1,14 @@ +using Flow.Launcher.Localization.Attributes; + +namespace Flow.Launcher.Plugin.Calculator +{ + [EnumLocalize] + public enum HistoryCreationMode + { + [EnumLocalizeKey(nameof(Localize.flowlauncher_plugin_calculator_history_creation_mode_on_query))] + OnQuery, + + [EnumLocalizeKey(nameof(Localize.flowlauncher_plugin_calculator_history_creation_mode_on_enter))] + OnEnter + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml index 1205f09d3d7..fe615c0eb9d 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml @@ -19,4 +19,8 @@ Show error message when calculation fails Enable History Calculated at {0} + History save mode + Save on query + Save on Enter + Warning: Due to the plugin's architecture, saving on query may register incomplete calculations in the history while you type. diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/pt-br.xaml index 6f3ab9540eb..37218c19177 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/pt-br.xaml +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/pt-br.xaml @@ -1,4 +1,4 @@ - + Calculadora @@ -15,4 +15,10 @@ Show thousands separator in results Copy failed, please try later Show error message when calculation fails + Habilitar Histórico + Calculado em {0} + Modo de salvar no histórico + Salvar por query + Salvar por enter + Aviso: Devido à arquitetura do plugin, salvar por query pode registrar cálculos incompletos no histórico enquanto você digita. diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs index eb1032d2dc1..e94b61581eb 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs @@ -1,4 +1,4 @@ -namespace Flow.Launcher.Plugin.Calculator; +namespace Flow.Launcher.Plugin.Calculator; public class Settings { @@ -11,4 +11,6 @@ public class Settings public bool UseThousandsSeparator { get; set; } = true; public bool EnableHistory { get; set; } = false; + + public HistoryCreationMode HistoryCreationMode { get; set; } = HistoryCreationMode.OnQuery; } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs index 79236bdf8e5..8c78b697c6f 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace Flow.Launcher.Plugin.Calculator.ViewModels; @@ -23,4 +23,36 @@ public DecimalSeparator SelectedDecimalSeparator } } } + + public bool EnableHistory + { + get => Settings.EnableHistory; + set + { + if (Settings.EnableHistory != value) + { + Settings.EnableHistory = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(ShowHistoryQueryWarning)); + } + } + } + + public List AllHistoryCreationMode { get; } = HistoryCreationModeLocalized.GetValues(); + + public HistoryCreationMode SelectedHistoryCreationMode + { + get => Settings.HistoryCreationMode; + set + { + if (Settings.HistoryCreationMode != value) + { + Settings.HistoryCreationMode = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(ShowHistoryQueryWarning)); + } + } + } + + public bool ShowHistoryQueryWarning => EnableHistory && SelectedHistoryCreationMode == HistoryCreationMode.OnQuery; } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml index 70e5c126c6f..ecb2f8fcc30 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml @@ -11,6 +11,10 @@ d:DesignWidth="800" mc:Ignorable="d"> + + + + @@ -18,6 +22,8 @@ + + @@ -89,6 +95,40 @@ HorizontalAlignment="Left" VerticalAlignment="Center" Content="{DynamicResource flowlauncher_plugin_calculator_enable_history}" - IsChecked="{Binding Settings.EnableHistory, Mode=TwoWay}" /> + IsChecked="{Binding EnableHistory, Mode=TwoWay}" /> + + + + + From e7d0dd30d7c51a9b0116640be3aaa657db988efb Mon Sep 17 00:00:00 2001 From: dridev Date: Sat, 4 Jul 2026 19:47:46 -0300 Subject: [PATCH 12/16] query and enter mode --- .../HistoryCreationMode.cs | 9 ++ .../HistoryHelper.cs | 101 ++++++++++++++++++ .../Languages/en.xaml | 14 ++- .../Languages/pt-br.xaml | 6 -- .../Flow.Launcher.Plugin.Calculator/Main.cs | 88 +++++++++++---- .../Storage/History.cs | 95 ++++++++++++---- .../Storage/HistoryItem.cs | 27 +++-- .../Storage/PendingHistoryItem.cs | 2 + 8 files changed, 285 insertions(+), 57 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Calculator/HistoryHelper.cs diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/HistoryCreationMode.cs b/Plugins/Flow.Launcher.Plugin.Calculator/HistoryCreationMode.cs index 6dfe9a7399a..e14d7eaf089 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/HistoryCreationMode.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/HistoryCreationMode.cs @@ -2,12 +2,21 @@ namespace Flow.Launcher.Plugin.Calculator { + /// + /// Represents the different modes for saving calculator calculations into the history. + /// [EnumLocalize] public enum HistoryCreationMode { + /// + /// Saves calculations into the history automatically as the query is typed. + /// [EnumLocalizeKey(nameof(Localize.flowlauncher_plugin_calculator_history_creation_mode_on_query))] OnQuery, + /// + /// Saves calculations into the history only when the action is executed (e.g. Enter is pressed). + /// [EnumLocalizeKey(nameof(Localize.flowlauncher_plugin_calculator_history_creation_mode_on_enter))] OnEnter } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/HistoryHelper.cs b/Plugins/Flow.Launcher.Plugin.Calculator/HistoryHelper.cs new file mode 100644 index 00000000000..0b367456bb1 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Calculator/HistoryHelper.cs @@ -0,0 +1,101 @@ +using System; +using System.Globalization; +using Flow.Launcher.Plugin; +using Flow.Launcher.Plugin.Calculator.Storage; + +namespace Flow.Launcher.Plugin.Calculator +{ + /// + /// Helper class providing utility methods for formatting relative time strings and creating pending history items. + /// + internal static class HistoryHelper + { + /// + /// Formats a DateTime value into a localized relative time string (e.g. "just now", "5 minutes ago"). + /// + /// The plugin init context used to retrieve localized strings. + /// The time when the calculation was recorded. + /// A localized relative time delta string. + public static string GetTimeDeltaString(PluginInitContext context, DateTime calculatedAt) + { + var now = DateTime.Now; + var timeSpan = now - calculatedAt; + + if (timeSpan.TotalSeconds < 0) + { + timeSpan = TimeSpan.Zero; + } + + if (timeSpan.TotalSeconds < 60) + { + return context == null ? "just now" : Localize.flowlauncher_plugin_calculator_time_just_now(); + } + if (timeSpan.TotalMinutes < 60) + { + var minutes = (int)timeSpan.TotalMinutes; + if (minutes == 1) + { + return context == null ? "1 minute ago" : Localize.flowlauncher_plugin_calculator_time_minute_ago(); + } + return context == null ? $"{minutes} minutes ago" : Localize.flowlauncher_plugin_calculator_time_minutes_ago(minutes); + } + if (timeSpan.TotalHours < 24) + { + var hours = (int)timeSpan.TotalHours; + if (hours == 1) + { + return context == null ? "1 hour ago" : Localize.flowlauncher_plugin_calculator_time_hour_ago(); + } + return context == null ? $"{hours} hours ago" : Localize.flowlauncher_plugin_calculator_time_hours_ago(hours); + } + if (timeSpan.TotalDays < 30) + { + var days = (int)timeSpan.TotalDays; + if (days == 1) + { + return context == null ? "1 day ago" : Localize.flowlauncher_plugin_calculator_time_day_ago(); + } + return context == null ? $"{days} days ago" : Localize.flowlauncher_plugin_calculator_time_days_ago(days); + } + if (timeSpan.TotalDays < 365) + { + var months = (int)(timeSpan.TotalDays / 30); + if (months == 1) + { + return context == null ? "1 month ago" : Localize.flowlauncher_plugin_calculator_time_month_ago(); + } + return context == null ? $"{months} months ago" : Localize.flowlauncher_plugin_calculator_time_months_ago(months); + } + var years = (int)(timeSpan.TotalDays / 365); + if (years == 1) + { + return context == null ? "1 year ago" : Localize.flowlauncher_plugin_calculator_time_year_ago(); + } + return context == null ? $"{years} years ago" : Localize.flowlauncher_plugin_calculator_time_years_ago(years); + } + + /// + /// Creates a representing a calculation that is currently being typed by the user. + /// + /// The plugin init context. + /// The query result object. + /// The text representation of the calculation result. + /// The math expression string. + /// A new instance. + public static PendingHistoryItem CreatePendingHistoryItem(PluginInitContext context, Result result, string calcResult, string expression) + { + var calculatedAt = DateTime.Now; + var copyToClipboard = context == null + ? "Copy this number to the clipboard" + : Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard(); + var timeDeltaStr = GetTimeDeltaString(context, calculatedAt); + var historySubtitle = context == null + ? string.Format(CultureInfo.CurrentCulture, "Calculated {0}", timeDeltaStr) + : Localize.flowlauncher_plugin_calculator_history_subtitle(timeDeltaStr); + var subtitle = + $"{calcResult} - {copyToClipboard}" + + $"\n{historySubtitle}"; + return new PendingHistoryItem(result, expression, result.Action, subtitle, calculatedAt); + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml index fe615c0eb9d..e88b12df54a 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml @@ -18,9 +18,21 @@ Copy failed, please try later Show error message when calculation fails Enable History - Calculated at {0} + {0} + just now + 1 minute ago + {0} minutes ago + 1 hour ago + {0} hours ago + 1 day ago + {0} days ago + 1 month ago + {0} months ago + 1 year ago + {0} years ago History save mode Save on query Save on Enter Warning: Due to the plugin's architecture, saving on query may register incomplete calculations in the history while you type. + Copy the result of this expression to the clipboard diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/pt-br.xaml index 37218c19177..6d9b3ebc125 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/pt-br.xaml +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/pt-br.xaml @@ -15,10 +15,4 @@ Show thousands separator in results Copy failed, please try later Show error message when calculation fails - Habilitar Histórico - Calculado em {0} - Modo de salvar no histórico - Salvar por query - Salvar por enter - Aviso: Devido à arquitetura do plugin, salvar por query pode registrar cálculos incompletos no histórico enquanto você digita. diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index 12ae5bf5190..3ebd77f05d7 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -138,7 +138,6 @@ public List Query(Query query) string newResult = FormatResult(roundedResult); var results = new List(); - var action = CreateClipboardAction(newResult); var resultObject = new Result { Title = newResult, @@ -148,22 +147,43 @@ public List Query(Query query) SubTitle = Context == null ? string.Empty : Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard(), - CopyText = newResult, - Action = action + CopyText = newResult }; + resultObject.Action = BuildResultAction(resultObject, newResult, expression); - if (isValidResultToAddHistory) + if (_settings.EnableHistory && _settings.HistoryCreationMode == HistoryCreationMode.OnQuery) { - var item = CreatePendingHistoryItem(resultObject, newResult, expression, action); + var item = HistoryHelper.CreatePendingHistoryItem(Context, resultObject, newResult, expression); History.AddOrUpdate(item); } - results.Add(resultObject); var historyItems = _settings.EnableHistory ? History.GetItemsExcluding(expression) : []; - return results.Concat(historyItems).ToList(); + var resultsToReturn = new List(); + if (_settings.EnableHistory) + { + foreach (var item in historyItems) + { + var timeDeltaStr = HistoryHelper.GetTimeDeltaString(Context, item.CalculatedAt); + var historySubtitle = Context == null + ? string.Format(CultureInfo.CurrentCulture, "{0}", timeDeltaStr) + : Localize.flowlauncher_plugin_calculator_history_subtitle(timeDeltaStr); + resultsToReturn.Add(new Result + { + Title = item.Query, + SubTitle = $"{item.CopyText} - {historySubtitle}", + IcoPath = item.IcoPath, + BadgeIcoPath = item.BadgeIcoPath, + ShowBadge = item.ShowBadge, + Score = item.Score, + CopyText = item.CopyText, + Action = item.Action + }); + } + } + return results.Concat(resultsToReturn).ToList(); } } @@ -184,20 +204,23 @@ public List Query(Query query) return EmptyResults; } - private PendingHistoryItem CreatePendingHistoryItem(Result result, string calcResult, string expression, - Func action) + + + private Func BuildResultAction + ( + Result result, + string newResult, + string expression + ) { - var calculatedAt = DateTime.Now; - var copyToClipboard = Context == null - ? "Copy this number to the clipboard" - : Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard(); - var historySubtitle = Context == null - ? string.Format(CultureInfo.CurrentCulture, "Calculated at {0}", calculatedAt) - : Localize.flowlauncher_plugin_calculator_history_subtitle(calculatedAt); - var subtitle = - $"{calcResult} - {copyToClipboard}" + - $"\n{historySubtitle}"; - return new PendingHistoryItem(result, expression, action, subtitle, calculatedAt); + + var action = + !_settings.EnableHistory || _settings.HistoryCreationMode == HistoryCreationMode.OnQuery + ? CreateClipboardAction(newResult) + : CreateClipboardActionWithHistory(result, newResult, expression); + + return action; + } private Func CreateClipboardAction(string newResult) @@ -219,7 +242,28 @@ private Func CreateClipboardAction(string newResult) } }; } - + + private Func CreateClipboardActionWithHistory(Result resultObject ,string newResult, string expression) + { + return (_) => + { + try + { + Context.API.CopyToClipboard(newResult); + var item = new HistoryItem(resultObject,newResult, expression, DateTime.Now); + History.AddOrUpdate(item); + return true; + } + catch (ExternalException) + { + Context.API.ShowMsgBox( + Localize.flowlauncher_plugin_calculator_failed_to_copy() + ); + return false; + } + }; + } + private static string PowMatchEvaluator(Match m) { // m.Groups[1].Value will be `(...)` with parens diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs index ed7963b1be2..75b69a3dcff 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs @@ -1,15 +1,32 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; using System.Threading; namespace Flow.Launcher.Plugin.Calculator.Storage; +/// +/// Manages the collection of calculation history items, providing methods to add, update, and retrieve items. +/// +/// Supports two creation modes: +/// +/// +/// On Query Mode: Uses with a debounce timer to save calculations automatically as the user types without logging incomplete states. +/// +/// +/// On Enter Mode: Adds or updates a standard immediately without any debounce when the calculation is executed. +/// +/// +/// +/// public class History { private const int MaxItems = 5; private const int DebounceDelayMs = 800; + /// + /// Gets or sets the list of saved history items. + /// [JsonInclude] public List Items { get; set; } = []; @@ -22,6 +39,40 @@ public class History [JsonIgnore] private PendingHistoryItem _pendingItem; + /// + /// Adds a completed history item immediately without any debounce, or updates its timestamp if the query already exists. + /// Used when HistoryCreationMode is set to OnEnter. + /// + /// The history item to add or update. + public void AddOrUpdate(HistoryItem item) + => AddOrUpdateInternal(item); + + /// + /// Unlocked internal helper to add a history item, or update its fields if the query already exists. + /// + /// The history item to add or update. + private void AddOrUpdateInternal(HistoryItem item) + { + var currentItem = Items.FirstOrDefault(x => x.Query == item.Query); + if (currentItem != null) + { + currentItem.CalculatedAt = item.CalculatedAt; + currentItem.Title = item.Title; + currentItem.SubTitle = item.SubTitle; + currentItem.CopyText = item.CopyText; + currentItem.Action = item.Action; + } + else + { + Add(item); + } + } + + /// + /// Schedules a pending history item to be added or updated after a debounce delay. + /// Used when HistoryCreationMode is set to OnQuery to prevent spamming the history with incomplete queries as the user types. + /// + /// The pending history item to be debounced. public void AddOrUpdate(PendingHistoryItem pendingItem) { lock (_syncRoot) @@ -39,6 +90,11 @@ public void AddOrUpdate(PendingHistoryItem pendingItem) } } + /// + /// Retrieves a list of history items, sorted by calculation date in descending order, excluding the specified expression. + /// + /// The expression to exclude from the results. + /// A list of history items matching the criteria. public List GetItemsExcluding(string expression) { lock (_syncRoot) @@ -50,6 +106,24 @@ public List GetItemsExcluding(string expression) } } + /// + /// Adds a history item to the list, removing the oldest calculation if the maximum count is exceeded. + /// + /// The history item to add. + private void Add(HistoryItem item) + { + if (Items.Count >= MaxItems) + { + + Items = Items.OrderByDescending(x => x.CalculatedAt).ToList(); + Items.RemoveAt(0); + } + Items.Add(item); + } + + /// + /// Flushes the currently pending history item by adding it to the list. + /// private void FlushPendingItem() { lock (_syncRoot) @@ -59,23 +133,8 @@ private void FlushPendingItem() return; } - var currentItem = Items.FirstOrDefault(x => x.Query == _pendingItem.Expression); - if (currentItem != null) - { - currentItem.Refresh(_pendingItem); - } - else - { - var item = new HistoryItem(_pendingItem); - - if (Items.Count >= MaxItems) - { - Items = Items.OrderByDescending(x => x.CalculatedAt).ToList(); - Items.RemoveAt(0); - } - - Items.Add(item); - } + var item = new HistoryItem(_pendingItem); + AddOrUpdateInternal(item); _pendingItem = null; } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs index ad8a81b8d43..9c7e16123d2 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text.Json.Serialization; namespace Flow.Launcher.Plugin.Calculator.Storage; @@ -14,26 +14,33 @@ public HistoryItem() { } - public HistoryItem(PendingHistoryItem item) + public HistoryItem(Result item ,string result, string expression, DateTime calculatedAt) { - CalculatedAt = item.CalculatedAt; - Title = item.Expression; - SubTitle = item.SubTitle; + CalculatedAt = calculatedAt; + Title = expression; + SubTitle = result; Score = 300; - IcoPath = item.Result.IcoPath; - Query = item.Expression; + IcoPath = item.IcoPath; + Query = expression; BadgeIcoPath = BadgeIconPath; ShowBadge = true; - CopyText = item.Result.Title; + CopyText = item.Title; Action = item.Action; + } - public void Refresh(PendingHistoryItem item) + + public HistoryItem(PendingHistoryItem item) { CalculatedAt = item.CalculatedAt; + Title = item.Expression; SubTitle = item.SubTitle; + Score = 300; + IcoPath = item.Result.IcoPath; + Query = item.Expression; + BadgeIcoPath = BadgeIconPath; + ShowBadge = true; CopyText = item.Result.Title; Action = item.Action; } - } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/PendingHistoryItem.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/PendingHistoryItem.cs index 569ebe3e4f9..972932199ae 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/PendingHistoryItem.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/PendingHistoryItem.cs @@ -10,3 +10,5 @@ public sealed record PendingHistoryItem string SubTitle, DateTime CalculatedAt ); + + From 2f7f2bbc18a9b08245c28d6fded2b589bb7e500b Mon Sep 17 00:00:00 2001 From: Diego Henrique <124473653+01Dri@users.noreply.github.com> Date: Sat, 4 Jul 2026 19:54:37 -0300 Subject: [PATCH 13/16] Update Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> --- Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs index 75b69a3dcff..8d7779c5bc4 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs @@ -115,7 +115,7 @@ private void Add(HistoryItem item) if (Items.Count >= MaxItems) { - Items = Items.OrderByDescending(x => x.CalculatedAt).ToList(); + Items = Items.OrderBy(x => x.CalculatedAt).ToList(); Items.RemoveAt(0); } Items.Add(item); From 264d9adc623bc1cefd24bd8c0f832d34172f7d98 Mon Sep 17 00:00:00 2001 From: dridev Date: Sat, 4 Jul 2026 19:58:32 -0300 Subject: [PATCH 14/16] code quality after ai review --- .../Flow.Launcher.Plugin.Calculator/Main.cs | 18 ++++++------------ .../Storage/HistoryItem.cs | 15 +++++---------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index 3ebd77f05d7..11d7c127394 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -243,24 +243,18 @@ private Func CreateClipboardAction(string newResult) }; } - private Func CreateClipboardActionWithHistory(Result resultObject ,string newResult, string expression) + private Func CreateClipboardActionWithHistory(Result resultObject, string newResult, string expression) { - return (_) => + var baseAction = CreateClipboardAction(newResult); + return (actionContext) => { - try + if (baseAction(actionContext)) { - Context.API.CopyToClipboard(newResult); - var item = new HistoryItem(resultObject,newResult, expression, DateTime.Now); + var item = new HistoryItem(resultObject, newResult, expression, DateTime.Now); History.AddOrUpdate(item); return true; } - catch (ExternalException) - { - Context.API.ShowMsgBox( - Localize.flowlauncher_plugin_calculator_failed_to_copy() - ); - return false; - } + return false; }; } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs index 9c7e16123d2..94e3d5b5ad0 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs @@ -30,17 +30,12 @@ public HistoryItem(Result item ,string result, string expression, DateTime calcu } + /// + /// Initializes a new instance of the class from a . + /// + /// The pending history item to copy properties from. public HistoryItem(PendingHistoryItem item) + : this(item.Result, item.SubTitle, item.Expression, item.CalculatedAt) { - CalculatedAt = item.CalculatedAt; - Title = item.Expression; - SubTitle = item.SubTitle; - Score = 300; - IcoPath = item.Result.IcoPath; - Query = item.Expression; - BadgeIcoPath = BadgeIconPath; - ShowBadge = true; - CopyText = item.Result.Title; - Action = item.Action; } } From a7efe5d8c0b97a1d9d7c7f74aa6eafceee6deee7 Mon Sep 17 00:00:00 2001 From: dridev Date: Sun, 5 Jul 2026 04:51:34 -0300 Subject: [PATCH 15/16] revert changes pt-br --- .../Flow.Launcher.Plugin.Calculator/Languages/pt-br.xaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/pt-br.xaml index 6d9b3ebc125..37218c19177 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/pt-br.xaml +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/pt-br.xaml @@ -15,4 +15,10 @@ Show thousands separator in results Copy failed, please try later Show error message when calculation fails + Habilitar Histórico + Calculado em {0} + Modo de salvar no histórico + Salvar por query + Salvar por enter + Aviso: Devido à arquitetura do plugin, salvar por query pode registrar cálculos incompletos no histórico enquanto você digita. From 285859d205bf14924a651102cdb6b563568061bf Mon Sep 17 00:00:00 2001 From: David Brett Date: Tue, 14 Jul 2026 09:25:22 +0200 Subject: [PATCH 16/16] Change history badge icon to clearer one --- .../Images/history.png | Bin 3300 -> 2261 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Images/history.png b/Plugins/Flow.Launcher.Plugin.Calculator/Images/history.png index b298f8c82842569c2988214ca6963d6143b235ad..58ebafd56a97b035e20c0ad718a9829ae3cc7f8f 100644 GIT binary patch literal 2261 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 literal 3300 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 N5VD;UsnEth_CI81bol@P