Skip to content

Commit e107939

Browse files
committed
backout 'smart' digit grouping, mages fixes + workaround
Mages did not like the previous change to smart thousands / decimal so backed that out. Workaround for FlorianRappl/Mages#132
1 parent b898c46 commit e107939

File tree

2 files changed

+65
-105
lines changed

2 files changed

+65
-105
lines changed

Plugins/Flow.Launcher.Plugin.Calculator/Main.cs

Lines changed: 64 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ namespace Flow.Launcher.Plugin.Calculator
1313
{
1414
public class Main : IPlugin, IPluginI18n, ISettingProvider
1515
{
16-
private static readonly Regex RegValidExpressChar = MainRegexHelper.GetRegValidExpressChar();
1716
private static readonly Regex RegBrackets = MainRegexHelper.GetRegBrackets();
1817
private static readonly Regex ThousandGroupRegex = MainRegexHelper.GetThousandGroupRegex();
1918
private static readonly Regex NumberRegex = MainRegexHelper.GetNumberRegex();
@@ -27,16 +26,6 @@ public class Main : IPlugin, IPluginI18n, ISettingProvider
2726
private Settings _settings;
2827
private SettingsViewModel _viewModel;
2928

30-
/// <summary>
31-
/// Holds the formatting information for a single query.
32-
/// This is used to ensure thread safety by keeping query state local.
33-
/// </summary>
34-
private class ParsingContext
35-
{
36-
public string InputDecimalSeparator { get; set; }
37-
public bool InputUsesGroupSeparators { get; set; }
38-
}
39-
4029
public void Init(PluginInitContext context)
4130
{
4231
Context = context;
@@ -59,24 +48,46 @@ public List<Result> Query(Query query)
5948
return new List<Result>();
6049
}
6150

62-
var context = new ParsingContext();
63-
6451
try
6552
{
66-
var expression = NumberRegex.Replace(query.Search, m => NormalizeNumber(m.Value, context));
53+
var expression = NumberRegex.Replace(query.Search, m => NormalizeNumber(m.Value));
54+
55+
// WORKAROUND START: The 'pow' function in Mages v3.0.0 is broken.
56+
// https://github.com/FlorianRappl/Mages/issues/132
57+
// We bypass it by rewriting any pow(x,y) expression to the equivalent (x^y) expression
58+
// before the engine sees it. This loop handles nested calls.
59+
string previous;
60+
do
61+
{
62+
previous = expression;
63+
expression = Regex.Replace(previous, @"\bpow\s*\(\s*([^,]+?)\s*,\s*([^)]+?)\s*\)", "($1^$2)");
64+
} while (previous != expression);
65+
// WORKAROUND END
6766

6867
var result = MagesEngine.Interpret(expression);
6968

70-
if (result?.ToString() == "NaN")
69+
if (result == null || string.IsNullOrEmpty(result.ToString()))
70+
{
71+
return new List<Result>
72+
{
73+
new Result
74+
{
75+
Title = Localize.flowlauncher_plugin_calculator_expression_not_complete(),
76+
IcoPath = "Images/calculator.png"
77+
}
78+
};
79+
}
80+
81+
if (result.ToString() == "NaN")
7182
result = Localize.flowlauncher_plugin_calculator_not_a_number();
7283

7384
if (result is Function)
7485
result = Localize.flowlauncher_plugin_calculator_expression_not_complete();
7586

76-
if (!string.IsNullOrEmpty(result?.ToString()))
87+
if (!string.IsNullOrEmpty(result.ToString()))
7788
{
7889
decimal roundedResult = Math.Round(Convert.ToDecimal(result), _settings.MaxDecimalPlaces, MidpointRounding.AwayFromZero);
79-
string newResult = FormatResult(roundedResult, context);
90+
string newResult = FormatResult(roundedResult);
8091

8192
return new List<Result>
8293
{
@@ -104,115 +115,69 @@ public List<Result> Query(Query query)
104115
};
105116
}
106117
}
107-
catch (Exception)
118+
catch (Exception e)
108119
{
109-
// ignored
120+
return new List<Result>
121+
{
122+
new Result
123+
{
124+
Title = e.Message,
125+
SubTitle = "Calculator Exception",
126+
IcoPath = "Images/calculator.png",
127+
Score = 300
128+
}
129+
};
110130
}
111131

112132
return new List<Result>();
113133
}
114134

115135
/// <summary>
116-
/// Parses a string representation of a number, detecting its format. It uses structural analysis
117-
/// and falls back to system culture for truly ambiguous cases (e.g., "1,234").
118-
/// It populates the provided ParsingContext with the detected format for later use.
136+
/// Parses a string representation of a number using the system's current culture.
119137
/// </summary>
120138
/// <returns>A normalized number string with '.' as the decimal separator for the Mages engine.</returns>
121-
private string NormalizeNumber(string numberStr, ParsingContext context)
139+
private string NormalizeNumber(string numberStr)
122140
{
123-
var systemGroupSep = CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator;
124-
int dotCount = numberStr.Count(f => f == '.');
125-
int commaCount = numberStr.Count(f => f == ',');
126-
127-
// Case 1: Unambiguous mixed separators (e.g., "1.234,56")
128-
if (dotCount > 0 && commaCount > 0)
129-
{
130-
context.InputUsesGroupSeparators = true;
131-
if (numberStr.LastIndexOf('.') > numberStr.LastIndexOf(','))
132-
{
133-
context.InputDecimalSeparator = Dot;
134-
return numberStr.Replace(Comma, string.Empty);
135-
}
136-
else
137-
{
138-
context.InputDecimalSeparator = Comma;
139-
return numberStr.Replace(Dot, string.Empty).Replace(Comma, Dot);
140-
}
141-
}
141+
var culture = CultureInfo.CurrentCulture;
142+
var groupSep = culture.NumberFormat.NumberGroupSeparator;
142143

143-
// Case 2: Only dots
144-
if (dotCount > 0)
144+
// If the string contains the group separator, check if it's used correctly.
145+
if (!string.IsNullOrEmpty(groupSep) && numberStr.Contains(groupSep))
145146
{
146-
if (dotCount > 1)
147-
{
148-
context.InputUsesGroupSeparators = true;
149-
return numberStr.Replace(Dot, string.Empty);
150-
}
151-
// A number is ambiguous if it has a single Dot in the thousands position,
152-
// and does not start with a "0." or "."
153-
bool isAmbiguous = numberStr.Length - numberStr.LastIndexOf('.') == 4
154-
&& !numberStr.StartsWith("0.")
155-
&& !numberStr.StartsWith(".");
156-
if (isAmbiguous)
147+
var parts = numberStr.Split(groupSep);
148+
// If any part after the first (excluding a possible last part with a decimal)
149+
// does not have 3 digits, then it's not a valid use of a thousand separator.
150+
for (int i = 1; i < parts.Length; i++)
157151
{
158-
if (systemGroupSep == Dot)
152+
var part = parts[i];
153+
// The last part might contain a decimal separator.
154+
if (i == parts.Length - 1 && part.Contains(culture.NumberFormat.NumberDecimalSeparator))
159155
{
160-
context.InputUsesGroupSeparators = true;
161-
return numberStr.Replace(Dot, string.Empty);
156+
part = part.Split(culture.NumberFormat.NumberDecimalSeparator)[0];
162157
}
163-
else
158+
159+
if (part.Length != 3)
164160
{
165-
context.InputDecimalSeparator = Dot;
161+
// This is not a number with valid thousand separators,
162+
// so it must be arguments to a function. Return it unmodified.
166163
return numberStr;
167164
}
168165
}
169-
else // Unambiguous decimal (e.g., "12.34" or "0.123" or ".123")
170-
{
171-
context.InputDecimalSeparator = Dot;
172-
return numberStr;
173-
}
174166
}
175167

176-
// Case 3: Only commas
177-
if (commaCount > 0)
168+
// At this point, any group separators are in valid positions (or there are none).
169+
// We can safely parse with the user's culture.
170+
if (decimal.TryParse(numberStr, NumberStyles.Any, culture, out var number))
178171
{
179-
if (commaCount > 1)
180-
{
181-
context.InputUsesGroupSeparators = true;
182-
return numberStr.Replace(Comma, string.Empty);
183-
}
184-
// A number is ambiguous if it has a single Comma in the thousands position,
185-
// and does not start with a "0," or ","
186-
bool isAmbiguous = numberStr.Length - numberStr.LastIndexOf(',') == 4
187-
&& !numberStr.StartsWith("0,")
188-
&& !numberStr.StartsWith(",");
189-
if (isAmbiguous)
190-
{
191-
if (systemGroupSep == Comma)
192-
{
193-
context.InputUsesGroupSeparators = true;
194-
return numberStr.Replace(Comma, string.Empty);
195-
}
196-
else
197-
{
198-
context.InputDecimalSeparator = Comma;
199-
return numberStr.Replace(Comma, Dot);
200-
}
201-
}
202-
else // Unambiguous decimal (e.g., "12,34" or "0,123" or ",123")
203-
{
204-
context.InputDecimalSeparator = Comma;
205-
return numberStr.Replace(Comma, Dot);
206-
}
172+
return number.ToString(CultureInfo.InvariantCulture);
207173
}
208174

209-
// Case 4: No separators
210175
return numberStr;
211176
}
212177

213-
private string FormatResult(decimal roundedResult, ParsingContext context)
178+
private string FormatResult(decimal roundedResult)
214179
{
215-
string decimalSeparator = context.InputDecimalSeparator ?? GetDecimalSeparator();
180+
string decimalSeparator = GetDecimalSeparator();
216181
string groupSeparator = GetGroupSeparator(decimalSeparator);
217182

218183
string resultStr = roundedResult.ToString(CultureInfo.InvariantCulture);
@@ -221,7 +186,7 @@ private string FormatResult(decimal roundedResult, ParsingContext context)
221186
string integerPart = parts[0];
222187
string fractionalPart = parts.Length > 1 ? parts[1] : string.Empty;
223188

224-
if (context.InputUsesGroupSeparators && integerPart.Length > 3)
189+
if (integerPart.Length > 3)
225190
{
226191
integerPart = ThousandGroupRegex.Replace(integerPart, groupSeparator);
227192
}
@@ -248,11 +213,6 @@ private bool CanCalculate(Query query)
248213
return false;
249214
}
250215

251-
if (!RegValidExpressChar.IsMatch(query.Search))
252-
{
253-
return false;
254-
}
255-
256216
if (!IsBracketComplete(query.Search))
257217
{
258218
return false;

Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ internal static partial class MainRegexHelper
1111
[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)]
1212
public static partial Regex GetRegValidExpressChar();
1313

14-
[GeneratedRegex(@"[\d\.,]+", RegexOptions.Compiled)]
14+
[GeneratedRegex(@"-?[\d\.,]+", RegexOptions.Compiled)]
1515
public static partial Regex GetNumberRegex();
1616

1717
[GeneratedRegex(@"\B(?=(\d{3})+(?!\d))", RegexOptions.Compiled)]

0 commit comments

Comments
 (0)