From f40255bc8a65b921c9a1490fad7efde1fabc9a78 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Sat, 6 Sep 2025 15:52:57 +0800
Subject: [PATCH 01/55] Use DialogJump to get explorer path
---
.../FileExplorerHelper.cs | 75 ++-----------------
1 file changed, 5 insertions(+), 70 deletions(-)
diff --git a/Flow.Launcher.Infrastructure/FileExplorerHelper.cs b/Flow.Launcher.Infrastructure/FileExplorerHelper.cs
index 1085cc833..6e2d86849 100644
--- a/Flow.Launcher.Infrastructure/FileExplorerHelper.cs
+++ b/Flow.Launcher.Infrastructure/FileExplorerHelper.cs
@@ -1,8 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using Windows.Win32;
namespace Flow.Launcher.Infrastructure
{
@@ -13,9 +9,10 @@ namespace Flow.Launcher.Infrastructure
///
public static string GetActiveExplorerPath()
{
- var explorerWindow = GetActiveExplorer();
- string locationUrl = explorerWindow?.LocationURL;
- return !string.IsNullOrEmpty(locationUrl) ? GetDirectoryPath(new Uri(locationUrl).LocalPath) : null;
+ var explorerPath = DialogJump.DialogJump.GetActiveExplorerPath();
+ return !string.IsNullOrEmpty(explorerPath) ?
+ GetDirectoryPath(new Uri(explorerPath).LocalPath) :
+ null;
}
///
@@ -23,74 +20,12 @@ namespace Flow.Launcher.Infrastructure
///
private static string GetDirectoryPath(string path)
{
- if (!path.EndsWith("\\"))
+ if (!path.EndsWith('\\'))
{
return path + "\\";
}
return path;
}
-
- ///
- /// Gets the file explorer that is currently in the foreground
- ///
- private static dynamic GetActiveExplorer()
- {
- Type type = Type.GetTypeFromProgID("Shell.Application");
- if (type == null) return null;
- dynamic shell = Activator.CreateInstance(type);
- if (shell == null)
- {
- return null;
- }
-
- var explorerWindows = new List();
- var openWindows = shell.Windows();
- for (int i = 0; i < openWindows.Count; i++)
- {
- var window = openWindows.Item(i);
- if (window == null) continue;
-
- // find the desired window and make sure that it is indeed a file explorer
- // we don't want the Internet Explorer or the classic control panel
- // ToLower() is needed, because Windows can report the path as "C:\\Windows\\Explorer.EXE"
- if (Path.GetFileName((string)window.FullName)?.ToLower() == "explorer.exe")
- {
- explorerWindows.Add(window);
- }
- }
-
- if (explorerWindows.Count == 0) return null;
-
- var zOrders = GetZOrder(explorerWindows);
-
- return explorerWindows.Zip(zOrders).MinBy(x => x.Second).First;
- }
-
- ///
- /// Gets the z-order for one or more windows atomically with respect to each other. In Windows, smaller z-order is higher. If the window is not top level, the z order is returned as -1.
- ///
- private static IEnumerable GetZOrder(List hWnds)
- {
- var z = new int[hWnds.Count];
- for (var i = 0; i < hWnds.Count; i++) z[i] = -1;
-
- var index = 0;
- var numRemaining = hWnds.Count;
- PInvoke.EnumWindows((wnd, _) =>
- {
- var searchIndex = hWnds.FindIndex(x => new IntPtr(x.HWND) == wnd);
- if (searchIndex != -1)
- {
- z[searchIndex] = index;
- numRemaining--;
- if (numRemaining == 0) return false;
- }
- index++;
- return true;
- }, IntPtr.Zero);
-
- return z;
- }
}
}
From e1079396c3bfb6e3a22ef4227900c91460cd300d Mon Sep 17 00:00:00 2001
From: dcog989
Date: Fri, 12 Sep 2025 19:20:19 +0100
Subject: [PATCH 02/55] backout 'smart' digit grouping, mages fixes +
workaround
Mages did not like the previous change to smart thousands / decimal so backed that out.
Workaround for https://github.com/FlorianRappl/Mages/issues/132
---
.../Flow.Launcher.Plugin.Calculator/Main.cs | 168 +++++++-----------
.../MainRegexHelper.cs | 2 +-
2 files changed, 65 insertions(+), 105 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
index 6878c54b4..d2e1ed821 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
@@ -13,7 +13,6 @@ namespace Flow.Launcher.Plugin.Calculator
{
public class Main : IPlugin, IPluginI18n, ISettingProvider
{
- private static readonly Regex RegValidExpressChar = MainRegexHelper.GetRegValidExpressChar();
private static readonly Regex RegBrackets = MainRegexHelper.GetRegBrackets();
private static readonly Regex ThousandGroupRegex = MainRegexHelper.GetThousandGroupRegex();
private static readonly Regex NumberRegex = MainRegexHelper.GetNumberRegex();
@@ -27,16 +26,6 @@ namespace Flow.Launcher.Plugin.Calculator
private Settings _settings;
private SettingsViewModel _viewModel;
- ///
- /// Holds the formatting information for a single query.
- /// This is used to ensure thread safety by keeping query state local.
- ///
- private class ParsingContext
- {
- public string InputDecimalSeparator { get; set; }
- public bool InputUsesGroupSeparators { get; set; }
- }
-
public void Init(PluginInitContext context)
{
Context = context;
@@ -59,24 +48,46 @@ namespace Flow.Launcher.Plugin.Calculator
return new List();
}
- var context = new ParsingContext();
-
try
{
- var expression = NumberRegex.Replace(query.Search, m => NormalizeNumber(m.Value, context));
+ var expression = NumberRegex.Replace(query.Search, m => NormalizeNumber(m.Value));
+
+ // 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
+ // before the engine sees it. This loop handles nested calls.
+ string previous;
+ do
+ {
+ previous = expression;
+ expression = Regex.Replace(previous, @"\bpow\s*\(\s*([^,]+?)\s*,\s*([^)]+?)\s*\)", "($1^$2)");
+ } while (previous != expression);
+ // WORKAROUND END
var result = MagesEngine.Interpret(expression);
- if (result?.ToString() == "NaN")
+ if (result == null || string.IsNullOrEmpty(result.ToString()))
+ {
+ return new List
+ {
+ new Result
+ {
+ Title = Localize.flowlauncher_plugin_calculator_expression_not_complete(),
+ IcoPath = "Images/calculator.png"
+ }
+ };
+ }
+
+ if (result.ToString() == "NaN")
result = Localize.flowlauncher_plugin_calculator_not_a_number();
if (result is Function)
result = Localize.flowlauncher_plugin_calculator_expression_not_complete();
- if (!string.IsNullOrEmpty(result?.ToString()))
+ if (!string.IsNullOrEmpty(result.ToString()))
{
decimal roundedResult = Math.Round(Convert.ToDecimal(result), _settings.MaxDecimalPlaces, MidpointRounding.AwayFromZero);
- string newResult = FormatResult(roundedResult, context);
+ string newResult = FormatResult(roundedResult);
return new List
{
@@ -104,115 +115,69 @@ namespace Flow.Launcher.Plugin.Calculator
};
}
}
- catch (Exception)
+ catch (Exception e)
{
- // ignored
+ return new List
+ {
+ new Result
+ {
+ Title = e.Message,
+ SubTitle = "Calculator Exception",
+ IcoPath = "Images/calculator.png",
+ Score = 300
+ }
+ };
}
return new List();
}
///
- /// Parses a string representation of a number, detecting its format. It uses structural analysis
- /// and falls back to system culture for truly ambiguous cases (e.g., "1,234").
- /// It populates the provided ParsingContext with the detected format for later use.
+ /// Parses a string representation of a number using the system's current culture.
///
/// A normalized number string with '.' as the decimal separator for the Mages engine.
- private string NormalizeNumber(string numberStr, ParsingContext context)
+ private string NormalizeNumber(string numberStr)
{
- var systemGroupSep = CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator;
- int dotCount = numberStr.Count(f => f == '.');
- int commaCount = numberStr.Count(f => f == ',');
+ var culture = CultureInfo.CurrentCulture;
+ var groupSep = culture.NumberFormat.NumberGroupSeparator;
- // Case 1: Unambiguous mixed separators (e.g., "1.234,56")
- if (dotCount > 0 && commaCount > 0)
+ // If the string contains the group separator, check if it's used correctly.
+ if (!string.IsNullOrEmpty(groupSep) && numberStr.Contains(groupSep))
{
- context.InputUsesGroupSeparators = true;
- if (numberStr.LastIndexOf('.') > numberStr.LastIndexOf(','))
+ var parts = numberStr.Split(groupSep);
+ // If any part after the first (excluding a possible last part with a decimal)
+ // does not have 3 digits, then it's not a valid use of a thousand separator.
+ for (int i = 1; i < parts.Length; i++)
{
- context.InputDecimalSeparator = Dot;
- return numberStr.Replace(Comma, string.Empty);
- }
- else
- {
- context.InputDecimalSeparator = Comma;
- return numberStr.Replace(Dot, string.Empty).Replace(Comma, Dot);
- }
- }
-
- // Case 2: Only dots
- if (dotCount > 0)
- {
- if (dotCount > 1)
- {
- context.InputUsesGroupSeparators = true;
- return numberStr.Replace(Dot, string.Empty);
- }
- // A number is ambiguous if it has a single Dot in the thousands position,
- // and does not start with a "0." or "."
- bool isAmbiguous = numberStr.Length - numberStr.LastIndexOf('.') == 4
- && !numberStr.StartsWith("0.")
- && !numberStr.StartsWith(".");
- if (isAmbiguous)
- {
- if (systemGroupSep == Dot)
+ var part = parts[i];
+ // The last part might contain a decimal separator.
+ if (i == parts.Length - 1 && part.Contains(culture.NumberFormat.NumberDecimalSeparator))
{
- context.InputUsesGroupSeparators = true;
- return numberStr.Replace(Dot, string.Empty);
+ part = part.Split(culture.NumberFormat.NumberDecimalSeparator)[0];
}
- else
+
+ if (part.Length != 3)
{
- context.InputDecimalSeparator = Dot;
+ // This is not a number with valid thousand separators,
+ // so it must be arguments to a function. Return it unmodified.
return numberStr;
}
}
- else // Unambiguous decimal (e.g., "12.34" or "0.123" or ".123")
- {
- context.InputDecimalSeparator = Dot;
- return numberStr;
- }
}
- // Case 3: Only commas
- if (commaCount > 0)
+ // At this point, any group separators are in valid positions (or there are none).
+ // We can safely parse with the user's culture.
+ if (decimal.TryParse(numberStr, NumberStyles.Any, culture, out var number))
{
- if (commaCount > 1)
- {
- context.InputUsesGroupSeparators = true;
- return numberStr.Replace(Comma, string.Empty);
- }
- // A number is ambiguous if it has a single Comma in the thousands position,
- // and does not start with a "0," or ","
- bool isAmbiguous = numberStr.Length - numberStr.LastIndexOf(',') == 4
- && !numberStr.StartsWith("0,")
- && !numberStr.StartsWith(",");
- if (isAmbiguous)
- {
- if (systemGroupSep == Comma)
- {
- context.InputUsesGroupSeparators = true;
- return numberStr.Replace(Comma, string.Empty);
- }
- else
- {
- context.InputDecimalSeparator = Comma;
- return numberStr.Replace(Comma, Dot);
- }
- }
- else // Unambiguous decimal (e.g., "12,34" or "0,123" or ",123")
- {
- context.InputDecimalSeparator = Comma;
- return numberStr.Replace(Comma, Dot);
- }
+ return number.ToString(CultureInfo.InvariantCulture);
}
- // Case 4: No separators
return numberStr;
}
- private string FormatResult(decimal roundedResult, ParsingContext context)
+ private string FormatResult(decimal roundedResult)
{
- string decimalSeparator = context.InputDecimalSeparator ?? GetDecimalSeparator();
+ string decimalSeparator = GetDecimalSeparator();
string groupSeparator = GetGroupSeparator(decimalSeparator);
string resultStr = roundedResult.ToString(CultureInfo.InvariantCulture);
@@ -221,7 +186,7 @@ namespace Flow.Launcher.Plugin.Calculator
string integerPart = parts[0];
string fractionalPart = parts.Length > 1 ? parts[1] : string.Empty;
- if (context.InputUsesGroupSeparators && integerPart.Length > 3)
+ if (integerPart.Length > 3)
{
integerPart = ThousandGroupRegex.Replace(integerPart, groupSeparator);
}
@@ -248,11 +213,6 @@ namespace Flow.Launcher.Plugin.Calculator
return false;
}
- if (!RegValidExpressChar.IsMatch(query.Search))
- {
- return false;
- }
-
if (!IsBracketComplete(query.Search))
{
return false;
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs b/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
index f4e2090e7..85db0c2cd 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
@@ -11,7 +11,7 @@ internal static partial class MainRegexHelper
[GeneratedRegex(@"^(ceil|floor|exp|pi|e|max|min|det|abs|log|ln|sqrt|sin|cos|tan|arcsin|arccos|arctan|eigval|eigvec|eig|sum|polar|plot|round|sort|real|zeta|bin2dec|hex2dec|oct2dec|factorial|sign|isprime|isinfty|==|~=|&&|\|\||(?:\<|\>)=?|[ei]|[0-9]|0x[\da-fA-F]+|[\+\%\-\*\/\^\., ""]|[\(\)\|\!\[\]])+$", RegexOptions.Compiled)]
public static partial Regex GetRegValidExpressChar();
- [GeneratedRegex(@"[\d\.,]+", RegexOptions.Compiled)]
+ [GeneratedRegex(@"-?[\d\.,]+", RegexOptions.Compiled)]
public static partial Regex GetNumberRegex();
[GeneratedRegex(@"\B(?=(\d{3})+(?!\d))", RegexOptions.Compiled)]
From 103d3832a087a6a9d7bc341898d9291367fdd158 Mon Sep 17 00:00:00 2001
From: dcog989
Date: Fri, 12 Sep 2025 19:30:07 +0100
Subject: [PATCH 03/55] dead code, improve messages, group separator fix?
---
.../Flow.Launcher.Plugin.Calculator/Main.cs | 18 +++++++++++-------
.../MainRegexHelper.cs | 3 ---
2 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
index d2e1ed821..85cd8a6fd 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
@@ -115,16 +115,15 @@ namespace Flow.Launcher.Plugin.Calculator
};
}
}
- catch (Exception e)
+ catch (Exception)
{
+ // Mages engine can throw various exceptions, for simplicity we catch them all and show a generic message.
return new List
{
new Result
{
- Title = e.Message,
- SubTitle = "Calculator Exception",
- IcoPath = "Images/calculator.png",
- Score = 300
+ Title = Localize.flowlauncher_plugin_calculator_expression_not_complete(),
+ IcoPath = "Images/calculator.png"
}
};
}
@@ -201,14 +200,19 @@ namespace Flow.Launcher.Plugin.Calculator
private string GetGroupSeparator(string decimalSeparator)
{
+ if (_settings.DecimalSeparator == DecimalSeparator.UseSystemLocale)
+ {
+ return CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator;
+ }
+
// This logic is now independent of the system's group separator
- // to ensure consistent output for unit testing.
+ // to ensure consistent output when a specific separator is chosen.
return decimalSeparator == Dot ? Comma : Dot;
}
private bool CanCalculate(Query query)
{
- if (query.Search.Length < 2)
+ if (string.IsNullOrWhiteSpace(query.Search))
{
return false;
}
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs b/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
index 85db0c2cd..2e0353614 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
@@ -8,9 +8,6 @@ internal static partial class MainRegexHelper
[GeneratedRegex(@"[\(\)\[\]]", RegexOptions.Compiled)]
public static partial Regex GetRegBrackets();
- [GeneratedRegex(@"^(ceil|floor|exp|pi|e|max|min|det|abs|log|ln|sqrt|sin|cos|tan|arcsin|arccos|arctan|eigval|eigvec|eig|sum|polar|plot|round|sort|real|zeta|bin2dec|hex2dec|oct2dec|factorial|sign|isprime|isinfty|==|~=|&&|\|\||(?:\<|\>)=?|[ei]|[0-9]|0x[\da-fA-F]+|[\+\%\-\*\/\^\., ""]|[\(\)\|\!\[\]])+$", RegexOptions.Compiled)]
- public static partial Regex GetRegValidExpressChar();
-
[GeneratedRegex(@"-?[\d\.,]+", RegexOptions.Compiled)]
public static partial Regex GetNumberRegex();
From 190e0e179f28ba12ad240347971cb5aec7454e07 Mon Sep 17 00:00:00 2001
From: dcog989
Date: Fri, 12 Sep 2025 20:01:55 +0100
Subject: [PATCH 04/55] Fix 'German' number formatting
---
Plugins/Flow.Launcher.Plugin.Calculator/Main.cs | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
index 85cd8a6fd..c81eb9b1c 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
@@ -139,6 +139,7 @@ namespace Flow.Launcher.Plugin.Calculator
{
var culture = CultureInfo.CurrentCulture;
var groupSep = culture.NumberFormat.NumberGroupSeparator;
+ var decimalSep = culture.NumberFormat.NumberDecimalSeparator;
// If the string contains the group separator, check if it's used correctly.
if (!string.IsNullOrEmpty(groupSep) && numberStr.Contains(groupSep))
@@ -164,14 +165,11 @@ namespace Flow.Launcher.Plugin.Calculator
}
}
- // At this point, any group separators are in valid positions (or there are none).
- // We can safely parse with the user's culture.
- if (decimal.TryParse(numberStr, NumberStyles.Any, culture, out var number))
- {
- return number.ToString(CultureInfo.InvariantCulture);
- }
+ // If validation passes, we can assume the separators are used correctly for numbers.
+ string processedStr = numberStr.Replace(groupSep, "");
+ processedStr = processedStr.Replace(decimalSep, ".");
- return numberStr;
+ return processedStr;
}
private string FormatResult(decimal roundedResult)
From bd186e7fe1bf046d54311175767b648fd8913969 Mon Sep 17 00:00:00 2001
From: dcog989
Date: Fri, 12 Sep 2025 20:02:34 +0100
Subject: [PATCH 05/55] correct + extend description
---
Plugins/Flow.Launcher.Plugin.Calculator/plugin.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json b/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json
index c9435e043..7b4b53cdb 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json
@@ -2,7 +2,7 @@
"ID": "CEA0FDFC6D3B4085823D60DC76F28855",
"ActionKeyword": "*",
"Name": "Calculator",
- "Description": "Perform mathematical calculations (including hexadecimal values). Use ',' or '.' as thousand separator or decimal place.",
+ "Description": "Perform mathematical calculations, including hex values and advanced math functions, such as 'min(1,2,3)', 'sqrt(123)', 'cos(123)', etc.. User locale determines thousand separator and decimal place.",
"Author": "cxfksword, dcog989",
"Version": "1.0.0",
"Language": "csharp",
From 110f571b40840e798237c304216b2435d9845796 Mon Sep 17 00:00:00 2001
From: dcog989
Date: Sat, 13 Sep 2025 13:17:08 +0100
Subject: [PATCH 06/55] Rework solution for nested Mages
Previous solution missed e.g. `pow(min(2,3), 4)`
---
.../Flow.Launcher.Plugin.Calculator/Main.cs | 89 +++++++++++++++----
1 file changed, 72 insertions(+), 17 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
index c81eb9b1c..99f0f8395 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
@@ -16,6 +16,8 @@ namespace Flow.Launcher.Plugin.Calculator
private static readonly Regex RegBrackets = MainRegexHelper.GetRegBrackets();
private static readonly Regex ThousandGroupRegex = MainRegexHelper.GetThousandGroupRegex();
private static readonly Regex NumberRegex = MainRegexHelper.GetNumberRegex();
+ private static readonly Regex PowRegex = new(@"\bpow(\((?:[^()\[\]]|\((?)|\)(?<-Depth>)|\[(?)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft);
+
private static Engine MagesEngine;
private const string Comma = ",";
@@ -43,11 +45,23 @@ namespace Flow.Launcher.Plugin.Calculator
public List Query(Query query)
{
- if (!CanCalculate(query))
+ if (string.IsNullOrWhiteSpace(query.Search))
{
return new List();
}
+ if (!IsBracketComplete(query.Search))
+ {
+ return new List
+ {
+ new Result
+ {
+ Title = Localize.flowlauncher_plugin_calculator_expression_not_complete(),
+ IcoPath = "Images/calculator.png"
+ }
+ };
+ }
+
try
{
var expression = NumberRegex.Replace(query.Search, m => NormalizeNumber(m.Value));
@@ -60,7 +74,7 @@ namespace Flow.Launcher.Plugin.Calculator
do
{
previous = expression;
- expression = Regex.Replace(previous, @"\bpow\s*\(\s*([^,]+?)\s*,\s*([^)]+?)\s*\)", "($1^$2)");
+ expression = PowRegex.Replace(previous, PowMatchEvaluator);
} while (previous != expression);
// WORKAROUND END
@@ -131,6 +145,57 @@ namespace Flow.Launcher.Plugin.Calculator
return new List();
}
+ private static string PowMatchEvaluator(Match m)
+ {
+ // m.Groups[1].Value will be `(...)` with parens
+ var contentWithParen = m.Groups[1].Value;
+ // remove outer parens. `(min(2,3), 4)` becomes `min(2,3), 4`
+ var argsContent = contentWithParen.Substring(1, contentWithParen.Length - 2);
+
+ var bracketCount = 0;
+ var splitIndex = -1;
+
+ // Find the top-level comma that separates the two arguments of pow.
+ for (var i = 0; i < argsContent.Length; i++)
+ {
+ switch (argsContent[i])
+ {
+ case '(':
+ case '[':
+ bracketCount++;
+ break;
+ case ')':
+ case ']':
+ bracketCount--;
+ break;
+ case ',' when bracketCount == 0:
+ splitIndex = i;
+ break;
+ }
+
+ if (splitIndex != -1)
+ break;
+ }
+
+ if (splitIndex == -1)
+ {
+ // This indicates malformed arguments for pow, e.g., pow(5) or pow().
+ // Return original string to let Mages handle the error.
+ return m.Value;
+ }
+
+ var arg1 = argsContent.Substring(0, splitIndex).Trim();
+ var arg2 = argsContent.Substring(splitIndex + 1).Trim();
+
+ // Check for empty arguments which can happen with stray commas, e.g., pow(,5)
+ if (string.IsNullOrEmpty(arg1) || string.IsNullOrEmpty(arg2))
+ {
+ return m.Value;
+ }
+
+ return $"({arg1}^{arg2})";
+ }
+
///
/// Parses a string representation of a number using the system's current culture.
///
@@ -208,21 +273,6 @@ namespace Flow.Launcher.Plugin.Calculator
return decimalSeparator == Dot ? Comma : Dot;
}
- private bool CanCalculate(Query query)
- {
- if (string.IsNullOrWhiteSpace(query.Search))
- {
- return false;
- }
-
- if (!IsBracketComplete(query.Search))
- {
- return false;
- }
-
- return true;
- }
-
private string GetDecimalSeparator()
{
string systemDecimalSeparator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
@@ -249,6 +299,11 @@ namespace Flow.Launcher.Plugin.Calculator
{
leftBracketCount--;
}
+
+ if (leftBracketCount < 0)
+ {
+ return false;
+ }
}
return leftBracketCount == 0;
From 11f5ea5074be16e3b712a05f4484b1c91e3bfa28 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Sun, 14 Sep 2025 12:33:46 +0800
Subject: [PATCH 07/55] Improve code quality
---
Plugins/Flow.Launcher.Plugin.Calculator/Main.cs | 6 ++----
Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs | 4 +++-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
index 99f0f8395..0fd32555f 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
-using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Windows.Controls;
@@ -16,8 +15,7 @@ namespace Flow.Launcher.Plugin.Calculator
private static readonly Regex RegBrackets = MainRegexHelper.GetRegBrackets();
private static readonly Regex ThousandGroupRegex = MainRegexHelper.GetThousandGroupRegex();
private static readonly Regex NumberRegex = MainRegexHelper.GetNumberRegex();
- private static readonly Regex PowRegex = new(@"\bpow(\((?:[^()\[\]]|\((?)|\)(?<-Depth>)|\[(?)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft);
-
+ private static readonly Regex PowRegex = MainRegexHelper.GetPowRegex();
private static Engine MagesEngine;
private const string Comma = ",";
@@ -200,7 +198,7 @@ namespace Flow.Launcher.Plugin.Calculator
/// Parses a string representation of a number using the system's current culture.
///
/// A normalized number string with '.' as the decimal separator for the Mages engine.
- private string NormalizeNumber(string numberStr)
+ private static string NormalizeNumber(string numberStr)
{
var culture = CultureInfo.CurrentCulture;
var groupSep = culture.NumberFormat.NumberGroupSeparator;
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs b/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
index 2e0353614..d8c2795dc 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
@@ -4,7 +4,6 @@ namespace Flow.Launcher.Plugin.Calculator;
internal static partial class MainRegexHelper
{
-
[GeneratedRegex(@"[\(\)\[\]]", RegexOptions.Compiled)]
public static partial Regex GetRegBrackets();
@@ -13,4 +12,7 @@ internal static partial class MainRegexHelper
[GeneratedRegex(@"\B(?=(\d{3})+(?!\d))", RegexOptions.Compiled)]
public static partial Regex GetThousandGroupRegex();
+
+ [GeneratedRegex(@"\bpow(\((?:[^()\[\]]|\((?)|\)(?<-Depth>)|\[(?)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft)]
+ public static partial Regex GetPowRegex();
}
From 495ace124687f86fd9828439ddaab2436079972d Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Sun, 14 Sep 2025 12:39:11 +0800
Subject: [PATCH 08/55] Improve plugin description
---
Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml | 2 +-
Plugins/Flow.Launcher.Plugin.Calculator/plugin.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml
index b71e5d8a0..29a0ed26f 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml
@@ -4,7 +4,7 @@
xmlns:system="clr-namespace:System;assembly=mscorlib">
Calculator
- Perform mathematical calculations (including hexadecimal values). Use ',' or '.' as thousand separator or decimal place.
+ Perform mathematical calculations, including hex values and advanced functions such as 'min(1,2,3)', 'sqrt(123)' and 'cos(123)'.
Not a number (NaN)
Expression wrong or incomplete (Did you forget some parentheses?)
Copy this number to the clipboard
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json b/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json
index 7b4b53cdb..93df9ec72 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json
@@ -2,7 +2,7 @@
"ID": "CEA0FDFC6D3B4085823D60DC76F28855",
"ActionKeyword": "*",
"Name": "Calculator",
- "Description": "Perform mathematical calculations, including hex values and advanced math functions, such as 'min(1,2,3)', 'sqrt(123)', 'cos(123)', etc.. User locale determines thousand separator and decimal place.",
+ "Description": "Perform mathematical calculations, including hex values and advanced functions such as 'min(1,2,3)', 'sqrt(123)' and 'cos(123)'.",
"Author": "cxfksword, dcog989",
"Version": "1.0.0",
"Language": "csharp",
From daf35a49725fab9107104238a81e72295ef7965f Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Sun, 14 Sep 2025 15:54:35 +0800
Subject: [PATCH 09/55] Do not check bracket complete
---
.../Flow.Launcher.Plugin.Calculator/Main.cs | 36 -------------------
1 file changed, 36 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
index 0fd32555f..d19dbc9c2 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
@@ -48,18 +48,6 @@ namespace Flow.Launcher.Plugin.Calculator
return new List();
}
- if (!IsBracketComplete(query.Search))
- {
- return new List
- {
- new Result
- {
- Title = Localize.flowlauncher_plugin_calculator_expression_not_complete(),
- IcoPath = "Images/calculator.png"
- }
- };
- }
-
try
{
var expression = NumberRegex.Replace(query.Search, m => NormalizeNumber(m.Value));
@@ -283,30 +271,6 @@ namespace Flow.Launcher.Plugin.Calculator
};
}
- private static bool IsBracketComplete(string query)
- {
- var matchs = RegBrackets.Matches(query);
- var leftBracketCount = 0;
- foreach (Match match in matchs)
- {
- if (match.Value == "(" || match.Value == "[")
- {
- leftBracketCount++;
- }
- else
- {
- leftBracketCount--;
- }
-
- if (leftBracketCount < 0)
- {
- return false;
- }
- }
-
- return leftBracketCount == 0;
- }
-
public string GetTranslatedPluginTitle()
{
return Localize.flowlauncher_plugin_calculator_plugin_name();
From 336e51d1047f085b83fb5bb9b886b3b11b4dd0ed Mon Sep 17 00:00:00 2001
From: dcog989
Date: Sun, 14 Sep 2025 17:08:37 +0100
Subject: [PATCH 10/55] IcoPath to const string
---
Plugins/Flow.Launcher.Plugin.Calculator/Main.cs | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
index d19dbc9c2..54ac61ffb 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
@@ -20,6 +20,7 @@ namespace Flow.Launcher.Plugin.Calculator
private static Engine MagesEngine;
private const string Comma = ",";
private const string Dot = ".";
+ private const string IcoPath = "Images/calculator.png";
internal static PluginInitContext Context { get; set; } = null!;
@@ -73,7 +74,7 @@ namespace Flow.Launcher.Plugin.Calculator
new Result
{
Title = Localize.flowlauncher_plugin_calculator_expression_not_complete(),
- IcoPath = "Images/calculator.png"
+ IcoPath = IcoPath
}
};
}
@@ -94,7 +95,7 @@ namespace Flow.Launcher.Plugin.Calculator
new Result
{
Title = newResult,
- IcoPath = "Images/calculator.png",
+ IcoPath = IcoPath,
Score = 300,
SubTitle = Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard(),
CopyText = newResult,
@@ -123,7 +124,7 @@ namespace Flow.Launcher.Plugin.Calculator
new Result
{
Title = Localize.flowlauncher_plugin_calculator_expression_not_complete(),
- IcoPath = "Images/calculator.png"
+ IcoPath = IcoPath
}
};
}
From e990e0ff5b38ead1cc1f9851a573aac7802951a8 Mon Sep 17 00:00:00 2001
From: dcog989
Date: Sun, 14 Sep 2025 19:39:06 +0100
Subject: [PATCH 11/55] Handle misplaced separators, Mages edge cases
Allow for e.g. `25,00` when `,` used as digit grouping.
Exclude Mages function from the above relaxed logic.
---
.../Flow.Launcher.Plugin.Calculator/Main.cs | 132 +++++++++++++-----
.../MainRegexHelper.cs | 6 +-
2 files changed, 103 insertions(+), 35 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
index 54ac61ffb..a15420a63 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Windows.Controls;
@@ -12,10 +13,10 @@ namespace Flow.Launcher.Plugin.Calculator
{
public class Main : IPlugin, IPluginI18n, ISettingProvider
{
- private static readonly Regex RegBrackets = MainRegexHelper.GetRegBrackets();
private static readonly Regex ThousandGroupRegex = MainRegexHelper.GetThousandGroupRegex();
private static readonly Regex NumberRegex = MainRegexHelper.GetNumberRegex();
private static readonly Regex PowRegex = MainRegexHelper.GetPowRegex();
+ private static readonly Regex FunctionRegex = MainRegexHelper.GetFunctionRegex();
private static Engine MagesEngine;
private const string Comma = ",";
@@ -51,7 +52,8 @@ namespace Flow.Launcher.Plugin.Calculator
try
{
- var expression = NumberRegex.Replace(query.Search, m => NormalizeNumber(m.Value));
+ bool isFunctionPresent = FunctionRegex.IsMatch(query.Search);
+ var expression = NumberRegex.Replace(query.Search, m => NormalizeNumber(m.Value, isFunctionPresent));
// WORKAROUND START: The 'pow' function in Mages v3.0.0 is broken.
// https://github.com/FlorianRappl/Mages/issues/132
@@ -183,47 +185,104 @@ namespace Flow.Launcher.Plugin.Calculator
return $"({arg1}^{arg2})";
}
- ///
- /// Parses a string representation of a number using the system's current culture.
- ///
- /// A normalized number string with '.' as the decimal separator for the Mages engine.
- private static string NormalizeNumber(string numberStr)
+ private static string NormalizeNumber(string numberStr, bool isFunctionPresent)
{
var culture = CultureInfo.CurrentCulture;
var groupSep = culture.NumberFormat.NumberGroupSeparator;
var decimalSep = culture.NumberFormat.NumberDecimalSeparator;
- // If the string contains the group separator, check if it's used correctly.
- if (!string.IsNullOrEmpty(groupSep) && numberStr.Contains(groupSep))
+ if (isFunctionPresent)
{
- var parts = numberStr.Split(groupSep);
- // If any part after the first (excluding a possible last part with a decimal)
- // does not have 3 digits, then it's not a valid use of a thousand separator.
- for (int i = 1; i < parts.Length; i++)
+ // STRICT MODE: When functions are present, ',' is ALWAYS an argument separator.
+ // It must not be normalized.
+ if (numberStr.Contains(','))
{
- var part = parts[i];
- // The last part might contain a decimal separator.
- if (i == parts.Length - 1 && part.Contains(culture.NumberFormat.NumberDecimalSeparator))
- {
- part = part.Split(culture.NumberFormat.NumberDecimalSeparator)[0];
- }
+ return numberStr;
+ }
- if (part.Length != 3)
+ // The string has no commas. It could have a '.' group separator (e.g. in de-DE)
+ // or a '.' decimal separator (e.g. in en-US).
+ // Since Mages' decimal separator is '.', we only need to strip the group separator.
+ if (groupSep == ".")
+ {
+ var parts = numberStr.Split('.');
+ // A number with a dot group separator, e.g., "1.234"
+ if (parts.Length > 1)
{
- // This is not a number with valid thousand separators,
- // so it must be arguments to a function. Return it unmodified.
- return numberStr;
+ // Check if the parts after the first dot have the correct group length (usually 3).
+ for (int i = 1; i < parts.Length; i++)
+ {
+ if (parts[i].Length != 3)
+ {
+ // Malformed grouping, e.g., "1.23". This is likely a decimal number.
+ // Return as is and let Mages handle it.
+ return numberStr;
+ }
+ }
+ // Correct grouping, e.g., "1.234" or "1.234.567". Strip separators.
+ return numberStr.Replace(".", "");
}
}
+
+ // For any other case (e.g. en-US culture where group sep is ',' which was already handled),
+ // return the string as is.
+ return numberStr;
+ }
+ else
+ {
+ // LENIENT MODE: No functions are present, so we can be flexible.
+ string processedStr = numberStr;
+ if (!string.IsNullOrEmpty(groupSep))
+ {
+ processedStr = processedStr.Replace(groupSep, "");
+ }
+ processedStr = processedStr.Replace(decimalSep, ".");
+ return processedStr;
+ }
+ }
+
+ private static bool IsValidGrouping(string[] parts, int[] groupSizes)
+ {
+ if (parts.Length <= 1) return true;
+
+ if (groupSizes is null || groupSizes.Length == 0 || groupSizes[0] == 0)
+ return false; // has groups, but culture defines none.
+
+ var firstPart = parts[0];
+ if (firstPart.StartsWith("-")) firstPart = firstPart.Substring(1);
+ if (firstPart.Length == 0) return false; // e.g. ",123"
+
+ if (firstPart.Length > groupSizes[0]) return false;
+
+ var lastGroupSize = groupSizes.Last();
+ var canRepeatLastGroup = lastGroupSize != 0;
+
+ int groupIndex = 0;
+ for (int i = parts.Length - 1; i > 0; i--)
+ {
+ int expectedSize;
+ if (groupIndex < groupSizes.Length)
+ {
+ expectedSize = groupSizes[groupIndex];
+ }
+ else if(canRepeatLastGroup)
+ {
+ expectedSize = lastGroupSize;
+ }
+ else
+ {
+ return false;
+ }
+
+ if (parts[i].Length != expectedSize) return false;
+
+ groupIndex++;
}
- // If validation passes, we can assume the separators are used correctly for numbers.
- string processedStr = numberStr.Replace(groupSep, "");
- processedStr = processedStr.Replace(decimalSep, ".");
-
- return processedStr;
+ return true;
}
+
private string FormatResult(decimal roundedResult)
{
string decimalSeparator = GetDecimalSeparator();
@@ -250,14 +309,23 @@ namespace Flow.Launcher.Plugin.Calculator
private string GetGroupSeparator(string decimalSeparator)
{
+ var culture = CultureInfo.CurrentCulture;
+ var systemGroupSeparator = culture.NumberFormat.NumberGroupSeparator;
+
if (_settings.DecimalSeparator == DecimalSeparator.UseSystemLocale)
{
- return CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator;
+ return systemGroupSeparator;
}
- // This logic is now independent of the system's group separator
- // to ensure consistent output when a specific separator is chosen.
- return decimalSeparator == Dot ? Comma : Dot;
+ // When a custom decimal separator is used,
+ // use the system's group separator unless it conflicts with the custom decimal separator.
+ if (decimalSeparator == systemGroupSeparator)
+ {
+ // Conflict: use the opposite of the decimal separator as a fallback.
+ return decimalSeparator == Dot ? Comma : Dot;
+ }
+
+ return systemGroupSeparator;
}
private string GetDecimalSeparator()
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs b/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
index d8c2795dc..5c2c19cf4 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
@@ -4,9 +4,6 @@ namespace Flow.Launcher.Plugin.Calculator;
internal static partial class MainRegexHelper
{
- [GeneratedRegex(@"[\(\)\[\]]", RegexOptions.Compiled)]
- public static partial Regex GetRegBrackets();
-
[GeneratedRegex(@"-?[\d\.,]+", RegexOptions.Compiled)]
public static partial Regex GetNumberRegex();
@@ -15,4 +12,7 @@ internal static partial class MainRegexHelper
[GeneratedRegex(@"\bpow(\((?:[^()\[\]]|\((?)|\)(?<-Depth>)|\[(?)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft)]
public static partial Regex GetPowRegex();
+
+ [GeneratedRegex(@"\b(sqrt|pow|factorial|abs|sign|ceil|floor|round|exp|log|log2|log10|min|max|lt|eq|gt|sin|cos|tan|arcsin|arccos|arctan|isnan|isint|isprime|isinfty|rand|randi|type|is|as|length|throw|catch|eval|map|clamp|lerp|regex|shuffle)\s*\(", RegexOptions.Compiled | RegexOptions.IgnoreCase)]
+ public static partial Regex GetFunctionRegex();
}
From edc76faeb4c8860c3f676e5addb7da948a33b3ef Mon Sep 17 00:00:00 2001
From: dcog989
Date: Sun, 14 Sep 2025 21:51:47 +0100
Subject: [PATCH 12/55] Review feedback, case insensitive, consistent
separators
---
.../Flow.Launcher.Plugin.Calculator/Main.cs | 72 ++++++++++++-------
.../MainRegexHelper.cs | 2 +-
2 files changed, 46 insertions(+), 28 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
index a15420a63..545f343f2 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
@@ -52,8 +52,15 @@ namespace Flow.Launcher.Plugin.Calculator
try
{
- bool isFunctionPresent = FunctionRegex.IsMatch(query.Search);
- var expression = NumberRegex.Replace(query.Search, m => NormalizeNumber(m.Value, isFunctionPresent));
+ var search = query.Search;
+ bool isFunctionPresent = FunctionRegex.IsMatch(search);
+
+ // 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));
// WORKAROUND START: The 'pow' function in Mages v3.0.0 is broken.
// https://github.com/FlorianRappl/Mages/issues/132
@@ -185,48 +192,56 @@ namespace Flow.Launcher.Plugin.Calculator
return $"({arg1}^{arg2})";
}
- private static string NormalizeNumber(string numberStr, bool isFunctionPresent)
+ private static string NormalizeNumber(string numberStr, bool isFunctionPresent, string decimalSep, string groupSep)
{
- var culture = CultureInfo.CurrentCulture;
- var groupSep = culture.NumberFormat.NumberGroupSeparator;
- var decimalSep = culture.NumberFormat.NumberDecimalSeparator;
-
if (isFunctionPresent)
{
// STRICT MODE: When functions are present, ',' is ALWAYS an argument separator.
- // It must not be normalized.
if (numberStr.Contains(','))
{
return numberStr;
}
- // The string has no commas. It could have a '.' group separator (e.g. in de-DE)
- // or a '.' decimal separator (e.g. in en-US).
- // Since Mages' decimal separator is '.', we only need to strip the group separator.
- if (groupSep == ".")
+ string processedStr = numberStr;
+
+ // Handle group separator, with special care for ambiguous dot.
+ if (!string.IsNullOrEmpty(groupSep))
{
- var parts = numberStr.Split('.');
- // A number with a dot group separator, e.g., "1.234"
- if (parts.Length > 1)
+ if (groupSep == ".")
{
- // Check if the parts after the first dot have the correct group length (usually 3).
- for (int i = 1; i < parts.Length; i++)
+ var parts = processedStr.Split('.');
+ if (parts.Length > 1)
{
- if (parts[i].Length != 3)
+ bool isGrouped = true;
+ for (var i = 1; i < parts.Length; i++)
{
- // Malformed grouping, e.g., "1.23". This is likely a decimal number.
- // Return as is and let Mages handle it.
- return numberStr;
+ if (parts[i].Length != 3)
+ {
+ isGrouped = false;
+ break;
+ }
}
+
+ if (isGrouped)
+ {
+ processedStr = processedStr.Replace(groupSep, "");
+ }
+ // If not grouped, it's likely a decimal number, so we don't strip dots.
}
- // Correct grouping, e.g., "1.234" or "1.234.567". Strip separators.
- return numberStr.Replace(".", "");
+ }
+ else
+ {
+ processedStr = processedStr.Replace(groupSep, "");
}
}
+
+ // Handle decimal separator.
+ if (decimalSep != ".")
+ {
+ processedStr = processedStr.Replace(decimalSep, ".");
+ }
- // For any other case (e.g. en-US culture where group sep is ',' which was already handled),
- // return the string as is.
- return numberStr;
+ return processedStr;
}
else
{
@@ -236,7 +251,10 @@ namespace Flow.Launcher.Plugin.Calculator
{
processedStr = processedStr.Replace(groupSep, "");
}
- processedStr = processedStr.Replace(decimalSep, ".");
+ if (decimalSep != ".")
+ {
+ processedStr = processedStr.Replace(decimalSep, ".");
+ }
return processedStr;
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs b/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
index 5c2c19cf4..dacc249d9 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
@@ -10,7 +10,7 @@ internal static partial class MainRegexHelper
[GeneratedRegex(@"\B(?=(\d{3})+(?!\d))", RegexOptions.Compiled)]
public static partial Regex GetThousandGroupRegex();
- [GeneratedRegex(@"\bpow(\((?:[^()\[\]]|\((?)|\)(?<-Depth>)|\[(?)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft)]
+ [GeneratedRegex(@"\bpow(\((?:[^()\[\]]|\((?)|\)(?<-Depth>)|\[(?)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft | RegexOptions.IgnoreCase)]
public static partial Regex GetPowRegex();
[GeneratedRegex(@"\b(sqrt|pow|factorial|abs|sign|ceil|floor|round|exp|log|log2|log10|min|max|lt|eq|gt|sin|cos|tan|arcsin|arccos|arctan|isnan|isint|isprime|isinfty|rand|randi|type|is|as|length|throw|catch|eval|map|clamp|lerp|regex|shuffle)\s*\(", RegexOptions.Compiled | RegexOptions.IgnoreCase)]
From 9be8b71f0992d16bc235efe7be8ed1b781a22131 Mon Sep 17 00:00:00 2001
From: dcog989
Date: Sun, 14 Sep 2025 22:28:37 +0100
Subject: [PATCH 13/55] review feedback, CultureInvariant, mild refactor
---
Plugins/Flow.Launcher.Plugin.Calculator/Main.cs | 14 ++------------
.../MainRegexHelper.cs | 6 +++---
2 files changed, 5 insertions(+), 15 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
index 545f343f2..a4c01fc21 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
@@ -212,17 +212,8 @@ namespace Flow.Launcher.Plugin.Calculator
var parts = processedStr.Split('.');
if (parts.Length > 1)
{
- bool isGrouped = true;
- for (var i = 1; i < parts.Length; i++)
- {
- if (parts[i].Length != 3)
- {
- isGrouped = false;
- break;
- }
- }
-
- if (isGrouped)
+ var culture = CultureInfo.CurrentCulture;
+ if (IsValidGrouping(parts, culture.NumberFormat.NumberGroupSizes))
{
processedStr = processedStr.Replace(groupSep, "");
}
@@ -300,7 +291,6 @@ namespace Flow.Launcher.Plugin.Calculator
return true;
}
-
private string FormatResult(decimal roundedResult)
{
string decimalSeparator = GetDecimalSeparator();
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs b/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
index dacc249d9..0746e4556 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
@@ -4,15 +4,15 @@ namespace Flow.Launcher.Plugin.Calculator;
internal static partial class MainRegexHelper
{
- [GeneratedRegex(@"-?[\d\.,]+", RegexOptions.Compiled)]
+ [GeneratedRegex(@"-?[\d\.,'\u00A0\u202F]+", RegexOptions.Compiled | RegexOptions.CultureInvariant)]
public static partial Regex GetNumberRegex();
[GeneratedRegex(@"\B(?=(\d{3})+(?!\d))", RegexOptions.Compiled)]
public static partial Regex GetThousandGroupRegex();
- [GeneratedRegex(@"\bpow(\((?:[^()\[\]]|\((?)|\)(?<-Depth>)|\[(?)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft | RegexOptions.IgnoreCase)]
+ [GeneratedRegex(@"\bpow(\((?:[^()\[\]]|\((?)|\)(?<-Depth>)|\[(?)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
public static partial Regex GetPowRegex();
- [GeneratedRegex(@"\b(sqrt|pow|factorial|abs|sign|ceil|floor|round|exp|log|log2|log10|min|max|lt|eq|gt|sin|cos|tan|arcsin|arccos|arctan|isnan|isint|isprime|isinfty|rand|randi|type|is|as|length|throw|catch|eval|map|clamp|lerp|regex|shuffle)\s*\(", RegexOptions.Compiled | RegexOptions.IgnoreCase)]
+ [GeneratedRegex(@"\b(sqrt|pow|factorial|abs|sign|ceil|floor|round|exp|log|log2|log10|min|max|lt|eq|gt|sin|cos|tan|arcsin|arccos|arctan|isnan|isint|isprime|isinfty|rand|randi|type|is|as|length|throw|catch|eval|map|clamp|lerp|regex|shuffle)\s*\(", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
public static partial Regex GetFunctionRegex();
}
From b07420a193c8c51468d404155a4680fe7253d5aa Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Mon, 15 Sep 2025 12:51:31 +0800
Subject: [PATCH 14/55] Use EmptyResults to improve code quality
---
Plugins/Flow.Launcher.Plugin.Calculator/Main.cs | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
index a4c01fc21..cd54a2155 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
@@ -22,6 +22,7 @@ namespace Flow.Launcher.Plugin.Calculator
private const string Comma = ",";
private const string Dot = ".";
private const string IcoPath = "Images/calculator.png";
+ private static readonly List EmptyResults = [];
internal static PluginInitContext Context { get; set; } = null!;
@@ -47,7 +48,7 @@ namespace Flow.Launcher.Plugin.Calculator
{
if (string.IsNullOrWhiteSpace(query.Search))
{
- return new List();
+ return EmptyResults;
}
try
@@ -138,7 +139,7 @@ namespace Flow.Launcher.Plugin.Calculator
};
}
- return new List();
+ return EmptyResults;
}
private static string PowMatchEvaluator(Match m)
From cea1402dde4bddf71fef55ebbc01007089c8315f Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Mon, 15 Sep 2025 12:53:17 +0800
Subject: [PATCH 15/55] Improve code quality
---
Plugins/Flow.Launcher.Plugin.Calculator/Main.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
index cd54a2155..5b04ae7e8 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
@@ -181,8 +181,8 @@ namespace Flow.Launcher.Plugin.Calculator
return m.Value;
}
- var arg1 = argsContent.Substring(0, splitIndex).Trim();
- var arg2 = argsContent.Substring(splitIndex + 1).Trim();
+ var arg1 = argsContent[..splitIndex].Trim();
+ var arg2 = argsContent[(splitIndex + 1)..].Trim();
// Check for empty arguments which can happen with stray commas, e.g., pow(,5)
if (string.IsNullOrEmpty(arg1) || string.IsNullOrEmpty(arg2))
@@ -259,7 +259,7 @@ namespace Flow.Launcher.Plugin.Calculator
return false; // has groups, but culture defines none.
var firstPart = parts[0];
- if (firstPart.StartsWith("-")) firstPart = firstPart.Substring(1);
+ if (firstPart.StartsWith('-')) firstPart = firstPart[1..];
if (firstPart.Length == 0) return false; // e.g. ",123"
if (firstPart.Length > groupSizes[0]) return false;
From 1906d6854177b0960f0a861582254c31fe836439 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Mon, 15 Sep 2025 12:57:25 +0800
Subject: [PATCH 16/55] Add ShowErrorMessage setting
---
.../Flow.Launcher.Plugin.Calculator/Languages/en.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Calculator/Main.cs | 6 ++++++
Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs | 5 ++++-
.../Views/CalculatorSettings.xaml | 10 ++++++++++
4 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml
index 29a0ed26f..b12972b1b 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml
@@ -15,4 +15,5 @@
Dot (.)
Max. decimal places
Copy failed, please try later
+ Show error message when calculation fails
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
index 5b04ae7e8..429b04979 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
@@ -79,6 +79,7 @@ namespace Flow.Launcher.Plugin.Calculator
if (result == null || string.IsNullOrEmpty(result.ToString()))
{
+ if (!_settings.ShowErrorMessage) return EmptyResults;
return new List
{
new Result
@@ -90,10 +91,14 @@ namespace Flow.Launcher.Plugin.Calculator
}
if (result.ToString() == "NaN")
+ {
result = Localize.flowlauncher_plugin_calculator_not_a_number();
+ }
if (result is Function)
+ {
result = Localize.flowlauncher_plugin_calculator_expression_not_complete();
+ }
if (!string.IsNullOrEmpty(result.ToString()))
{
@@ -129,6 +134,7 @@ namespace Flow.Launcher.Plugin.Calculator
catch (Exception)
{
// Mages engine can throw various exceptions, for simplicity we catch them all and show a generic message.
+ if (!_settings.ShowErrorMessage) return EmptyResults;
return new List
{
new Result
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs
index 8354863b8..0f32b09a9 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs
@@ -4,6 +4,9 @@ namespace Flow.Launcher.Plugin.Calculator
public class Settings
{
public DecimalSeparator DecimalSeparator { get; set; } = DecimalSeparator.UseSystemLocale;
- public int MaxDecimalPlaces { get; set; } = 10;
+
+ public int MaxDecimalPlaces { get; set; } = 10;
+
+ public bool ShowErrorMessage { get; set; } = false;
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml
index 8d240ef39..9e7549b2d 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml
@@ -15,6 +15,7 @@
+
@@ -58,5 +59,14 @@
ItemsSource="{Binding MaxDecimalPlacesRange}"
SelectedItem="{Binding Settings.MaxDecimalPlaces}" />
+
From f9facda5216369e393bb3f126b5f7fdc543fef12 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Mon, 15 Sep 2025 13:02:42 +0800
Subject: [PATCH 17/55] Improve code quality
---
.../Flow.Launcher.Plugin.Calculator/Main.cs | 20 +++++------
.../Settings.cs | 13 ++++---
.../ViewModels/SettingsViewModel.cs | 36 ++++++++-----------
.../Views/CalculatorSettings.xaml.cs | 26 ++++++--------
4 files changed, 41 insertions(+), 54 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
index 429b04979..17b4a85a4 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
@@ -80,14 +80,14 @@ namespace Flow.Launcher.Plugin.Calculator
if (result == null || string.IsNullOrEmpty(result.ToString()))
{
if (!_settings.ShowErrorMessage) return EmptyResults;
- return new List
- {
+ return
+ [
new Result
{
Title = Localize.flowlauncher_plugin_calculator_expression_not_complete(),
IcoPath = IcoPath
}
- };
+ ];
}
if (result.ToString() == "NaN")
@@ -105,8 +105,8 @@ namespace Flow.Launcher.Plugin.Calculator
decimal roundedResult = Math.Round(Convert.ToDecimal(result), _settings.MaxDecimalPlaces, MidpointRounding.AwayFromZero);
string newResult = FormatResult(roundedResult);
- return new List
- {
+ return
+ [
new Result
{
Title = newResult,
@@ -128,21 +128,21 @@ namespace Flow.Launcher.Plugin.Calculator
}
}
}
- };
+ ];
}
}
catch (Exception)
{
// Mages engine can throw various exceptions, for simplicity we catch them all and show a generic message.
if (!_settings.ShowErrorMessage) return EmptyResults;
- return new List
- {
+ return
+ [
new Result
{
Title = Localize.flowlauncher_plugin_calculator_expression_not_complete(),
IcoPath = IcoPath
}
- };
+ ];
}
return EmptyResults;
@@ -153,7 +153,7 @@ namespace Flow.Launcher.Plugin.Calculator
// m.Groups[1].Value will be `(...)` with parens
var contentWithParen = m.Groups[1].Value;
// remove outer parens. `(min(2,3), 4)` becomes `min(2,3), 4`
- var argsContent = contentWithParen.Substring(1, contentWithParen.Length - 2);
+ var argsContent = contentWithParen[1..^1];
var bracketCount = 0;
var splitIndex = -1;
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs
index 0f32b09a9..cac0f3080 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs
@@ -1,12 +1,11 @@
-namespace Flow.Launcher.Plugin.Calculator
+namespace Flow.Launcher.Plugin.Calculator;
+
+public class Settings
{
- public class Settings
- {
- public DecimalSeparator DecimalSeparator { get; set; } = DecimalSeparator.UseSystemLocale;
+ public DecimalSeparator DecimalSeparator { get; set; } = DecimalSeparator.UseSystemLocale;
- public int MaxDecimalPlaces { get; set; } = 10;
+ public int MaxDecimalPlaces { get; set; } = 10;
- public bool ShowErrorMessage { get; set; } = false;
- }
+ public bool ShowErrorMessage { get; set; } = false;
}
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs
index 87ae72fb6..36c277f15 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs
@@ -1,31 +1,25 @@
using System.Collections.Generic;
using System.Linq;
-namespace Flow.Launcher.Plugin.Calculator.ViewModels
+namespace Flow.Launcher.Plugin.Calculator.ViewModels;
+
+public class SettingsViewModel(Settings settings) : BaseModel
{
- public class SettingsViewModel : BaseModel
+ public Settings Settings { get; init; } = settings;
+
+ public static IEnumerable MaxDecimalPlacesRange => Enumerable.Range(1, 20);
+
+ public List AllDecimalSeparator { get; } = DecimalSeparatorLocalized.GetValues();
+
+ public DecimalSeparator SelectedDecimalSeparator
{
- public SettingsViewModel(Settings settings)
+ get => Settings.DecimalSeparator;
+ set
{
- Settings = settings;
- }
-
- public Settings Settings { get; init; }
-
- public static IEnumerable MaxDecimalPlacesRange => Enumerable.Range(1, 20);
-
- public List AllDecimalSeparator { get; } = DecimalSeparatorLocalized.GetValues();
-
- public DecimalSeparator SelectedDecimalSeparator
- {
- get => Settings.DecimalSeparator;
- set
+ if (Settings.DecimalSeparator != value)
{
- if (Settings.DecimalSeparator != value)
- {
- Settings.DecimalSeparator = value;
- OnPropertyChanged();
- }
+ Settings.DecimalSeparator = value;
+ OnPropertyChanged();
}
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml.cs
index 7bc307d11..9e75e7bfb 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml.cs
@@ -1,22 +1,16 @@
using System.Windows.Controls;
using Flow.Launcher.Plugin.Calculator.ViewModels;
-namespace Flow.Launcher.Plugin.Calculator.Views
-{
- ///
- /// Interaction logic for CalculatorSettings.xaml
- ///
- public partial class CalculatorSettings : UserControl
- {
- private readonly SettingsViewModel _viewModel;
- private readonly Settings _settings;
+namespace Flow.Launcher.Plugin.Calculator.Views;
- public CalculatorSettings(Settings settings)
- {
- _viewModel = new SettingsViewModel(settings);
- _settings = _viewModel.Settings;
- DataContext = _viewModel;
- InitializeComponent();
- }
+public partial class CalculatorSettings : UserControl
+{
+ private readonly SettingsViewModel _viewModel;
+
+ public CalculatorSettings(Settings settings)
+ {
+ _viewModel = new SettingsViewModel(settings);
+ DataContext = _viewModel;
+ InitializeComponent();
}
}
From 684fafdfddca9acfdaf6372a0a4546bc59e705e0 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Mon, 15 Sep 2025 15:18:42 +0800
Subject: [PATCH 18/55] Improve code quality
---
.../ViewModels/SettingsViewModel.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs
index 36c277f15..79236bdf8 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs
@@ -5,7 +5,7 @@ namespace Flow.Launcher.Plugin.Calculator.ViewModels;
public class SettingsViewModel(Settings settings) : BaseModel
{
- public Settings Settings { get; init; } = settings;
+ public Settings Settings { get; } = settings;
public static IEnumerable MaxDecimalPlacesRange => Enumerable.Range(1, 20);
From 9321a7df140ce303fbd21f44eb7c09bdfea31225 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Mon, 15 Sep 2025 15:40:49 +0800
Subject: [PATCH 19/55] Fix program lock issue
---
Plugins/Flow.Launcher.Plugin.Program/Main.cs | 62 ++++++++++++-------
.../Views/Commands/ProgramSettingDisplay.cs | 52 +++++++++++-----
2 files changed, 79 insertions(+), 35 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs
index 7c30c0c96..0eb1fd403 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs
@@ -31,7 +31,7 @@ namespace Flow.Launcher.Plugin.Program
internal static PluginInitContext Context { get; private set; }
- private static readonly List emptyResults = new();
+ private static readonly List emptyResults = [];
private static readonly MemoryCacheOptions cacheOptions = new() { SizeLimit = 1560 };
private static MemoryCache cache = new(cacheOptions);
@@ -84,7 +84,6 @@ namespace Flow.Launcher.Plugin.Program
{
await _win32sLock.WaitAsync(token);
await _uwpsLock.WaitAsync(token);
-
try
{
// Collect all UWP Windows app directories
@@ -117,7 +116,7 @@ namespace Flow.Launcher.Plugin.Program
}
}, token);
- resultList = resultList.Any() ? resultList : emptyResults;
+ resultList = resultList.Count != 0 ? resultList : emptyResults;
entry.SetSize(resultList.Count);
entry.SetSlidingExpiration(TimeSpan.FromHours(8));
@@ -250,14 +249,26 @@ namespace Flow.Launcher.Plugin.Program
}
await _win32sLock.WaitAsync();
- _win32s = await context.API.LoadCacheBinaryStorageAsync(Win32CacheName, pluginCacheDirectory, new List());
- _win32sCount = _win32s.Count;
- _win32sLock.Release();
+ try
+ {
+ _win32s = await context.API.LoadCacheBinaryStorageAsync(Win32CacheName, pluginCacheDirectory, new List());
+ _win32sCount = _win32s.Count;
+ }
+ finally
+ {
+ _win32sLock.Release();
+ }
await _uwpsLock.WaitAsync();
- _uwps = await context.API.LoadCacheBinaryStorageAsync(UwpCacheName, pluginCacheDirectory, new List());
- _uwpsCount = _uwps.Count;
- _uwpsLock.Release();
+ try
+ {
+ _uwps = await context.API.LoadCacheBinaryStorageAsync(UwpCacheName, pluginCacheDirectory, new List());
+ _uwpsCount = _uwps.Count;
+ }
+ finally
+ {
+ _uwpsLock.Release();
+ }
});
Context.API.LogInfo(ClassName, $"Number of preload win32 programs <{_win32sCount}>");
Context.API.LogInfo(ClassName, $"Number of preload uwps <{_uwpsCount}>");
@@ -408,38 +419,47 @@ namespace Flow.Launcher.Plugin.Program
return;
await _uwpsLock.WaitAsync();
- if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
+ var reindexUwps = true;
+ try
{
+ reindexUwps = _uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
var program = _uwps.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
program.Enabled = false;
_settings.DisabledProgramSources.Add(new ProgramSource(program));
+ }
+ finally
+ {
_uwpsLock.Release();
+ }
- // Reindex UWP programs
+ // Reindex UWP programs
+ if (reindexUwps)
+ {
_ = Task.Run(IndexUwpProgramsAsync);
return;
}
- else
- {
- _uwpsLock.Release();
- }
await _win32sLock.WaitAsync();
- if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
+ var reindexWin32s = true;
+ try
{
+ reindexWin32s = _win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
var program = _win32s.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
program.Enabled = false;
_settings.DisabledProgramSources.Add(new ProgramSource(program));
_win32sLock.Release();
-
- // Reindex Win32 programs
- _ = Task.Run(IndexWin32ProgramsAsync);
- return;
}
- else
+ finally
{
_win32sLock.Release();
}
+
+ // Reindex Win32 programs
+ if (reindexWin32s)
+ {
+ _ = Task.Run(IndexWin32ProgramsAsync);
+ return;
+ }
}
public static void StartProcess(Func runProcess, ProcessStartInfo info)
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs b/Plugins/Flow.Launcher.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs
index b89a2a6ba..2a6a3e987 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs
@@ -19,18 +19,30 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands
internal static async Task DisplayAllProgramsAsync()
{
await Main._win32sLock.WaitAsync();
- var win32 = Main._win32s
+ try
+ {
+ var win32 = Main._win32s
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
.Select(x => new ProgramSource(x));
- ProgramSetting.ProgramSettingDisplayList.AddRange(win32);
- Main._win32sLock.Release();
+ ProgramSetting.ProgramSettingDisplayList.AddRange(win32);
+ }
+ finally
+ {
+ Main._win32sLock.Release();
+ }
await Main._uwpsLock.WaitAsync();
- var uwp = Main._uwps
+ try
+ {
+ var uwp = Main._uwps
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
.Select(x => new ProgramSource(x));
- ProgramSetting.ProgramSettingDisplayList.AddRange(uwp);
- Main._uwpsLock.Release();
+ ProgramSetting.ProgramSettingDisplayList.AddRange(uwp);
+ }
+ finally
+ {
+ Main._uwpsLock.Release();
+ }
}
internal static async Task SetProgramSourcesStatusAsync(List selectedProgramSourcesToDisable, bool status)
@@ -44,24 +56,36 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands
}
await Main._win32sLock.WaitAsync();
- foreach (var program in Main._win32s)
+ try
{
- if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
+ foreach (var program in Main._win32s)
{
- program.Enabled = status;
+ if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
+ {
+ program.Enabled = status;
+ }
}
}
- Main._win32sLock.Release();
+ finally
+ {
+ Main._win32sLock.Release();
+ }
await Main._uwpsLock.WaitAsync();
- foreach (var program in Main._uwps)
+ try
{
- if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
+ foreach (var program in Main._uwps)
{
- program.Enabled = status;
+ if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
+ {
+ program.Enabled = status;
+ }
}
}
- Main._uwpsLock.Release();
+ finally
+ {
+ Main._uwpsLock.Release();
+ }
}
internal static void StoreDisabledInSettings()
From ef69e329fc1d28b3e1352f3a04f94bb33da8afc6 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Mon, 15 Sep 2025 15:50:22 +0800
Subject: [PATCH 20/55] Fix release
---
Plugins/Flow.Launcher.Plugin.Program/Main.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs
index 0eb1fd403..0258a10d2 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs
@@ -447,7 +447,6 @@ namespace Flow.Launcher.Plugin.Program
var program = _win32s.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
program.Enabled = false;
_settings.DisabledProgramSources.Add(new ProgramSource(program));
- _win32sLock.Release();
}
finally
{
From e204daa8443aad283feba9dabeed576e16d13c7e Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Mon, 15 Sep 2025 21:33:21 +0800
Subject: [PATCH 21/55] Catch exception when creating setting panel
---
Flow.Launcher/ViewModel/PluginViewModel.cs | 36 +++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs
index d889bdd52..36c509902 100644
--- a/Flow.Launcher/ViewModel/PluginViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginViewModel.cs
@@ -131,11 +131,45 @@ namespace Flow.Launcher.ViewModel
=> IsExpanded
? _settingControl
??= HasSettingControl
- ? ((ISettingProvider)PluginPair.Plugin).CreateSettingPanel()
+ ? TryCreateSettingPanel(PluginPair)
: null
: null;
private ImageSource _image = ImageLoader.MissingImage;
+ private static readonly Thickness SettingPanelMargin = (Thickness)Application.Current.FindResource("SettingPanelMargin");
+ private static readonly Thickness SettingPanelItemTopBottomMargin = (Thickness)Application.Current.FindResource("SettingPanelItemTopBottomMargin");
+ private static Control TryCreateSettingPanel(PluginPair pair)
+ {
+ try
+ {
+ // We can safely cast here as we already check this in HasSettingControl
+ return ((ISettingProvider)pair.Plugin).CreateSettingPanel();
+ }
+ catch (System.Exception e)
+ {
+ var errorMsg = $"Error creating setting panel for plugin {pair.Metadata}\n{e.Message}";
+ var grid = new Grid()
+ {
+ Margin = SettingPanelMargin
+ };
+ var textBox = new TextBox
+ {
+ Text = errorMsg,
+ IsReadOnly = true,
+ HorizontalAlignment = HorizontalAlignment.Stretch,
+ VerticalAlignment = VerticalAlignment.Top,
+ TextWrapping = TextWrapping.Wrap,
+ Margin = SettingPanelItemTopBottomMargin
+ };
+ textBox.SetResourceReference(TextBlock.ForegroundProperty, "Color04B");
+ grid.Children.Add(textBox);
+ return new UserControl
+ {
+ Content = grid
+ };
+ }
+ }
+
public Visibility ActionKeywordsVisibility => PluginPair.Metadata.HideActionKeywordPanel ?
Visibility.Collapsed : Visibility.Visible;
public string InitializeTime => PluginPair.Metadata.InitTime + "ms";
From 0355993b009b3e29b399a3fdbc02fba481337303 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Mon, 15 Sep 2025 21:38:53 +0800
Subject: [PATCH 22/55] Use translation
---
Flow.Launcher/Languages/en.xaml | 1 +
Flow.Launcher/ViewModel/PluginViewModel.cs | 8 +++++---
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index f7fd0c8e5..e97bd6cf5 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -219,6 +219,7 @@
Fail to uninstall {0}
Unable to find plugin.json from the extracted zip file, or this path {0} does not exist
A plugin with the same ID and version already exists, or the version is greater than this downloaded plugin
+ Error creating setting panel for plugin {0}:{1}{2}
Plugin Store
diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs
index 36c509902..4de1ae661 100644
--- a/Flow.Launcher/ViewModel/PluginViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginViewModel.cs
@@ -1,4 +1,5 @@
-using System.Threading.Tasks;
+using System;
+using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
@@ -145,9 +146,10 @@ namespace Flow.Launcher.ViewModel
// We can safely cast here as we already check this in HasSettingControl
return ((ISettingProvider)pair.Plugin).CreateSettingPanel();
}
- catch (System.Exception e)
+ catch (Exception e)
{
- var errorMsg = $"Error creating setting panel for plugin {pair.Metadata}\n{e.Message}";
+ var errorMsg = string.Format(App.API.GetTranslation("errorCreatingSettingPanel"),
+ pair.Metadata.Name, Environment.NewLine, e.Message);
var grid = new Grid()
{
Margin = SettingPanelMargin
From 18c8a04cbcc5fe062730abef62af7c0760682eae Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Mon, 15 Sep 2025 21:46:03 +0800
Subject: [PATCH 23/55] Log exception
---
Flow.Launcher/ViewModel/PluginViewModel.cs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs
index 4de1ae661..59bb53a4a 100644
--- a/Flow.Launcher/ViewModel/PluginViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginViewModel.cs
@@ -15,6 +15,8 @@ namespace Flow.Launcher.ViewModel
{
public partial class PluginViewModel : BaseModel
{
+ private static readonly string ClassName = nameof(PluginViewModel);
+
private static readonly Settings Settings = Ioc.Default.GetRequiredService();
private readonly PluginPair _pluginPair;
@@ -148,6 +150,10 @@ namespace Flow.Launcher.ViewModel
}
catch (Exception e)
{
+ // Log exception
+ App.API.LogException(ClassName, $"Failed to create setting panel for {pair.Metadata.Name}", e);
+
+ // Show error message in UI
var errorMsg = string.Format(App.API.GetTranslation("errorCreatingSettingPanel"),
pair.Metadata.Name, Environment.NewLine, e.Message);
var grid = new Grid()
From 6841ad5410fd44c3402362a56723f4289cf1b7a0 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Tue, 16 Sep 2025 16:08:17 +0800
Subject: [PATCH 24/55] Add calculator unit testing
---
Flow.Launcher.Test/Flow.Launcher.Test.csproj | 1 +
Flow.Launcher.Test/Plugins/CalculatorTest.cs | 86 +++++++++++++++++++
.../Flow.Launcher.Plugin.Calculator/Main.cs | 3 +-
3 files changed, 89 insertions(+), 1 deletion(-)
create mode 100644 Flow.Launcher.Test/Plugins/CalculatorTest.cs
diff --git a/Flow.Launcher.Test/Flow.Launcher.Test.csproj b/Flow.Launcher.Test/Flow.Launcher.Test.csproj
index 1164e5ebe..11ccff05b 100644
--- a/Flow.Launcher.Test/Flow.Launcher.Test.csproj
+++ b/Flow.Launcher.Test/Flow.Launcher.Test.csproj
@@ -39,6 +39,7 @@
+
diff --git a/Flow.Launcher.Test/Plugins/CalculatorTest.cs b/Flow.Launcher.Test/Plugins/CalculatorTest.cs
new file mode 100644
index 000000000..3f403b24e
--- /dev/null
+++ b/Flow.Launcher.Test/Plugins/CalculatorTest.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using Flow.Launcher.Plugin.Calculator;
+using Mages.Core;
+using NUnit.Framework;
+using NUnit.Framework.Legacy;
+
+namespace Flow.Launcher.Test.Plugins
+{
+ [TestFixture]
+ public class CalculatorPluginTest
+ {
+ private readonly Main _plugin;
+
+ public CalculatorPluginTest()
+ {
+ _plugin = new Main();
+
+ var settingField = typeof(Main).GetField("_settings", BindingFlags.NonPublic | BindingFlags.Instance);
+ if (settingField == null)
+ Assert.Fail("Could not find field '_settings' on Flow.Launcher.Plugin.Calculator.Main");
+ settingField.SetValue(_plugin, new Settings
+ {
+ ShowErrorMessage = false // Make sure we return the empty results when error occurs
+ });
+
+ var engineField = typeof(Main).GetField("MagesEngine", BindingFlags.NonPublic | BindingFlags.Static);
+ if (engineField == null)
+ Assert.Fail("Could not find static field 'MagesEngine' on Flow.Launcher.Plugin.Calculator.Main");
+ engineField.SetValue(null, new Engine(new Configuration
+ {
+ Scope = new Dictionary
+ {
+ { "e", Math.E }, // e is not contained in the default mages engine
+ }
+ }));
+ }
+
+ // Basic operations
+ [TestCase(@"1+1", "2")]
+ [TestCase(@"2-1", "1")]
+ [TestCase(@"2*2", "4")]
+ [TestCase(@"4/2", "2")]
+ [TestCase(@"2^3", "8")]
+ // Decimal places
+ [TestCase(@"10/3", "3.3333333333")]
+ // Parentheses
+ [TestCase(@"(1+2)*3", "9")]
+ [TestCase(@"2^(1+2)", "8")]
+ // Functions
+ [TestCase(@"pow(2,3)", "8")]
+ [TestCase(@"min(1,-1,-2)", "-2")]
+ [TestCase(@"max(1,-1,-2)", "1")]
+ [TestCase(@"sqrt(16)", "4")]
+ [TestCase(@"sin(pi)", "0")]
+ [TestCase(@"cos(0)", "1")]
+ [TestCase(@"tan(0)", "0")]
+ [TestCase(@"log(100)", "2")]
+ [TestCase(@"ln(e)", "1")]
+ [TestCase(@"abs(-5)", "5")]
+ // Constants
+ [TestCase(@"pi", "3.1415926536")]
+ // Complex expressions
+ [TestCase(@"(2+3)*sqrt(16)-log(100)/ln(e)", "19")]
+ [TestCase(@"sin(pi/2)+cos(0)+tan(0)", "2")]
+ // Error handling (should return empty result)
+ [TestCase(@"10/0", "")]
+ [TestCase(@"sqrt(-1)", "")]
+ [TestCase(@"log(0)", "")]
+ [TestCase(@"invalid_expression", "")]
+ public void CalculatorTest(string expression, string result)
+ {
+ ClassicAssert.AreEqual(GetCalculationResult(expression), result);
+ }
+
+ private string GetCalculationResult(string expression)
+ {
+ var results = _plugin.Query(new Plugin.Query()
+ {
+ Search = expression
+ });
+ return results.Count > 0 ? results[0].Title : string.Empty;
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
index 17b4a85a4..42cbafb43 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
@@ -112,7 +112,8 @@ namespace Flow.Launcher.Plugin.Calculator
Title = newResult,
IcoPath = IcoPath,
Score = 300,
- SubTitle = Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard(),
+ // Check context nullability for unit testing
+ SubTitle = Context == null ? string.Empty : Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard(),
CopyText = newResult,
Action = c =>
{
From e10b9254ed6211ba8a8fdcc1047d9fe25724e7c4 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Tue, 16 Sep 2025 16:31:06 +0800
Subject: [PATCH 25/55] Add workaround for log & ln function
---
Flow.Launcher.Test/Plugins/CalculatorTest.cs | 4 +-
.../Flow.Launcher.Plugin.Calculator/Main.cs | 63 +++++++++++++++++--
.../MainRegexHelper.cs | 6 ++
3 files changed, 67 insertions(+), 6 deletions(-)
diff --git a/Flow.Launcher.Test/Plugins/CalculatorTest.cs b/Flow.Launcher.Test/Plugins/CalculatorTest.cs
index 3f403b24e..146552323 100644
--- a/Flow.Launcher.Test/Plugins/CalculatorTest.cs
+++ b/Flow.Launcher.Test/Plugins/CalculatorTest.cs
@@ -53,10 +53,12 @@ namespace Flow.Launcher.Test.Plugins
[TestCase(@"min(1,-1,-2)", "-2")]
[TestCase(@"max(1,-1,-2)", "1")]
[TestCase(@"sqrt(16)", "4")]
- [TestCase(@"sin(pi)", "0")]
+ [TestCase(@"sin(pi)", "0.0000000000")]
[TestCase(@"cos(0)", "1")]
[TestCase(@"tan(0)", "0")]
+ [TestCase(@"log10(100)", "2")]
[TestCase(@"log(100)", "2")]
+ [TestCase(@"log2(8)", "3")]
[TestCase(@"ln(e)", "1")]
[TestCase(@"abs(-5)", "5")]
// Constants
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
index 42cbafb43..9d5e4700f 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
@@ -16,6 +16,8 @@ namespace Flow.Launcher.Plugin.Calculator
private static readonly Regex ThousandGroupRegex = MainRegexHelper.GetThousandGroupRegex();
private static readonly Regex NumberRegex = MainRegexHelper.GetNumberRegex();
private static readonly Regex PowRegex = MainRegexHelper.GetPowRegex();
+ private static readonly Regex LogRegex = MainRegexHelper.GetLogRegex();
+ private static readonly Regex LnRegex = MainRegexHelper.GetLnRegex();
private static readonly Regex FunctionRegex = MainRegexHelper.GetFunctionRegex();
private static Engine MagesEngine;
@@ -67,12 +69,36 @@ namespace Flow.Launcher.Plugin.Calculator
// https://github.com/FlorianRappl/Mages/issues/132
// We bypass it by rewriting any pow(x,y) expression to the equivalent (x^y) expression
// before the engine sees it. This loop handles nested calls.
- string previous;
- do
{
- previous = expression;
- expression = PowRegex.Replace(previous, PowMatchEvaluator);
- } while (previous != expression);
+ string previous;
+ do
+ {
+ previous = expression;
+ expression = PowRegex.Replace(previous, PowMatchEvaluator);
+ } while (previous != expression);
+ }
+ // WORKAROUND END
+
+ // WORKAROUND START: The 'log' & 'ln' function in Mages v3.0.0 are broken.
+ // https://github.com/FlorianRappl/Mages/issues/137
+ // We bypass it by rewriting any log & ln expression to the equivalent (log10 & log) expression
+ // before the engine sees it. This loop handles nested calls.
+ {
+ string previous;
+ do
+ {
+ previous = expression;
+ expression = LogRegex.Replace(previous, LogMatchEvaluator);
+ } while (previous != expression);
+ }
+ {
+ string previous;
+ do
+ {
+ previous = expression;
+ expression = LnRegex.Replace(previous, LnMatchEvaluator);
+ } while (previous != expression);
+ }
// WORKAROUND END
var result = MagesEngine.Interpret(expression);
@@ -200,6 +226,33 @@ namespace Flow.Launcher.Plugin.Calculator
return $"({arg1}^{arg2})";
}
+ private static string LogMatchEvaluator(Match m)
+ {
+ // m.Groups[1].Value will be `(...)` with parens
+ var contentWithParen = m.Groups[1].Value;
+ var argsContent = contentWithParen[1..^1];
+
+ // log is unary — if malformed, return original to let Mages handle it
+ var arg = argsContent.Trim();
+ if (string.IsNullOrEmpty(arg)) return m.Value;
+
+ // log(x) -> log10(x) (natural log)
+ return $"(log10({arg}))";
+ }
+
+ private static string LnMatchEvaluator(Match m)
+ {
+ // m.Groups[1].Value will be `(...)` with parens
+ var contentWithParen = m.Groups[1].Value;
+ var argsContent = contentWithParen[1..^1];
+
+ // ln is unary — if malformed, return original to let Mages handle it
+ var arg = argsContent.Trim();
+ if (string.IsNullOrEmpty(arg)) return m.Value;
+
+ // ln(x) -> log(x) (natural log)
+ return $"(log({arg}))";
+ }
private static string NormalizeNumber(string numberStr, bool isFunctionPresent, string decimalSep, string groupSep)
{
if (isFunctionPresent)
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs b/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
index 0746e4556..a8b582ccc 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs
@@ -13,6 +13,12 @@ internal static partial class MainRegexHelper
[GeneratedRegex(@"\bpow(\((?:[^()\[\]]|\((?)|\)(?<-Depth>)|\[(?)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
public static partial Regex GetPowRegex();
+ [GeneratedRegex(@"\blog(\((?:[^()\[\]]|\((?)|\)(?<-Depth>)|\[(?)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
+ public static partial Regex GetLogRegex();
+
+ [GeneratedRegex(@"\bln(\((?:[^()\[\]]|\((?)|\)(?<-Depth>)|\[(?)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
+ public static partial Regex GetLnRegex();
+
[GeneratedRegex(@"\b(sqrt|pow|factorial|abs|sign|ceil|floor|round|exp|log|log2|log10|min|max|lt|eq|gt|sin|cos|tan|arcsin|arccos|arctan|isnan|isint|isprime|isinfty|rand|randi|type|is|as|length|throw|catch|eval|map|clamp|lerp|regex|shuffle)\s*\(", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
public static partial Regex GetFunctionRegex();
}
From 552b6547db09301c7ab57c0a1f000eb5ee5daf44 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Tue, 16 Sep 2025 16:40:36 +0800
Subject: [PATCH 26/55] Fix unit test result issue
---
Flow.Launcher.Test/Plugins/CalculatorTest.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Test/Plugins/CalculatorTest.cs b/Flow.Launcher.Test/Plugins/CalculatorTest.cs
index 146552323..c58800002 100644
--- a/Flow.Launcher.Test/Plugins/CalculatorTest.cs
+++ b/Flow.Launcher.Test/Plugins/CalculatorTest.cs
@@ -64,7 +64,7 @@ namespace Flow.Launcher.Test.Plugins
// Constants
[TestCase(@"pi", "3.1415926536")]
// Complex expressions
- [TestCase(@"(2+3)*sqrt(16)-log(100)/ln(e)", "19")]
+ [TestCase(@"(2+3)*sqrt(16)-log(100)/ln(e)", "18")]
[TestCase(@"sin(pi/2)+cos(0)+tan(0)", "2")]
// Error handling (should return empty result)
[TestCase(@"10/0", "")]
From 8321e400c1a6608c541a3e617967ece77df0860b Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Tue, 16 Sep 2025 21:04:56 +0800
Subject: [PATCH 27/55] Fix test setting & Add instances to private fields
---
Flow.Launcher.Test/Plugins/CalculatorTest.cs | 26 +++++++++++---------
1 file changed, 15 insertions(+), 11 deletions(-)
diff --git a/Flow.Launcher.Test/Plugins/CalculatorTest.cs b/Flow.Launcher.Test/Plugins/CalculatorTest.cs
index c58800002..b075813db 100644
--- a/Flow.Launcher.Test/Plugins/CalculatorTest.cs
+++ b/Flow.Launcher.Test/Plugins/CalculatorTest.cs
@@ -12,6 +12,19 @@ namespace Flow.Launcher.Test.Plugins
public class CalculatorPluginTest
{
private readonly Main _plugin;
+ private readonly Settings _settings = new()
+ {
+ DecimalSeparator = DecimalSeparator.UseSystemLocale,
+ MaxDecimalPlaces = 10,
+ ShowErrorMessage = false // Make sure we return the empty results when error occurs
+ };
+ private readonly Engine _engine = new(new Configuration
+ {
+ Scope = new Dictionary
+ {
+ { "e", Math.E }, // e is not contained in the default mages engine
+ }
+ });
public CalculatorPluginTest()
{
@@ -20,21 +33,12 @@ namespace Flow.Launcher.Test.Plugins
var settingField = typeof(Main).GetField("_settings", BindingFlags.NonPublic | BindingFlags.Instance);
if (settingField == null)
Assert.Fail("Could not find field '_settings' on Flow.Launcher.Plugin.Calculator.Main");
- settingField.SetValue(_plugin, new Settings
- {
- ShowErrorMessage = false // Make sure we return the empty results when error occurs
- });
+ settingField.SetValue(_plugin, _settings);
var engineField = typeof(Main).GetField("MagesEngine", BindingFlags.NonPublic | BindingFlags.Static);
if (engineField == null)
Assert.Fail("Could not find static field 'MagesEngine' on Flow.Launcher.Plugin.Calculator.Main");
- engineField.SetValue(null, new Engine(new Configuration
- {
- Scope = new Dictionary
- {
- { "e", Math.E }, // e is not contained in the default mages engine
- }
- }));
+ engineField.SetValue(null, _engine);
}
// Basic operations
From 23d0b73e20bb0963d5a11a6c910edc04035b52af Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 17 Sep 2025 17:30:03 +0800
Subject: [PATCH 28/55] Fix AllEverythingSortOptions issue
---
.../ViewModels/SettingsViewModel.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs
index 7292697ce..ae2235c5c 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs
@@ -577,8 +577,8 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
}
}
- public int MaxResultLowerLimit => 1;
- public int MaxResultUpperLimit => 100000;
+ public int MaxResultLowerLimit { get; } = 1;
+ public int MaxResultUpperLimit { get; } = 100000;
public int MaxResult
{
@@ -592,7 +592,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
#region Everything FastSortWarning
- public List AllEverythingSortOptions = EverythingSortOptionLocalized.GetValues();
+ public List AllEverythingSortOptions { get; } = EverythingSortOptionLocalized.GetValues();
public EverythingSortOption SelectedEverythingSortOption
{
From 80c283a370fa7a9a2cb74778b97e55b2829069a0 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 17 Sep 2025 19:15:46 +0800
Subject: [PATCH 29/55] Improve code quality
---
Flow.Launcher/Helper/WallpaperPathRetrieval.cs | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
index 93b9a8aaa..c16e1170d 100644
--- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
+++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Threading;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
@@ -16,7 +17,7 @@ public static class WallpaperPathRetrieval
private const int MaxCacheSize = 3;
private static readonly Dictionary<(string, DateTime), ImageBrush> WallpaperCache = new();
- private static readonly object CacheLock = new();
+ private static readonly Lock CacheLock = new();
public static Brush GetWallpaperBrush()
{
@@ -56,7 +57,7 @@ public static class WallpaperPathRetrieval
if (originalWidth == 0 || originalHeight == 0)
{
- App.API.LogInfo(ClassName, $"Failed to load bitmap: Width={originalWidth}, Height={originalHeight}");
+ App.API.LogError(ClassName, $"Failed to load bitmap: Width={originalWidth}, Height={originalHeight}");
return new SolidColorBrush(Colors.Transparent);
}
@@ -104,13 +105,13 @@ public static class WallpaperPathRetrieval
private static Color GetWallpaperColor()
{
- RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", false);
+ using var key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", false);
var result = key?.GetValue("Background", null);
if (result is string strResult)
{
try
{
- var parts = strResult.Trim().Split(new[] { ' ' }, 3).Select(byte.Parse).ToList();
+ var parts = strResult.Trim().Split([' '], 3).Select(byte.Parse).ToList();
return Color.FromRgb(parts[0], parts[1], parts[2]);
}
catch (Exception ex)
From 5c16b86edfde80d6b07c08da6a59fddc3ee61880 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 17 Sep 2025 19:16:09 +0800
Subject: [PATCH 30/55] Fix file lock during file stream
---
Flow.Launcher/Helper/WallpaperPathRetrieval.cs | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
index c16e1170d..be35b8f69 100644
--- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
+++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
@@ -48,12 +48,15 @@ public static class WallpaperPathRetrieval
return cachedWallpaper;
}
}
-
- using var fileStream = File.OpenRead(wallpaperPath);
- var decoder = BitmapDecoder.Create(fileStream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.None);
- var frame = decoder.Frames[0];
- var originalWidth = frame.PixelWidth;
- var originalHeight = frame.PixelHeight;
+
+ int originalWidth, originalHeight;
+ using (var fileStream = File.OpenRead(wallpaperPath))
+ {
+ var decoder = BitmapDecoder.Create(fileStream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.None);
+ var frame = decoder.Frames[0];
+ originalWidth = frame.PixelWidth;
+ originalHeight = frame.PixelHeight;
+ }
if (originalWidth == 0 || originalHeight == 0)
{
From 83f02f5c9144cf49c387a0e408fa92c43e0f7c9f Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 17 Sep 2025 19:16:58 +0800
Subject: [PATCH 31/55] Use log error & Improve returned color
---
Flow.Launcher/Helper/WallpaperPathRetrieval.cs | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
index be35b8f69..aa41c9998 100644
--- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
+++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
@@ -32,7 +32,7 @@ public static class WallpaperPathRetrieval
var wallpaperPath = Win32Helper.GetWallpaperPath();
if (string.IsNullOrEmpty(wallpaperPath) || !File.Exists(wallpaperPath))
{
- App.API.LogInfo(ClassName, $"Wallpaper path is invalid: {wallpaperPath}");
+ App.API.LogError(ClassName, $"Wallpaper path is invalid: {wallpaperPath}");
var wallpaperColor = GetWallpaperColor();
return new SolidColorBrush(wallpaperColor);
}
@@ -61,7 +61,8 @@ public static class WallpaperPathRetrieval
if (originalWidth == 0 || originalHeight == 0)
{
App.API.LogError(ClassName, $"Failed to load bitmap: Width={originalWidth}, Height={originalHeight}");
- return new SolidColorBrush(Colors.Transparent);
+ var wallpaperColor = GetWallpaperColor();
+ return new SolidColorBrush(wallpaperColor);
}
// Calculate the scaling factor to fit the image within 800x600 while preserving aspect ratio
From 60ec9b5c497c7a30e58dc29b0d5d602fb50a8adc Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 17 Sep 2025 19:24:32 +0800
Subject: [PATCH 32/55] Use OnLoaded to ensure the wallpaper file is not locked
---
Flow.Launcher/Helper/WallpaperPathRetrieval.cs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
index aa41c9998..57c1bbe8d 100644
--- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
+++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
@@ -75,7 +75,9 @@ public static class WallpaperPathRetrieval
// Set DecodePixelWidth and DecodePixelHeight to resize the image while preserving aspect ratio
var bitmap = new BitmapImage();
bitmap.BeginInit();
+ bitmap.CacheOption = BitmapCacheOption.OnLoad; // Use OnLoaded to ensure the wallpaper file is not locked
bitmap.UriSource = new Uri(wallpaperPath);
+ bitmap.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
bitmap.DecodePixelWidth = decodedPixelWidth;
bitmap.DecodePixelHeight = decodedPixelHeight;
bitmap.EndInit();
From 354b04bea449091efa5bd61e182d133552367391 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Wed, 17 Sep 2025 19:29:36 +0800
Subject: [PATCH 33/55] Add code comments
---
Flow.Launcher/Helper/WallpaperPathRetrieval.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
index 57c1bbe8d..67618d760 100644
--- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
+++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
@@ -50,6 +50,7 @@ public static class WallpaperPathRetrieval
}
int originalWidth, originalHeight;
+ // Use `using ()` instead of `using var` sentence here to ensure the wallpaper file is not locked
using (var fileStream = File.OpenRead(wallpaperPath))
{
var decoder = BitmapDecoder.Create(fileStream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.None);
From e50a2772f82392234eb1389e3fad8b01dfae3e3d Mon Sep 17 00:00:00 2001
From: Jack Ye <1160210343@qq.com>
Date: Wed, 17 Sep 2025 19:33:46 +0800
Subject: [PATCH 34/55] Update code comments
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
Flow.Launcher/Helper/WallpaperPathRetrieval.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
index 67618d760..fd04b3e88 100644
--- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
+++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
@@ -76,7 +76,7 @@ public static class WallpaperPathRetrieval
// Set DecodePixelWidth and DecodePixelHeight to resize the image while preserving aspect ratio
var bitmap = new BitmapImage();
bitmap.BeginInit();
- bitmap.CacheOption = BitmapCacheOption.OnLoad; // Use OnLoaded to ensure the wallpaper file is not locked
+ bitmap.CacheOption = BitmapCacheOption.OnLoad; // Use OnLoad to ensure the wallpaper file is not locked
bitmap.UriSource = new Uri(wallpaperPath);
bitmap.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
bitmap.DecodePixelWidth = decodedPixelWidth;
From 1fea8edb747470b076376b76473d52f9c8253f51 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 11:46:09 +0800
Subject: [PATCH 35/55] Add translation for default items & new profile item
for explorer & browser window
---
.../UserSettings/CustomBrowserViewModel.cs | 17 +++++++++++++----
.../UserSettings/CustomExplorerViewModel.cs | 15 ++++++++++++++-
Flow.Launcher/Languages/en.xaml | 3 +++
Flow.Launcher/SelectBrowserWindow.xaml | 2 +-
Flow.Launcher/SelectBrowserWindow.xaml.cs | 12 +++++++++++-
Flow.Launcher/SelectFileManagerWindow.xaml | 2 +-
.../ViewModels/SettingsPaneGeneralViewModel.cs | 4 +++-
.../SettingPages/Views/SettingsPaneGeneral.xaml | 4 ++--
.../ViewModel/SelectBrowserViewModel.cs | 12 +-----------
9 files changed, 49 insertions(+), 22 deletions(-)
diff --git a/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs b/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs
index 24584115d..9c795f952 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs
@@ -1,11 +1,18 @@
+using System.Text.Json.Serialization;
+using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
-using System.Text.Json.Serialization;
namespace Flow.Launcher.Infrastructure.UserSettings
{
public class CustomBrowserViewModel : BaseModel
{
+ // We should not initialize API in static constructor because it will create another API instance
+ private static IPublicAPI api = null;
+ private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService();
+
public string Name { get; set; }
+ [JsonIgnore]
+ public string DisplayName => Name == "Default" ? API.GetTranslation("defaultBrowser_default") : Name;
public string Path { get; set; }
public string PrivateArg { get; set; }
public bool EnablePrivate { get; set; }
@@ -26,8 +33,10 @@ namespace Flow.Launcher.Infrastructure.UserSettings
Editable = Editable
};
}
+
+ public void OnDisplayNameChanged()
+ {
+ OnPropertyChanged(nameof(DisplayName));
+ }
}
}
-
-
-
diff --git a/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs b/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs
index c54c30478..5727f0735 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs
@@ -1,10 +1,18 @@
-using Flow.Launcher.Plugin;
+using System.Text.Json.Serialization;
+using CommunityToolkit.Mvvm.DependencyInjection;
+using Flow.Launcher.Plugin;
namespace Flow.Launcher.ViewModel
{
public class CustomExplorerViewModel : BaseModel
{
+ // We should not initialize API in static constructor because it will create another API instance
+ private static IPublicAPI api = null;
+ private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService();
+
public string Name { get; set; }
+ [JsonIgnore]
+ public string DisplayName => Name == "Explorer" ? API.GetTranslation("fileManagerExplorer") : Name;
public string Path { get; set; }
public string FileArgument { get; set; } = "\"%d\"";
public string DirectoryArgument { get; set; } = "\"%d\"";
@@ -21,5 +29,10 @@ namespace Flow.Launcher.ViewModel
Editable = Editable
};
}
+
+ public void OnDisplayNameChanged()
+ {
+ OnPropertyChanged(nameof(DisplayName));
+ }
}
}
diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml
index f7fd0c8e5..b38fe8aab 100644
--- a/Flow.Launcher/Languages/en.xaml
+++ b/Flow.Launcher/Languages/en.xaml
@@ -487,6 +487,7 @@
Arg For File
The file manager '{0}' could not be located at '{1}'. Would you like to continue?
File Manager Path Error
+ File Explorer
Default Web Browser
@@ -497,6 +498,8 @@
New Window
New Tab
Private Mode
+ Default
+ New Profile
Change Priority
diff --git a/Flow.Launcher/SelectBrowserWindow.xaml b/Flow.Launcher/SelectBrowserWindow.xaml
index d51d597b7..67c22b07d 100644
--- a/Flow.Launcher/SelectBrowserWindow.xaml
+++ b/Flow.Launcher/SelectBrowserWindow.xaml
@@ -92,7 +92,7 @@
SelectedIndex="{Binding SelectedCustomBrowserIndex}">
-
+
diff --git a/Flow.Launcher/SelectBrowserWindow.xaml.cs b/Flow.Launcher/SelectBrowserWindow.xaml.cs
index 565b4cbc3..8ef50ca75 100644
--- a/Flow.Launcher/SelectBrowserWindow.xaml.cs
+++ b/Flow.Launcher/SelectBrowserWindow.xaml.cs
@@ -31,7 +31,7 @@ namespace Flow.Launcher
private void btnBrowseFile_Click(object sender, RoutedEventArgs e)
{
- var selectedFilePath = _viewModel.SelectFile();
+ var selectedFilePath = SelectFile();
if (!string.IsNullOrEmpty(selectedFilePath))
{
@@ -41,5 +41,15 @@ namespace Flow.Launcher
((Button)sender).Focus();
}
}
+
+ private static string SelectFile()
+ {
+ var dlg = new Microsoft.Win32.OpenFileDialog();
+ var result = dlg.ShowDialog();
+ if (result == true)
+ return dlg.FileName;
+
+ return string.Empty;
+ }
}
}
diff --git a/Flow.Launcher/SelectFileManagerWindow.xaml b/Flow.Launcher/SelectFileManagerWindow.xaml
index b3b219d1c..cd4bec424 100644
--- a/Flow.Launcher/SelectFileManagerWindow.xaml
+++ b/Flow.Launcher/SelectFileManagerWindow.xaml
@@ -102,7 +102,7 @@
SelectedIndex="{Binding SelectedCustomExplorerIndex}">
-
+
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
index ec75ddf90..b47b53654 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
@@ -219,6 +219,8 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
DropdownDataGeneric.UpdateLabels(DialogJumpFileResultBehaviours);
// Since we are using Binding instead of DynamicResource, we need to manually trigger the update
OnPropertyChanged(nameof(AlwaysPreviewToolTip));
+ Settings.CustomExplorer.OnDisplayNameChanged();
+ Settings.CustomBrowser.OnDisplayNameChanged();
}
public string Language
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
index 81e15df69..07cc7b6a7 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
@@ -403,7 +403,7 @@
MaxWidth="250"
Margin="10 0 0 0"
Command="{Binding SelectFileManagerCommand}"
- Content="{Binding Settings.CustomExplorer.Name}" />
+ Content="{Binding Settings.CustomExplorer.DisplayName}" />
+ Content="{Binding Settings.CustomBrowser.DisplayName}" />
diff --git a/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs b/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs
index 67bbbd930..bcc6f1489 100644
--- a/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs
+++ b/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs
@@ -40,22 +40,12 @@ public partial class SelectBrowserViewModel : BaseModel
return true;
}
- internal string SelectFile()
- {
- var dlg = new Microsoft.Win32.OpenFileDialog();
- var result = dlg.ShowDialog();
- if (result == true)
- return dlg.FileName;
-
- return string.Empty;
- }
-
[RelayCommand]
private void Add()
{
CustomBrowsers.Add(new()
{
- Name = "New Profile"
+ Name = App.API.GetTranslation("defaultBrowser_new_profile")
});
SelectedCustomBrowserIndex = CustomBrowsers.Count - 1;
}
From 647c55eaf7e60a525f7e9c4ffa3a1c5809e39d66 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 11:47:18 +0800
Subject: [PATCH 36/55] Add property changed check
---
Flow.Launcher/ViewModel/SelectBrowserViewModel.cs | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs b/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs
index bcc6f1489..f75a0ef8b 100644
--- a/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs
+++ b/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs
@@ -17,8 +17,11 @@ public partial class SelectBrowserViewModel : BaseModel
get => selectedCustomBrowserIndex;
set
{
- selectedCustomBrowserIndex = value;
- OnPropertyChanged(nameof(CustomBrowser));
+ if (selectedCustomBrowserIndex != value)
+ {
+ selectedCustomBrowserIndex = value;
+ OnPropertyChanged(nameof(CustomBrowser));
+ }
}
}
From dd07baff59756db8b83e0a1ca362c136cd51b2d6 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 11:58:38 +0800
Subject: [PATCH 37/55] Add SelectFile helper method & Improve code quality
---
Flow.Launcher.Infrastructure/Win32Helper.cs | 16 +++++++++++++++-
Flow.Launcher/SelectBrowserWindow.xaml.cs | 13 ++-----------
Flow.Launcher/SelectFileManagerWindow.xaml.cs | 5 +++--
.../ViewModel/SelectFileManagerViewModel.cs | 15 ---------------
4 files changed, 20 insertions(+), 29 deletions(-)
diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs
index 811733925..5d30b740d 100644
--- a/Flow.Launcher.Infrastructure/Win32Helper.cs
+++ b/Flow.Launcher.Infrastructure/Win32Helper.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
@@ -904,5 +904,19 @@ namespace Flow.Launcher.Infrastructure
}
#endregion
+
+ #region File / Folder Dialog
+
+ public static string SelectFile()
+ {
+ var dlg = new OpenFileDialog();
+ var result = dlg.ShowDialog();
+ if (result == true)
+ return dlg.FileName;
+
+ return string.Empty;
+ }
+
+ #endregion
}
}
diff --git a/Flow.Launcher/SelectBrowserWindow.xaml.cs b/Flow.Launcher/SelectBrowserWindow.xaml.cs
index 8ef50ca75..290712aad 100644
--- a/Flow.Launcher/SelectBrowserWindow.xaml.cs
+++ b/Flow.Launcher/SelectBrowserWindow.xaml.cs
@@ -1,6 +1,7 @@
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.DependencyInjection;
+using Flow.Launcher.Infrastructure;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher
@@ -31,7 +32,7 @@ namespace Flow.Launcher
private void btnBrowseFile_Click(object sender, RoutedEventArgs e)
{
- var selectedFilePath = SelectFile();
+ var selectedFilePath = Win32Helper.SelectFile();
if (!string.IsNullOrEmpty(selectedFilePath))
{
@@ -41,15 +42,5 @@ namespace Flow.Launcher
((Button)sender).Focus();
}
}
-
- private static string SelectFile()
- {
- var dlg = new Microsoft.Win32.OpenFileDialog();
- var result = dlg.ShowDialog();
- if (result == true)
- return dlg.FileName;
-
- return string.Empty;
- }
}
}
diff --git a/Flow.Launcher/SelectFileManagerWindow.xaml.cs b/Flow.Launcher/SelectFileManagerWindow.xaml.cs
index d9c672aff..5143f9a56 100644
--- a/Flow.Launcher/SelectFileManagerWindow.xaml.cs
+++ b/Flow.Launcher/SelectFileManagerWindow.xaml.cs
@@ -2,6 +2,7 @@
using System.Windows.Controls;
using System.Windows.Navigation;
using CommunityToolkit.Mvvm.DependencyInjection;
+using Flow.Launcher.Infrastructure;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher
@@ -32,13 +33,13 @@ namespace Flow.Launcher
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
- _viewModel.OpenUrl(e.Uri.AbsoluteUri);
+ App.API.OpenUrl(e.Uri.AbsoluteUri);
e.Handled = true;
}
private void btnBrowseFile_Click(object sender, RoutedEventArgs e)
{
- var selectedFilePath = _viewModel.SelectFile();
+ var selectedFilePath = Win32Helper.SelectFile();
if (!string.IsNullOrEmpty(selectedFilePath))
{
diff --git a/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs b/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs
index 77f004980..253f74b47 100644
--- a/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs
+++ b/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs
@@ -98,21 +98,6 @@ public partial class SelectFileManagerViewModel : BaseModel
}
}
- internal void OpenUrl(string absoluteUri)
- {
- App.API.OpenUrl(absoluteUri);
- }
-
- internal string SelectFile()
- {
- var dlg = new Microsoft.Win32.OpenFileDialog();
- var result = dlg.ShowDialog();
- if (result == true)
- return dlg.FileName;
-
- return string.Empty;
- }
-
[RelayCommand]
private void Add()
{
From a03f62832ad52362cefc06f23f5130e1d78c82af Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 12:08:49 +0800
Subject: [PATCH 38/55] Ignore index change for -1
---
Flow.Launcher/ViewModel/SelectBrowserViewModel.cs | 2 ++
Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs | 2 ++
2 files changed, 4 insertions(+)
diff --git a/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs b/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs
index f75a0ef8b..e3a0e4e44 100644
--- a/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs
+++ b/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs
@@ -17,6 +17,8 @@ public partial class SelectBrowserViewModel : BaseModel
get => selectedCustomBrowserIndex;
set
{
+ // When one custom browser is selected and removed, the index will become -1, so we need to ignore this change
+ if (value < 0) return;
if (selectedCustomBrowserIndex != value)
{
selectedCustomBrowserIndex = value;
diff --git a/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs b/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs
index 253f74b47..b0851b90c 100644
--- a/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs
+++ b/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs
@@ -21,6 +21,8 @@ public partial class SelectFileManagerViewModel : BaseModel
get => selectedCustomExplorerIndex;
set
{
+ // When one custom file manager is selected and removed, the index will become -1, so we need to ignore this change
+ if (value < 0) return;
if (selectedCustomExplorerIndex != value)
{
selectedCustomExplorerIndex = value;
From 256ae5c4b0a49b24f37326ef6a25218581d68aec Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 12:15:33 +0800
Subject: [PATCH 39/55] Fill missing translation
---
Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs b/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs
index b0851b90c..f6a32e3fe 100644
--- a/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs
+++ b/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs
@@ -105,7 +105,7 @@ public partial class SelectFileManagerViewModel : BaseModel
{
CustomExplorers.Add(new()
{
- Name = "New Profile"
+ Name = App.API.GetTranslation("defaultBrowser_new_profile")
});
SelectedCustomExplorerIndex = CustomExplorers.Count - 1;
}
From 101750115389e169a491f42c3ece4310165eec3d Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 12:36:40 +0800
Subject: [PATCH 40/55] Fix namespace issue
---
.../UserSettings/CustomExplorerViewModel.cs | 2 +-
Flow.Launcher.Infrastructure/UserSettings/Settings.cs | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs b/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs
index 5727f0735..2af0bb0e5 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs
@@ -2,7 +2,7 @@
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
-namespace Flow.Launcher.ViewModel
+namespace Flow.Launcher.Infrastructure.UserSettings
{
public class CustomExplorerViewModel : BaseModel
{
diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
index 23f9047fe..f70c4559b 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
@@ -9,7 +9,6 @@ using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
-using Flow.Launcher.ViewModel;
namespace Flow.Launcher.Infrastructure.UserSettings
{
From b05c2c1e1af62d07a8bd27dc2e9ae409a362df0f Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 16:25:21 +0800
Subject: [PATCH 41/55] Remove old dictionaries references to fix possible
memory leak
---
Flow.Launcher.Core/Resource/Internationalization.cs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs
index 8261feab3..e8711819c 100644
--- a/Flow.Launcher.Core/Resource/Internationalization.cs
+++ b/Flow.Launcher.Core/Resource/Internationalization.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
@@ -256,6 +256,7 @@ namespace Flow.Launcher.Core.Resource
foreach (var r in _oldResources)
{
dicts.Remove(r);
+ _oldResources.Remove(r);
}
}
From 6c695f09e71ef88571c40494747cb08f95e14979 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 17:55:06 +0800
Subject: [PATCH 42/55] Use clear function
---
Flow.Launcher.Core/Resource/Internationalization.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs
index e8711819c..6df2a28c6 100644
--- a/Flow.Launcher.Core/Resource/Internationalization.cs
+++ b/Flow.Launcher.Core/Resource/Internationalization.cs
@@ -256,8 +256,8 @@ namespace Flow.Launcher.Core.Resource
foreach (var r in _oldResources)
{
dicts.Remove(r);
- _oldResources.Remove(r);
}
+ _oldResources.Clear();
}
private void LoadLanguage(Language language)
From 330e6c09e7d8321ca268147de4a6490331d92bc0 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 18:07:18 +0800
Subject: [PATCH 43/55] Add language change lock
---
.../Resource/Internationalization.cs | 32 ++++++++++++-------
1 file changed, 21 insertions(+), 11 deletions(-)
diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs
index 6df2a28c6..c1fa2ea16 100644
--- a/Flow.Launcher.Core/Resource/Internationalization.cs
+++ b/Flow.Launcher.Core/Resource/Internationalization.cs
@@ -30,6 +30,7 @@ namespace Flow.Launcher.Core.Resource
private readonly List _languageDirectories = [];
private readonly List _oldResources = [];
private static string SystemLanguageCode;
+ private readonly SemaphoreSlim _langChangeLock = new(1, 1);
public Internationalization(Settings settings)
{
@@ -185,20 +186,29 @@ namespace Flow.Launcher.Core.Resource
private async Task ChangeLanguageAsync(Language language, bool updateMetadata = true)
{
- // Remove old language files and load language
- RemoveOldLanguageFiles();
- if (language != AvailableLanguages.English)
+ await _langChangeLock.WaitAsync();
+
+ try
{
- LoadLanguage(language);
+ // Remove old language files and load language
+ RemoveOldLanguageFiles();
+ if (language != AvailableLanguages.English)
+ {
+ LoadLanguage(language);
+ }
+
+ // Change culture info
+ ChangeCultureInfo(language.LanguageCode);
+
+ if (updateMetadata)
+ {
+ // Raise event for plugins after culture is set
+ await Task.Run(UpdatePluginMetadataTranslations);
+ }
}
-
- // Change culture info
- ChangeCultureInfo(language.LanguageCode);
-
- if (updateMetadata)
+ finally
{
- // Raise event for plugins after culture is set
- await Task.Run(UpdatePluginMetadataTranslations);
+ _langChangeLock.Release();
}
}
From 86581e6a00f0d7806a3e910fa39283c03c0f498b Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 18:17:27 +0800
Subject: [PATCH 44/55] Add disposable for internalization
---
Flow.Launcher.Core/Resource/Internationalization.cs | 12 +++++++++++-
Flow.Launcher/App.xaml.cs | 5 ++++-
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs
index c1fa2ea16..2e270a20b 100644
--- a/Flow.Launcher.Core/Resource/Internationalization.cs
+++ b/Flow.Launcher.Core/Resource/Internationalization.cs
@@ -14,7 +14,7 @@ using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Resource
{
- public class Internationalization
+ public class Internationalization : IDisposable
{
private static readonly string ClassName = nameof(Internationalization);
@@ -379,5 +379,15 @@ namespace Flow.Launcher.Core.Resource
}
#endregion
+
+ #region IDisposable
+
+ public void Dispose()
+ {
+ RemoveOldLanguageFiles();
+ _langChangeLock.Dispose();
+ }
+
+ #endregion
}
}
diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs
index 0360c761e..8ec11e5ff 100644
--- a/Flow.Launcher/App.xaml.cs
+++ b/Flow.Launcher/App.xaml.cs
@@ -45,6 +45,7 @@ namespace Flow.Launcher
private static Settings _settings;
private static MainWindow _mainWindow;
private readonly MainViewModel _mainVM;
+ private readonly Internationalization _internationalization;
// To prevent two disposals running at the same time.
private static readonly object _disposingLock = new();
@@ -107,6 +108,7 @@ namespace Flow.Launcher
API = Ioc.Default.GetRequiredService();
_settings.Initialize();
_mainVM = Ioc.Default.GetRequiredService();
+ _internationalization = Ioc.Default.GetRequiredService();
}
catch (Exception e)
{
@@ -193,7 +195,7 @@ namespace Flow.Launcher
Win32Helper.EnableWin32DarkMode(_settings.ColorScheme);
// Initialize language before portable clean up since it needs translations
- await Ioc.Default.GetRequiredService().InitializeLanguageAsync();
+ await _internationalization.InitializeLanguageAsync();
Ioc.Default.GetRequiredService().PreStartCleanUpAfterPortabilityUpdate();
@@ -421,6 +423,7 @@ namespace Flow.Launcher
_mainWindow?.Dispatcher.Invoke(_mainWindow.Dispose);
_mainVM?.Dispose();
DialogJump.Dispose();
+ _internationalization.Dispose();
}
API.LogInfo(ClassName, "End Flow Launcher dispose ----------------------------------------------------");
From 0f6245a072f8ad7c7b7fa93b4915d2505c880f0f Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 18:18:02 +0800
Subject: [PATCH 45/55] Handle exceptions inside ChangeLanguageAsync to avoid
unobserved task crashes
---
Flow.Launcher.Core/Resource/Internationalization.cs | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs
index 2e270a20b..983f8b234 100644
--- a/Flow.Launcher.Core/Resource/Internationalization.cs
+++ b/Flow.Launcher.Core/Resource/Internationalization.cs
@@ -206,6 +206,10 @@ namespace Flow.Launcher.Core.Resource
await Task.Run(UpdatePluginMetadataTranslations);
}
}
+ catch (Exception e)
+ {
+ API.LogException(ClassName, $"Failed to change language to <{language.LanguageCode}>", e);
+ }
finally
{
_langChangeLock.Release();
From fc2e3fec630f98c532ae4525b8a99eddbf3e1314 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 18:29:45 +0800
Subject: [PATCH 46/55] Improve ImageLoader performance
---
Flow.Launcher.Infrastructure/Image/ImageLoader.cs | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
index 64d323de6..598347fd2 100644
--- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
+++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
@@ -22,7 +22,7 @@ namespace Flow.Launcher.Infrastructure.Image
private static Lock storageLock { get; } = new();
private static BinaryStorage> _storage;
private static readonly ConcurrentDictionary GuidToKey = new();
- private static IImageHashGenerator _hashGenerator;
+ private static ImageHashGenerator _hashGenerator;
private static readonly bool EnableImageHash = true;
public static ImageSource Image => ImageCache[Constant.ImageIcon, false];
public static ImageSource MissingImage => ImageCache[Constant.MissingImgIcon, false];
@@ -31,7 +31,7 @@ namespace Flow.Launcher.Infrastructure.Image
public const int FullIconSize = 256;
public const int FullImageSize = 320;
- private static readonly string[] ImageExtensions = { ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico" };
+ private static readonly string[] ImageExtensions = [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico"];
private static readonly string SvgExtension = ".svg";
public static async Task InitializeAsync()
@@ -327,7 +327,7 @@ namespace Flow.Launcher.Infrastructure.Image
return img;
}
- private static ImageSource LoadFullImage(string path)
+ private static BitmapImage LoadFullImage(string path)
{
BitmapImage image = new BitmapImage();
image.BeginInit();
@@ -364,7 +364,7 @@ namespace Flow.Launcher.Infrastructure.Image
return image;
}
- private static ImageSource LoadSvgImage(string path, bool loadFullImage = false)
+ private static RenderTargetBitmap LoadSvgImage(string path, bool loadFullImage = false)
{
// Set up drawing settings
var desiredHeight = loadFullImage ? FullImageSize : SmallIconSize;
From 9a597f2b4d1e3928c849b28de3d0b1b6cd7d84c8 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 18:32:13 +0800
Subject: [PATCH 47/55] Disable cache feature
---
Flow.Launcher/Helper/WallpaperPathRetrieval.cs | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
index fd04b3e88..fe0ff39ad 100644
--- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
+++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
@@ -15,8 +15,9 @@ public static class WallpaperPathRetrieval
{
private static readonly string ClassName = nameof(WallpaperPathRetrieval);
- private const int MaxCacheSize = 3;
- private static readonly Dictionary<(string, DateTime), ImageBrush> WallpaperCache = new();
+ // Disable cache feature because some wallpaper applications (like Wallpaper Engine) may change wallpaper frequently
+ private const int MaxCacheSize = 0;//3;
+ private static readonly Dictionary<(string, DateTime), ImageBrush> WallpaperCache = [];
private static readonly Lock CacheLock = new();
public static Brush GetWallpaperBrush()
From 35a5e27e2d10f19b1f689e989d4efbf92d28a78f Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 19:15:22 +0800
Subject: [PATCH 48/55] Fix DirectoryNotFoundException when deleting cache
twice
---
.../ViewModels/SettingsPaneAboutViewModel.cs | 59 ++++++++++---------
1 file changed, 32 insertions(+), 27 deletions(-)
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs
index 1efc89972..5e24b9dc8 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs
@@ -231,36 +231,41 @@ public partial class SettingsPaneAboutViewModel : BaseModel
}
});
- // Firstly, delete plugin cache directories
- pluginCacheDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
- .ToList()
- .ForEach(dir =>
+ // Check if plugin cache directory exists before attempting to delete
+ // Or it will throw DirectoryNotFoundException in `pluginCacheDirectory.EnumerateDirectories`
+ if (pluginCacheDirectory.Exists)
+ {
+ // Firstly, delete plugin cache directories
+ pluginCacheDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
+ .ToList()
+ .ForEach(dir =>
+ {
+ try
+ {
+ // Plugin may create directories in its cache directory
+ dir.Delete(recursive: true);
+ }
+ catch (Exception e)
+ {
+ App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
+ success = false;
+ }
+ });
+
+ // Then, delete plugin directory
+ var dir = pluginCacheDirectory;
+ try
{
- try
- {
- // Plugin may create directories in its cache directory
- dir.Delete(recursive: true);
- }
- catch (Exception e)
- {
- App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
- success = false;
- }
- });
+ dir.Delete(recursive: false);
+ }
+ catch (Exception e)
+ {
+ App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
+ success = false;
+ }
- // Then, delete plugin directory
- var dir = GetPluginCacheDir();
- try
- {
- dir.Delete(recursive: false);
+ OnPropertyChanged(nameof(CacheFolderSize));
}
- catch (Exception e)
- {
- App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
- success = false;
- }
-
- OnPropertyChanged(nameof(CacheFolderSize));
return success;
}
From ec182ade398544605da0a90bc7afadf65ef575be Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 19:18:11 +0800
Subject: [PATCH 49/55] Fix property changed event
---
.../SettingPages/ViewModels/SettingsPaneAboutViewModel.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs
index 5e24b9dc8..7a6a1d91b 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs
@@ -263,10 +263,10 @@ public partial class SettingsPaneAboutViewModel : BaseModel
App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
success = false;
}
-
- OnPropertyChanged(nameof(CacheFolderSize));
}
+ OnPropertyChanged(nameof(CacheFolderSize));
+
return success;
}
From 4bea4101a1c5a7e13f8befa7ef33b0bafe98e1e7 Mon Sep 17 00:00:00 2001
From: Jeremy Wu
Date: Thu, 18 Sep 2025 21:40:37 +1000
Subject: [PATCH 50/55] add comment for cache folder size refresh event
---
.../SettingPages/ViewModels/SettingsPaneAboutViewModel.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs
index 7a6a1d91b..647b36701 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs
@@ -265,6 +265,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
}
}
+ // Raise regardless to cover scenario where size needs to be recalculated if the folder is manually removed on disk.
OnPropertyChanged(nameof(CacheFolderSize));
return success;
From 7a5e55e5f0b5f96a29e67cefab6a6dec63463ba6 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 21:06:18 +0800
Subject: [PATCH 51/55] Use Debug instead of Info
---
Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs
index 2ff51ff73..841099dd1 100644
--- a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs
@@ -75,7 +75,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
- API.LogInfo(ClassName, $"Fetching from {ManifestFileUrl} was cancelled by caller.");
+ API.LogDebug(ClassName, $"Fetching from {ManifestFileUrl} was cancelled by caller.");
return null;
}
catch (TaskCanceledException)
From 245c492906aad3cd138b501363e9cdee914c484d Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 21:13:12 +0800
Subject: [PATCH 52/55] Improve code quality
---
Flow.Launcher/ViewModel/PluginViewModel.cs | 48 ++++++++++++----------
1 file changed, 27 insertions(+), 21 deletions(-)
diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs
index 59bb53a4a..c42791e8f 100644
--- a/Flow.Launcher/ViewModel/PluginViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginViewModel.cs
@@ -19,6 +19,9 @@ namespace Flow.Launcher.ViewModel
private static readonly Settings Settings = Ioc.Default.GetRequiredService();
+ private static readonly Thickness SettingPanelMargin = (Thickness)Application.Current.FindResource("SettingPanelMargin");
+ private static readonly Thickness SettingPanelItemTopBottomMargin = (Thickness)Application.Current.FindResource("SettingPanelItemTopBottomMargin");
+
private readonly PluginPair _pluginPair;
public PluginPair PluginPair
{
@@ -139,8 +142,6 @@ namespace Flow.Launcher.ViewModel
: null;
private ImageSource _image = ImageLoader.MissingImage;
- private static readonly Thickness SettingPanelMargin = (Thickness)Application.Current.FindResource("SettingPanelMargin");
- private static readonly Thickness SettingPanelItemTopBottomMargin = (Thickness)Application.Current.FindResource("SettingPanelItemTopBottomMargin");
private static Control TryCreateSettingPanel(PluginPair pair)
{
try
@@ -156,25 +157,7 @@ namespace Flow.Launcher.ViewModel
// Show error message in UI
var errorMsg = string.Format(App.API.GetTranslation("errorCreatingSettingPanel"),
pair.Metadata.Name, Environment.NewLine, e.Message);
- var grid = new Grid()
- {
- Margin = SettingPanelMargin
- };
- var textBox = new TextBox
- {
- Text = errorMsg,
- IsReadOnly = true,
- HorizontalAlignment = HorizontalAlignment.Stretch,
- VerticalAlignment = VerticalAlignment.Top,
- TextWrapping = TextWrapping.Wrap,
- Margin = SettingPanelItemTopBottomMargin
- };
- textBox.SetResourceReference(TextBlock.ForegroundProperty, "Color04B");
- grid.Children.Add(textBox);
- return new UserControl
- {
- Content = grid
- };
+ return CreateErrorSettingPanel(errorMsg);
}
}
@@ -228,5 +211,28 @@ namespace Flow.Launcher.ViewModel
var changeKeywordsWindow = new ActionKeywords(this);
changeKeywordsWindow.ShowDialog();
}
+
+ private static UserControl CreateErrorSettingPanel(string text)
+ {
+ var grid = new Grid()
+ {
+ Margin = SettingPanelMargin
+ };
+ var textBox = new TextBox
+ {
+ Text = text,
+ IsReadOnly = true,
+ HorizontalAlignment = HorizontalAlignment.Stretch,
+ VerticalAlignment = VerticalAlignment.Top,
+ TextWrapping = TextWrapping.Wrap,
+ Margin = SettingPanelItemTopBottomMargin
+ };
+ textBox.SetResourceReference(TextBlock.ForegroundProperty, "Color04B");
+ grid.Children.Add(textBox);
+ return new UserControl
+ {
+ Content = grid
+ };
+ }
}
}
From 72dae631fe8c3fd8e9cd7c466c3481be8ae3da7a Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 21:19:49 +0800
Subject: [PATCH 53/55] Use TextBox.ForegroundProperty
---
Flow.Launcher/ViewModel/PluginViewModel.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs
index c42791e8f..29f2b9b43 100644
--- a/Flow.Launcher/ViewModel/PluginViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginViewModel.cs
@@ -227,7 +227,7 @@ namespace Flow.Launcher.ViewModel
TextWrapping = TextWrapping.Wrap,
Margin = SettingPanelItemTopBottomMargin
};
- textBox.SetResourceReference(TextBlock.ForegroundProperty, "Color04B");
+ textBox.SetResourceReference(TextBox.ForegroundProperty, "Color04B");
grid.Children.Add(textBox);
return new UserControl
{
From bbc12ec04123b7006b902001e860d9c78da4ef0d Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Thu, 18 Sep 2025 21:45:24 +0800
Subject: [PATCH 54/55] Revert "Disable cache feature"
This reverts commit 9a597f2b4d1e3928c849b28de3d0b1b6cd7d84c8.
---
Flow.Launcher/Helper/WallpaperPathRetrieval.cs | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
index fe0ff39ad..fd04b3e88 100644
--- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
+++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs
@@ -15,9 +15,8 @@ public static class WallpaperPathRetrieval
{
private static readonly string ClassName = nameof(WallpaperPathRetrieval);
- // Disable cache feature because some wallpaper applications (like Wallpaper Engine) may change wallpaper frequently
- private const int MaxCacheSize = 0;//3;
- private static readonly Dictionary<(string, DateTime), ImageBrush> WallpaperCache = [];
+ private const int MaxCacheSize = 3;
+ private static readonly Dictionary<(string, DateTime), ImageBrush> WallpaperCache = new();
private static readonly Lock CacheLock = new();
public static Brush GetWallpaperBrush()
From 78e5bf2a601e30c41a7aaaf4428bfd7822f36a93 Mon Sep 17 00:00:00 2001
From: Jack251970 <1160210343@qq.com>
Date: Sun, 21 Sep 2025 11:50:51 +0800
Subject: [PATCH 55/55] Use Flow.Launcher.Localization to improve code quality
---
...low.Launcher.Plugin.BrowserBookmark.csproj | 2 +-
.../Flow.Launcher.Plugin.Calculator.csproj | 2 +-
.../ContextMenu.cs | 114 ++++++++----------
.../Flow.Launcher.Plugin.Explorer.csproj | 2 +-
.../Languages/en.xaml | 1 +
Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 4 +-
.../Everything/EverythingDownloadHelper.cs | 15 ++-
.../Everything/EverythingSearchManager.cs | 14 +--
.../Search/ResultManager.cs | 29 ++---
.../Search/SearchManager.cs | 4 +-
.../WindowsIndex/WindowsIndexSearchManager.cs | 4 +-
.../Flow.Launcher.Plugin.Explorer/Settings.cs | 9 +-
.../ViewModels/SettingsViewModel.cs | 12 +-
.../Views/ActionKeywordSetting.xaml.cs | 12 +-
.../Views/PreviewPanel.xaml.cs | 59 +++++----
.../Views/QuickAccessLinkSettings.xaml.cs | 4 +-
16 files changed, 134 insertions(+), 153 deletions(-)
diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
index 9cb2469d9..e3233f73d 100644
--- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
+++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj
@@ -104,7 +104,7 @@
-
+
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj
index b3cee425d..20a0ec4f0 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj
@@ -63,7 +63,7 @@
-
+
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs
index 3802c701b..90db87966 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs
@@ -66,8 +66,8 @@ namespace Flow.Launcher.Plugin.Explorer
{
contextMenus.Add(new Result
{
- Title = Context.API.GetTranslation("plugin_explorer_add_to_quickaccess_title"),
- SubTitle = Context.API.GetTranslation("plugin_explorer_add_to_quickaccess_subtitle"),
+ Title = Localize.plugin_explorer_add_to_quickaccess_title(),
+ SubTitle = Localize.plugin_explorer_add_to_quickaccess_subtitle(),
Action = (context) =>
{
Settings.QuickAccessLinks.Add(new AccessLink
@@ -77,16 +77,14 @@ namespace Flow.Launcher.Plugin.Explorer
Type = record.Type
});
- Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_addfilefoldersuccess"),
- Context.API.GetTranslation("plugin_explorer_addfilefoldersuccess_detail"),
- Constants.ExplorerIconImageFullPath);
-
-
+ Context.API.ShowMsg(Localize.plugin_explorer_addfilefoldersuccess(),
+ Localize.plugin_explorer_addfilefoldersuccess_detail(),
+ Constants.ExplorerIconImageFullPath);
return true;
},
- SubTitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_titletooltip"),
- TitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_titletooltip"),
+ SubTitleToolTip = Localize.plugin_explorer_contextmenu_titletooltip(),
+ TitleToolTip = Localize.plugin_explorer_contextmenu_titletooltip(),
IcoPath = Constants.QuickAccessImagePath,
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue718"),
});
@@ -95,22 +93,20 @@ namespace Flow.Launcher.Plugin.Explorer
{
contextMenus.Add(new Result
{
- Title = Context.API.GetTranslation("plugin_explorer_remove_from_quickaccess_title"),
- SubTitle = Context.API.GetTranslation("plugin_explorer_remove_from_quickaccess_subtitle"),
+ Title = Localize.plugin_explorer_remove_from_quickaccess_title(),
+ SubTitle = Localize.plugin_explorer_remove_from_quickaccess_subtitle(),
Action = (context) =>
{
Settings.QuickAccessLinks.Remove(Settings.QuickAccessLinks.FirstOrDefault(x => string.Equals(x.Path, record.FullPath, StringComparison.OrdinalIgnoreCase)));
- Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_removefilefoldersuccess"),
- Context.API.GetTranslation("plugin_explorer_removefilefoldersuccess_detail"),
- Constants.ExplorerIconImageFullPath);
-
-
+ Context.API.ShowMsg(Localize.plugin_explorer_removefilefoldersuccess(),
+ Localize.plugin_explorer_removefilefoldersuccess_detail(),
+ Constants.ExplorerIconImageFullPath);
return true;
},
- SubTitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_remove_titletooltip"),
- TitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_remove_titletooltip"),
+ SubTitleToolTip = Localize.plugin_explorer_contextmenu_remove_titletooltip(),
+ TitleToolTip = Localize.plugin_explorer_contextmenu_remove_titletooltip(),
IcoPath = Constants.RemoveQuickAccessImagePath,
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uecc9")
});
@@ -118,8 +114,8 @@ namespace Flow.Launcher.Plugin.Explorer
contextMenus.Add(new Result
{
- Title = Context.API.GetTranslation("plugin_explorer_copypath"),
- SubTitle = Context.API.GetTranslation("plugin_explorer_copypath_subtitle"),
+ Title = Localize.plugin_explorer_copypath(),
+ SubTitle = Localize.plugin_explorer_copypath_subtitle(),
Action = _ =>
{
try
@@ -130,7 +126,7 @@ namespace Flow.Launcher.Plugin.Explorer
catch (Exception e)
{
LogException("Fail to set text in clipboard", e);
- Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_fail_to_set_text"));
+ Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_set_text());
return false;
}
},
@@ -140,8 +136,8 @@ namespace Flow.Launcher.Plugin.Explorer
contextMenus.Add(new Result
{
- Title = Context.API.GetTranslation("plugin_explorer_copyname"),
- SubTitle = Context.API.GetTranslation("plugin_explorer_copyname_subtitle"),
+ Title = Localize.plugin_explorer_copyname(),
+ SubTitle = Localize.plugin_explorer_copyname_subtitle(),
Action = _ =>
{
try
@@ -152,7 +148,7 @@ namespace Flow.Launcher.Plugin.Explorer
catch (Exception e)
{
LogException("Fail to set text in clipboard", e);
- Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_fail_to_set_text"));
+ Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_set_text());
return false;
}
},
@@ -162,8 +158,8 @@ namespace Flow.Launcher.Plugin.Explorer
contextMenus.Add(new Result
{
- Title = Context.API.GetTranslation("plugin_explorer_copyfilefolder"),
- SubTitle = isFile ? Context.API.GetTranslation("plugin_explorer_copyfile_subtitle") : Context.API.GetTranslation("plugin_explorer_copyfolder_subtitle"),
+ Title = Localize.plugin_explorer_copyfilefolder(),
+ SubTitle = isFile ? Localize.plugin_explorer_copyfile_subtitle(): Localize.plugin_explorer_copyfolder_subtitle(),
Action = _ =>
{
try
@@ -174,28 +170,26 @@ namespace Flow.Launcher.Plugin.Explorer
catch (Exception e)
{
LogException($"Fail to set file/folder in clipboard", e);
- Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_fail_to_set_files"));
+ Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_set_files());
return false;
}
-
},
IcoPath = icoPath,
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uf12b")
});
-
if (record.Type is ResultType.File or ResultType.Folder)
contextMenus.Add(new Result
{
- Title = Context.API.GetTranslation("plugin_explorer_deletefilefolder"),
- SubTitle = isFile ? Context.API.GetTranslation("plugin_explorer_deletefile_subtitle") : Context.API.GetTranslation("plugin_explorer_deletefolder_subtitle"),
+ Title = Localize.plugin_explorer_deletefilefolder(),
+ SubTitle = isFile ? Localize.plugin_explorer_deletefile_subtitle(): Localize.plugin_explorer_deletefolder_subtitle(),
Action = (context) =>
{
try
{
if (Context.API.ShowMsgBox(
- string.Format(Context.API.GetTranslation("plugin_explorer_delete_folder_link"), record.FullPath),
- Context.API.GetTranslation("plugin_explorer_deletefilefolder"),
+ Localize.plugin_explorer_delete_folder_link(record.FullPath),
+ Localize.plugin_explorer_deletefilefolder(),
MessageBoxButton.OKCancel,
MessageBoxImage.Warning)
== MessageBoxResult.Cancel)
@@ -208,15 +202,15 @@ namespace Flow.Launcher.Plugin.Explorer
_ = Task.Run(() =>
{
- Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_deletefilefoldersuccess"),
- string.Format(Context.API.GetTranslation("plugin_explorer_deletefilefoldersuccess_detail"), record.FullPath),
+ Context.API.ShowMsg(Localize.plugin_explorer_deletefilefoldersuccess(),
+ Localize.plugin_explorer_deletefilefoldersuccess_detail(record.FullPath),
Constants.ExplorerIconImageFullPath);
});
}
catch (Exception e)
{
LogException($"Fail to delete {record.FullPath}", e);
- Context.API.ShowMsgError(string.Format(Context.API.GetTranslation("plugin_explorer_fail_to_delete"), record.FullPath));
+ Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_delete(record.FullPath));
return false;
}
@@ -230,7 +224,7 @@ namespace Flow.Launcher.Plugin.Explorer
{
contextMenus.Add(new Result()
{
- Title = Context.API.GetTranslation("plugin_explorer_show_contextmenu_title"),
+ Title = Localize.plugin_explorer_show_contextmenu_title(),
IcoPath = Constants.ShowContextMenuImagePath,
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue700"),
Action = _ =>
@@ -248,8 +242,8 @@ namespace Flow.Launcher.Plugin.Explorer
if (record.Type == ResultType.File && CanRunAsDifferentUser(record.FullPath))
contextMenus.Add(new Result
{
- Title = Context.API.GetTranslation("plugin_explorer_runasdifferentuser"),
- SubTitle = Context.API.GetTranslation("plugin_explorer_runasdifferentuser_subtitle"),
+ Title = Localize.plugin_explorer_runasdifferentuser(),
+ SubTitle = Localize.plugin_explorer_runasdifferentuser_subtitle(),
Action = (context) =>
{
try
@@ -259,8 +253,8 @@ namespace Flow.Launcher.Plugin.Explorer
catch (FileNotFoundException e)
{
Context.API.ShowMsgError(
- Context.API.GetTranslation("plugin_explorer_plugin_name"),
- string.Format(Context.API.GetTranslation("plugin_explorer_file_not_found"), e.Message));
+ Localize.plugin_explorer_plugin_name(),
+ Localize.plugin_explorer_file_not_found(e.Message));
return false;
}
@@ -317,8 +311,8 @@ namespace Flow.Launcher.Plugin.Explorer
{
return new Result
{
- Title = Context.API.GetTranslation("plugin_explorer_opencontainingfolder"),
- SubTitle = Context.API.GetTranslation("plugin_explorer_opencontainingfolder_subtitle"),
+ Title = Localize.plugin_explorer_opencontainingfolder(),
+ SubTitle = Localize.plugin_explorer_opencontainingfolder_subtitle(),
Action = _ =>
{
try
@@ -328,7 +322,7 @@ namespace Flow.Launcher.Plugin.Explorer
catch (Exception e)
{
LogException($"Fail to open file at {record.FullPath}", e);
- Context.API.ShowMsgError(string.Format(Context.API.GetTranslation("plugin_explorer_fail_to_open"), record.FullPath));
+ Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_open(record.FullPath));
return false;
}
@@ -339,11 +333,9 @@ namespace Flow.Launcher.Plugin.Explorer
};
}
-
-
private Result CreateOpenWithEditorResult(SearchResult record, string editorPath)
{
- var name = $"{Context.API.GetTranslation("plugin_explorer_openwitheditor")} {Path.GetFileNameWithoutExtension(editorPath)}";
+ var name = $"{Localize.plugin_explorer_openwitheditor()} {Path.GetFileNameWithoutExtension(editorPath)}";
return new Result
{
@@ -361,8 +353,7 @@ namespace Flow.Launcher.Plugin.Explorer
}
catch (Exception e)
{
- var raw_message = Context.API.GetTranslation("plugin_explorer_openwitheditor_error");
- var message = string.Format(raw_message, record.FullPath, Path.GetFileNameWithoutExtension(editorPath), editorPath);
+ var message = Localize.plugin_explorer_openwitheditor_error(record.FullPath, Path.GetFileNameWithoutExtension(editorPath), editorPath);
LogException(message, e);
Context.API.ShowMsgError(message);
return false;
@@ -377,7 +368,7 @@ namespace Flow.Launcher.Plugin.Explorer
{
string shellPath = Settings.ShellPath;
- var name = $"{Context.API.GetTranslation("plugin_explorer_openwithshell")} {Path.GetFileNameWithoutExtension(shellPath)}";
+ var name = $"{Localize.plugin_explorer_openwithshell()} {Path.GetFileNameWithoutExtension(shellPath)}";
return new Result
{
@@ -394,8 +385,7 @@ namespace Flow.Launcher.Plugin.Explorer
}
catch (Exception e)
{
- var raw_message = Context.API.GetTranslation("plugin_explorer_openwithshell_error");
- var message = string.Format(raw_message, record.FullPath, Path.GetFileNameWithoutExtension(shellPath), shellPath);
+ var message = Localize.plugin_explorer_openwithshell_error(record.FullPath, Path.GetFileNameWithoutExtension(shellPath), shellPath);
LogException(message, e);
Context.API.ShowMsgError(message);
return false;
@@ -410,8 +400,8 @@ namespace Flow.Launcher.Plugin.Explorer
{
return new Result
{
- Title = Context.API.GetTranslation("plugin_explorer_excludefromindexsearch"),
- SubTitle = Context.API.GetTranslation("plugin_explorer_path") + " " + record.FullPath,
+ Title = Localize.plugin_explorer_excludefromindexsearch(),
+ SubTitle = Localize.plugin_explorer_path()+ " " + record.FullPath,
Action = c_ =>
{
if (!Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => string.Equals(x.Path, record.FullPath, StringComparison.OrdinalIgnoreCase)))
@@ -422,8 +412,8 @@ namespace Flow.Launcher.Plugin.Explorer
_ = Task.Run(() =>
{
- Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_excludedfromindexsearch_msg"),
- Context.API.GetTranslation("plugin_explorer_path") +
+ Context.API.ShowMsg(Localize.plugin_explorer_excludedfromindexsearch_msg(),
+ Localize.plugin_explorer_path()+
" " + record.FullPath, Constants.ExplorerIconImageFullPath);
// so the new path can be persisted to storage and not wait till next ViewModel save.
@@ -441,8 +431,8 @@ namespace Flow.Launcher.Plugin.Explorer
{
return new Result
{
- Title = Context.API.GetTranslation("plugin_explorer_openindexingoptions"),
- SubTitle = Context.API.GetTranslation("plugin_explorer_openindexingoptions_subtitle"),
+ Title = Localize.plugin_explorer_openindexingoptions(),
+ SubTitle = Localize.plugin_explorer_openindexingoptions_subtitle(),
Action = _ =>
{
try
@@ -459,7 +449,7 @@ namespace Flow.Launcher.Plugin.Explorer
}
catch (Exception e)
{
- var message = Context.API.GetTranslation("plugin_explorer_openindexingoptions_errormsg");
+ var message = Localize.plugin_explorer_openindexingoptions_errormsg();
LogException(message, e);
Context.API.ShowMsgError(message);
return false;
@@ -470,12 +460,12 @@ namespace Flow.Launcher.Plugin.Explorer
};
}
- private Result CreateOpenWithMenu(SearchResult record)
+ private static Result CreateOpenWithMenu(SearchResult record)
{
return new Result
{
- Title = Context.API.GetTranslation("plugin_explorer_openwith"),
- SubTitle = Context.API.GetTranslation("plugin_explorer_openwith_subtitle"),
+ Title = Localize.plugin_explorer_openwith(),
+ SubTitle = Localize.plugin_explorer_openwith_subtitle(),
Action = _ =>
{
Process.Start("rundll32.exe", $"{Path.Combine(Environment.SystemDirectory, "shell32.dll")},OpenAs_RunDLL {record.FullPath}");
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj
index b7c54e578..a837a49b4 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj
@@ -48,7 +48,7 @@
-
+
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml
index 16ef037cc..c40040df5 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml
@@ -24,6 +24,7 @@
Error occurred during search: {0}
Could not open folder
Could not open file
+ This new action keyword is already assigned to another plugin, please choose a different one
Delete
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs
index d93c6c77b..f5b8b9325 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs
@@ -90,12 +90,12 @@ namespace Flow.Launcher.Plugin.Explorer
public string GetTranslatedPluginTitle()
{
- return Context.API.GetTranslation("plugin_explorer_plugin_name");
+ return Localize.plugin_explorer_plugin_name();
}
public string GetTranslatedPluginDescription()
{
- return Context.API.GetTranslation("plugin_explorer_plugin_description");
+ return Localize.plugin_explorer_plugin_description();
}
public void OnCultureInfoChanged(CultureInfo newCulture)
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingDownloadHelper.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingDownloadHelper.cs
index c8bd68279..13d988f1a 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingDownloadHelper.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingDownloadHelper.cs
@@ -21,9 +21,9 @@ public static class EverythingDownloadHelper
if (string.IsNullOrEmpty(installedLocation))
{
if (api.ShowMsgBox(
- string.Format(api.GetTranslation("flowlauncher_plugin_everything_installing_select"), Environment.NewLine),
- api.GetTranslation("flowlauncher_plugin_everything_installing_title"),
- MessageBoxButton.YesNo) == MessageBoxResult.Yes)
+ Localize.flowlauncher_plugin_everything_installing_select(Environment.NewLine),
+ Localize.flowlauncher_plugin_everything_installing_title(),
+ MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
var dlg = new System.Windows.Forms.OpenFileDialog
{
@@ -41,13 +41,13 @@ public static class EverythingDownloadHelper
return installedLocation;
}
- api.ShowMsg(api.GetTranslation("flowlauncher_plugin_everything_installing_title"),
- api.GetTranslation("flowlauncher_plugin_everything_installing_subtitle"), "", useMainWindowAsOwner: false);
+ api.ShowMsg(Localize.flowlauncher_plugin_everything_installing_title(),
+ Localize.flowlauncher_plugin_everything_installing_subtitle(), "", useMainWindowAsOwner: false);
await DroplexPackage.Drop(App.Everything1_4_1_1009).ConfigureAwait(false);
- api.ShowMsg(api.GetTranslation("flowlauncher_plugin_everything_installing_title"),
- api.GetTranslation("flowlauncher_plugin_everything_installationsuccess_subtitle"), "", useMainWindowAsOwner: false);
+ api.ShowMsg(Localize.flowlauncher_plugin_everything_installing_title(),
+ Localize.flowlauncher_plugin_everything_installationsuccess_subtitle(), "", useMainWindowAsOwner: false);
installedLocation = "C:\\Program Files\\Everything\\Everything.exe";
@@ -83,6 +83,5 @@ public static class EverythingDownloadHelper
var scoopInstalledPath = Environment.ExpandEnvironmentVariables(@"%userprofile%\scoop\apps\everything\current\Everything.exe");
return File.Exists(scoopInstalledPath) ? scoopInstalledPath : string.Empty;
-
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs
index ce71c94ba..eb994a6f9 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs
@@ -27,8 +27,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
if (!await EverythingApi.IsEverythingRunningAsync(token))
throw new EngineNotAvailableException(
Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
- Main.Context.API.GetTranslation("flowlauncher_plugin_everything_click_to_launch_or_install"),
- Main.Context.API.GetTranslation("flowlauncher_plugin_everything_is_not_running"),
+ Localize.flowlauncher_plugin_everything_click_to_launch_or_install(),
+ Localize.flowlauncher_plugin_everything_is_not_running(),
Constants.EverythingErrorImagePath,
ClickToInstallEverythingAsync);
}
@@ -38,7 +38,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
"Please check whether your system is x86 or x64",
Constants.GeneralSearchErrorImagePath,
- Main.Context.API.GetTranslation("flowlauncher_plugin_everything_sdk_issue"));
+ Localize.flowlauncher_plugin_everything_sdk_issue());
}
}
@@ -50,7 +50,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
if (installedPath == null)
{
- Main.Context.API.ShowMsgError(Main.Context.API.GetTranslation("flowlauncher_plugin_everything_not_found"));
+ Main.Context.API.ShowMsgError(Localize.flowlauncher_plugin_everything_not_found());
Main.Context.API.LogError(ClassName, "Unable to find Everything.exe");
return false;
@@ -65,7 +65,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
// Just let the user know that Everything is not installed properly and ask them to install it manually
catch (Exception e)
{
- Main.Context.API.ShowMsgError(Main.Context.API.GetTranslation("flowlauncher_plugin_everything_install_issue"));
+ Main.Context.API.ShowMsgError(Localize.flowlauncher_plugin_everything_install_issue());
Main.Context.API.LogException(ClassName, "Failed to install Everything", e);
return false;
@@ -97,8 +97,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
if (!Settings.EnableEverythingContentSearch)
{
throw new EngineNotAvailableException(Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
- Main.Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search"),
- Main.Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search_tips"),
+ Localize.flowlauncher_plugin_everything_enable_content_search(),
+ Localize.flowlauncher_plugin_everything_enable_content_search_tips(),
Constants.EverythingErrorImagePath,
_ =>
{
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
index dfa2c8d43..18eb168b9 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -124,7 +124,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
}
catch (Exception ex)
{
- Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
+ Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_opendir_error());
return false;
}
}
@@ -138,7 +138,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
}
catch (Exception ex)
{
- Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
+ Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_opendir_error());
return false;
}
}
@@ -153,7 +153,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
}
catch (Exception ex)
{
- Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
+ Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_opendir_error());
return false;
}
}
@@ -166,7 +166,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
return false;
},
Score = score,
- TitleToolTip = Main.Context.API.GetTranslation("plugin_explorer_plugin_ToolTipOpenDirectory"),
+ TitleToolTip = Localize.plugin_explorer_plugin_ToolTipOpenDirectory(),
SubTitleToolTip = Settings.DisplayMoreInformationInToolTip ? GetFolderMoreInfoTooltip(path) : path,
ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path, WindowsIndexed = windowsIndexed }
};
@@ -190,7 +190,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
DriveInfo drv = new DriveInfo(driveLetter);
var freespace = ToReadableSize(drv.AvailableFreeSpace, 2);
var totalspace = ToReadableSize(drv.TotalSize, 2);
- var subtitle = string.Format(Context.API.GetTranslation("plugin_explorer_diskfreespace"), freespace, totalspace);
+ var subtitle = Localize.plugin_explorer_diskfreespace(freespace, totalspace);
double usingSize = (Convert.ToDouble(drv.TotalSize) - Convert.ToDouble(drv.AvailableFreeSpace)) / Convert.ToDouble(drv.TotalSize) * 100;
int? progressValue = Convert.ToInt32(usingSize);
@@ -262,8 +262,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
return new Result
{
- Title = Context.API.GetTranslation("plugin_explorer_openresultfolder"),
- SubTitle = Context.API.GetTranslation("plugin_explorer_openresultfolder_subtitle"),
+ Title = Localize.plugin_explorer_openresultfolder(),
+ SubTitle = Localize.plugin_explorer_openresultfolder_subtitle(),
AutoCompleteText = GetPathWithActionKeyword(folderPath, ResultType.Folder, actionKeyword),
IcoPath = folderPath,
Score = 500,
@@ -330,12 +330,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search
}
catch (Exception ex)
{
- Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_openfile_error"));
+ Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_openfile_error());
}
return true;
},
- TitleToolTip = Main.Context.API.GetTranslation("plugin_explorer_plugin_ToolTipOpenContainingFolder"),
+ TitleToolTip = Localize.plugin_explorer_plugin_ToolTipOpenContainingFolder(),
SubTitleToolTip = Settings.DisplayMoreInformationInToolTip ? GetFileMoreInfoTooltip(filePath) : filePath,
ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath, WindowsIndexed = windowsIndexed }
};
@@ -374,8 +374,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
var fileSize = PreviewPanel.GetFileSize(filePath);
var fileCreatedAt = PreviewPanel.GetFileCreatedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
var fileModifiedAt = PreviewPanel.GetFileLastModifiedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
- return string.Format(Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info"),
- filePath, fileSize, fileCreatedAt, fileModifiedAt, Environment.NewLine);
+ return Localize.plugin_explorer_plugin_tooltip_more_info(filePath, fileSize, fileCreatedAt, fileModifiedAt, Environment.NewLine);
}
catch (Exception e)
{
@@ -391,8 +390,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
var folderSize = PreviewPanel.GetFolderSize(folderPath);
var folderCreatedAt = PreviewPanel.GetFolderCreatedAt(folderPath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
var folderModifiedAt = PreviewPanel.GetFolderLastModifiedAt(folderPath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
- return string.Format(Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info"),
- folderPath, folderSize, folderCreatedAt, folderModifiedAt, Environment.NewLine);
+ return Localize.plugin_explorer_plugin_tooltip_more_info(folderPath, folderSize, folderCreatedAt, folderModifiedAt, Environment.NewLine);
}
catch (Exception e)
{
@@ -403,8 +401,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
private static string GetVolumeMoreInfoTooltip(string volumePath, string freespace, string totalspace)
{
- return string.Format(Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_volume"),
- volumePath, freespace, totalspace, Environment.NewLine);
+ return Localize.plugin_explorer_plugin_tooltip_more_info_volume(volumePath, freespace, totalspace, Environment.NewLine);
}
private static readonly string[] MediaExtensions =
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs
index f4f87d4d4..f9d8963e6 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs
@@ -161,8 +161,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{
new()
{
- Title = Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search"),
- SubTitle = Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search_tips"),
+ Title = Localize.flowlauncher_plugin_everything_enable_content_search(),
+ SubTitle = Localize.flowlauncher_plugin_everything_enable_content_search_tips(),
IcoPath = "Images/index_error.png",
Action = c =>
{
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs
index 3d69a1ee6..eeb5c2c4a 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs
@@ -105,8 +105,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
throw new EngineNotAvailableException(
"Windows Index",
- Main.Context.API.GetTranslation("plugin_explorer_windowsSearchServiceFix"),
- Main.Context.API.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"),
+ Localize.plugin_explorer_windowsSearchServiceFix(),
+ Localize.plugin_explorer_windowsSearchServiceNotRunning(),
Constants.WindowsIndexErrorImagePath,
c =>
{
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs
index 672e81d03..8d62531cd 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs
@@ -14,9 +14,9 @@ namespace Flow.Launcher.Plugin.Explorer
{
public int MaxResult { get; set; } = 100;
- public ObservableCollection QuickAccessLinks { get; set; } = new();
+ public ObservableCollection QuickAccessLinks { get; set; } = [];
- public ObservableCollection IndexSearchExcludedSubdirectoryPaths { get; set; } = new ObservableCollection();
+ public ObservableCollection IndexSearchExcludedSubdirectoryPaths { get; set; } = [];
public string EditorPath { get; set; } = "";
@@ -58,7 +58,6 @@ namespace Flow.Launcher.Plugin.Explorer
public bool QuickAccessKeywordEnabled { get; set; }
-
public bool WarnWindowsSearchServiceOff { get; set; } = true;
public bool ShowFileSizeInPreviewPanel { get; set; } = true;
@@ -69,7 +68,6 @@ namespace Flow.Launcher.Plugin.Explorer
public bool ShowFileAgeInPreviewPanel { get; set; } = false;
-
public string PreviewPanelDateFormat { get; set; } = "yyyy-MM-dd";
public string PreviewPanelTimeFormat { get; set; } = "HH:mm";
@@ -82,8 +80,8 @@ namespace Flow.Launcher.Plugin.Explorer
private EverythingSearchManager EverythingManagerInstance => _everythingManagerInstance ??= new EverythingSearchManager(this);
private WindowsIndexSearchManager WindowsIndexSearchManager => _windowsIndexSearchManager ??= new WindowsIndexSearchManager(this);
-
public IndexSearchEngineOption IndexSearchEngine { get; set; } = IndexSearchEngineOption.WindowsIndex;
+
[JsonIgnore]
public IIndexProvider IndexProvider => IndexSearchEngine switch
{
@@ -139,7 +137,6 @@ namespace Flow.Launcher.Plugin.Explorer
#endregion
-
#region Everything Settings
public string EverythingInstalledPath { get; set; }
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs
index ae2235c5c..2d46c6307 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs
@@ -296,7 +296,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
return;
}
- var actionKeywordWindow = new ActionKeywordSetting(actionKeyword, Context.API);
+ var actionKeywordWindow = new ActionKeywordSetting(actionKeyword);
if (!(actionKeywordWindow.ShowDialog() ?? false))
{
@@ -432,8 +432,8 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
case "QuickAccessLink":
if (SelectedQuickAccessLink == null) return;
if (Context.API.ShowMsgBox(
- Context.API.GetTranslation("plugin_explorer_delete_quick_access_link"),
- Context.API.GetTranslation("plugin_explorer_delete"),
+ Localize.plugin_explorer_delete_quick_access_link(),
+ Localize.plugin_explorer_delete(),
MessageBoxButton.OKCancel,
MessageBoxImage.Warning)
== MessageBoxResult.Cancel)
@@ -443,8 +443,8 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
case "IndexSearchExcludedPaths":
if (SelectedIndexSearchExcludedPath == null) return;
if (Context.API.ShowMsgBox(
- Context.API.GetTranslation("plugin_explorer_delete_index_search_excluded_path"),
- Context.API.GetTranslation("plugin_explorer_delete"),
+ Localize.plugin_explorer_delete_index_search_excluded_path(),
+ Localize.plugin_explorer_delete(),
MessageBoxButton.OKCancel,
MessageBoxImage.Warning)
== MessageBoxResult.Cancel)
@@ -457,7 +457,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
private void ShowUnselectedMessage()
{
- var warning = Context.API.GetTranslation("plugin_explorer_make_selection_warning");
+ var warning = Localize.plugin_explorer_make_selection_warning();
Context.API.ShowMsgBox(warning);
}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs
index 829a2feed..562170062 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs
@@ -29,13 +29,11 @@ namespace Flow.Launcher.Plugin.Explorer.Views
}
private string actionKeyword;
- private readonly IPublicAPI _api;
private bool _keywordEnabled;
- public ActionKeywordSetting(ActionKeywordModel selectedActionKeyword, IPublicAPI api)
+ public ActionKeywordSetting(ActionKeywordModel selectedActionKeyword)
{
CurrentActionKeyword = selectedActionKeyword;
- _api = api;
ActionKeyword = selectedActionKeyword.Keyword;
KeywordEnabled = selectedActionKeyword.Enabled;
@@ -60,14 +58,14 @@ namespace Flow.Launcher.Plugin.Explorer.Views
switch (CurrentActionKeyword.KeywordProperty, KeywordEnabled)
{
case (Settings.ActionKeyword.FileContentSearchActionKeyword, true):
- _api.ShowMsgBox(_api.GetTranslation("plugin_explorer_globalActionKeywordInvalid"));
+ Main.Context.API.ShowMsgBox(Localize.plugin_explorer_globalActionKeywordInvalid());
return;
case (Settings.ActionKeyword.QuickAccessActionKeyword, true):
- _api.ShowMsgBox(_api.GetTranslation("plugin_explorer_quickaccess_globalActionKeywordInvalid"));
+ Main.Context.API.ShowMsgBox(Localize.plugin_explorer_quickaccess_globalActionKeywordInvalid());
return;
}
- if (!KeywordEnabled || !_api.ActionKeywordAssigned(ActionKeyword))
+ if (!KeywordEnabled || !Main.Context.API.ActionKeywordAssigned(ActionKeyword))
{
DialogResult = true;
Close();
@@ -75,7 +73,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views
}
// The keyword is not valid, so show message
- _api.ShowMsgBox(_api.GetTranslation("newActionKeywordsHasBeenAssigned"));
+ Main.Context.API.ShowMsgBox(Localize.plugin_explorer_new_action_keyword_assigned());
}
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs
index 4dd0588ee..3c627cc06 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs
@@ -25,7 +25,7 @@ public partial class PreviewPanel : UserControl
public string FileName { get; }
[ObservableProperty]
- private string _fileSize = Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ private string _fileSize = Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
[ObservableProperty]
private string _createdAt = "";
@@ -111,17 +111,17 @@ public partial class PreviewPanel : UserControl
catch (FileNotFoundException)
{
Main.Context.API.LogError(ClassName, $"File not found: {filePath}");
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (UnauthorizedAccessException)
{
Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}");
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (Exception e)
{
Main.Context.API.LogException(ClassName, $"Failed to get file size for {filePath}", e);
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
}
@@ -142,17 +142,17 @@ public partial class PreviewPanel : UserControl
catch (FileNotFoundException)
{
Main.Context.API.LogError(ClassName, $"File not found: {filePath}");
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (UnauthorizedAccessException)
{
Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}");
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (Exception e)
{
Main.Context.API.LogException(ClassName, $"Failed to get file created date for {filePath}", e);
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
}
@@ -173,17 +173,17 @@ public partial class PreviewPanel : UserControl
catch (FileNotFoundException)
{
Main.Context.API.LogError(ClassName, $"File not found: {filePath}");
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (UnauthorizedAccessException)
{
Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}");
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (Exception e)
{
Main.Context.API.LogException(ClassName, $"Failed to get file modified date for {filePath}", e);
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
}
@@ -205,17 +205,17 @@ public partial class PreviewPanel : UserControl
catch (FileNotFoundException)
{
Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (UnauthorizedAccessException)
{
Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (OperationCanceledException)
{
Main.Context.API.LogError(ClassName, $"Operation timed out while calculating folder size for {folderPath}");
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
// For parallel operations, AggregateException may be thrown if any of the tasks fail
catch (AggregateException ae)
@@ -224,22 +224,22 @@ public partial class PreviewPanel : UserControl
{
case FileNotFoundException:
Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
case UnauthorizedAccessException:
Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
case OperationCanceledException:
Main.Context.API.LogError(ClassName, $"Operation timed out while calculating folder size for {folderPath}");
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
default:
Main.Context.API.LogException(ClassName, $"Failed to get folder size for {folderPath}", ae);
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
}
catch (Exception e)
{
Main.Context.API.LogException(ClassName, $"Failed to get folder size for {folderPath}", e);
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
}
@@ -260,17 +260,17 @@ public partial class PreviewPanel : UserControl
catch (FileNotFoundException)
{
Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (UnauthorizedAccessException)
{
Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (Exception e)
{
Main.Context.API.LogException(ClassName, $"Failed to get folder created date for {folderPath}", e);
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
}
@@ -291,17 +291,17 @@ public partial class PreviewPanel : UserControl
catch (FileNotFoundException)
{
Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (UnauthorizedAccessException)
{
Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
catch (Exception e)
{
Main.Context.API.LogException(ClassName, $"Failed to get folder modified date for {folderPath}", e);
- return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
}
}
@@ -311,21 +311,20 @@ public partial class PreviewPanel : UserControl
var difference = now - fileDateTime;
if (difference.TotalDays < 1)
- return Main.Context.API.GetTranslation("Today");
+ return Localize.Today();
if (difference.TotalDays < 30)
- return string.Format(Main.Context.API.GetTranslation("DaysAgo"), (int)difference.TotalDays);
+ return Localize.DaysAgo((int)difference.TotalDays);
var monthsDiff = (now.Year - fileDateTime.Year) * 12 + now.Month - fileDateTime.Month;
if (monthsDiff == 1)
- return Main.Context.API.GetTranslation("OneMonthAgo");
+ return Localize.OneMonthAgo();
if (monthsDiff < 12)
- return string.Format(Main.Context.API.GetTranslation("MonthsAgo"), monthsDiff);
+ return Localize.MonthsAgo(monthsDiff);
var yearsDiff = now.Year - fileDateTime.Year;
if (now.Month < fileDateTime.Month || (now.Month == fileDateTime.Month && now.Day < fileDateTime.Day))
yearsDiff--;
- return yearsDiff == 1 ? Main.Context.API.GetTranslation("OneYearAgo") :
- string.Format(Main.Context.API.GetTranslation("YearsAgo"), yearsDiff);
+ return yearsDiff == 1 ? Localize.OneYearAgo(): Localize.YearsAgo(yearsDiff);
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml.cs
index e6294b98b..f8929549b 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml.cs
@@ -97,7 +97,7 @@ public partial class QuickAccessLinkSettings
// Validate the input before proceeding
if (string.IsNullOrEmpty(SelectedName) || string.IsNullOrEmpty(SelectedPath))
{
- var warning = Main.Context.API.GetTranslation("plugin_explorer_quick_access_link_no_folder_selected");
+ var warning = Localize.plugin_explorer_quick_access_link_no_folder_selected();
Main.Context.API.ShowMsgBox(warning);
return;
}
@@ -107,7 +107,7 @@ public partial class QuickAccessLinkSettings
x.Path.Equals(SelectedPath, StringComparison.OrdinalIgnoreCase) &&
x.Name.Equals(SelectedName, StringComparison.OrdinalIgnoreCase)))
{
- var warning = Main.Context.API.GetTranslation("plugin_explorer_quick_access_link_path_already_exists");
+ var warning = Localize.plugin_explorer_quick_access_link_path_already_exists();
Main.Context.API.ShowMsgBox(warning);
return;
}