@@ -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 ;
0 commit comments