11using System ;
22using System . Collections . Generic ;
33using System . Globalization ;
4+ using System . Linq ;
45using System . Runtime . InteropServices ;
56using System . Text . RegularExpressions ;
67using System . Windows . Controls ;
@@ -12,10 +13,10 @@ namespace Flow.Launcher.Plugin.Calculator
1213{
1314 public class Main : IPlugin , IPluginI18n , ISettingProvider
1415 {
15- private static readonly Regex RegBrackets = MainRegexHelper . GetRegBrackets ( ) ;
1616 private static readonly Regex ThousandGroupRegex = MainRegexHelper . GetThousandGroupRegex ( ) ;
1717 private static readonly Regex NumberRegex = MainRegexHelper . GetNumberRegex ( ) ;
1818 private static readonly Regex PowRegex = MainRegexHelper . GetPowRegex ( ) ;
19+ private static readonly Regex FunctionRegex = MainRegexHelper . GetFunctionRegex ( ) ;
1920
2021 private static Engine MagesEngine ;
2122 private const string Comma = "," ;
@@ -51,7 +52,8 @@ public List<Result> Query(Query query)
5152
5253 try
5354 {
54- var expression = NumberRegex . Replace ( query . Search , m => NormalizeNumber ( m . Value ) ) ;
55+ bool isFunctionPresent = FunctionRegex . IsMatch ( query . Search ) ;
56+ var expression = NumberRegex . Replace ( query . Search , m => NormalizeNumber ( m . Value , isFunctionPresent ) ) ;
5557
5658 // WORKAROUND START: The 'pow' function in Mages v3.0.0 is broken.
5759 // https://github.com/FlorianRappl/Mages/issues/132
@@ -183,47 +185,104 @@ private static string PowMatchEvaluator(Match m)
183185 return $ "({ arg1 } ^{ arg2 } )";
184186 }
185187
186- /// <summary>
187- /// Parses a string representation of a number using the system's current culture.
188- /// </summary>
189- /// <returns>A normalized number string with '.' as the decimal separator for the Mages engine.</returns>
190- private static string NormalizeNumber ( string numberStr )
188+ private static string NormalizeNumber ( string numberStr , bool isFunctionPresent )
191189 {
192190 var culture = CultureInfo . CurrentCulture ;
193191 var groupSep = culture . NumberFormat . NumberGroupSeparator ;
194192 var decimalSep = culture . NumberFormat . NumberDecimalSeparator ;
195193
196- // If the string contains the group separator, check if it's used correctly.
197- if ( ! string . IsNullOrEmpty ( groupSep ) && numberStr . Contains ( groupSep ) )
194+ if ( isFunctionPresent )
198195 {
199- var parts = numberStr . Split ( groupSep ) ;
200- // If any part after the first (excluding a possible last part with a decimal)
201- // does not have 3 digits, then it's not a valid use of a thousand separator.
202- for ( int i = 1 ; i < parts . Length ; i ++ )
196+ // STRICT MODE: When functions are present, ',' is ALWAYS an argument separator.
197+ // It must not be normalized.
198+ if ( numberStr . Contains ( ',' ) )
203199 {
204- var part = parts [ i ] ;
205- // The last part might contain a decimal separator.
206- if ( i == parts . Length - 1 && part . Contains ( culture . NumberFormat . NumberDecimalSeparator ) )
207- {
208- part = part . Split ( culture . NumberFormat . NumberDecimalSeparator ) [ 0 ] ;
209- }
200+ return numberStr ;
201+ }
210202
211- if ( part . Length != 3 )
203+ // The string has no commas. It could have a '.' group separator (e.g. in de-DE)
204+ // or a '.' decimal separator (e.g. in en-US).
205+ // Since Mages' decimal separator is '.', we only need to strip the group separator.
206+ if ( groupSep == "." )
207+ {
208+ var parts = numberStr . Split ( '.' ) ;
209+ // A number with a dot group separator, e.g., "1.234"
210+ if ( parts . Length > 1 )
212211 {
213- // This is not a number with valid thousand separators,
214- // so it must be arguments to a function. Return it unmodified.
215- return numberStr ;
212+ // Check if the parts after the first dot have the correct group length (usually 3).
213+ for ( int i = 1 ; i < parts . Length ; i ++ )
214+ {
215+ if ( parts [ i ] . Length != 3 )
216+ {
217+ // Malformed grouping, e.g., "1.23". This is likely a decimal number.
218+ // Return as is and let Mages handle it.
219+ return numberStr ;
220+ }
221+ }
222+ // Correct grouping, e.g., "1.234" or "1.234.567". Strip separators.
223+ return numberStr . Replace ( "." , "" ) ;
216224 }
217225 }
226+
227+ // For any other case (e.g. en-US culture where group sep is ',' which was already handled),
228+ // return the string as is.
229+ return numberStr ;
218230 }
231+ else
232+ {
233+ // LENIENT MODE: No functions are present, so we can be flexible.
234+ string processedStr = numberStr ;
235+ if ( ! string . IsNullOrEmpty ( groupSep ) )
236+ {
237+ processedStr = processedStr . Replace ( groupSep , "" ) ;
238+ }
239+ processedStr = processedStr . Replace ( decimalSep , "." ) ;
240+ return processedStr ;
241+ }
242+ }
243+
244+ private static bool IsValidGrouping ( string [ ] parts , int [ ] groupSizes )
245+ {
246+ if ( parts . Length <= 1 ) return true ;
247+
248+ if ( groupSizes is null || groupSizes . Length == 0 || groupSizes [ 0 ] == 0 )
249+ return false ; // has groups, but culture defines none.
250+
251+ var firstPart = parts [ 0 ] ;
252+ if ( firstPart . StartsWith ( "-" ) ) firstPart = firstPart . Substring ( 1 ) ;
253+ if ( firstPart . Length == 0 ) return false ; // e.g. ",123"
219254
220- // If validation passes, we can assume the separators are used correctly for numbers.
221- string processedStr = numberStr . Replace ( groupSep , "" ) ;
222- processedStr = processedStr . Replace ( decimalSep , "." ) ;
255+ if ( firstPart . Length > groupSizes [ 0 ] ) return false ;
223256
224- return processedStr ;
257+ var lastGroupSize = groupSizes . Last ( ) ;
258+ var canRepeatLastGroup = lastGroupSize != 0 ;
259+
260+ int groupIndex = 0 ;
261+ for ( int i = parts . Length - 1 ; i > 0 ; i -- )
262+ {
263+ int expectedSize ;
264+ if ( groupIndex < groupSizes . Length )
265+ {
266+ expectedSize = groupSizes [ groupIndex ] ;
267+ }
268+ else if ( canRepeatLastGroup )
269+ {
270+ expectedSize = lastGroupSize ;
271+ }
272+ else
273+ {
274+ return false ;
275+ }
276+
277+ if ( parts [ i ] . Length != expectedSize ) return false ;
278+
279+ groupIndex ++ ;
280+ }
281+
282+ return true ;
225283 }
226284
285+
227286 private string FormatResult ( decimal roundedResult )
228287 {
229288 string decimalSeparator = GetDecimalSeparator ( ) ;
@@ -250,14 +309,23 @@ private string FormatResult(decimal roundedResult)
250309
251310 private string GetGroupSeparator ( string decimalSeparator )
252311 {
312+ var culture = CultureInfo . CurrentCulture ;
313+ var systemGroupSeparator = culture . NumberFormat . NumberGroupSeparator ;
314+
253315 if ( _settings . DecimalSeparator == DecimalSeparator . UseSystemLocale )
254316 {
255- return CultureInfo . CurrentCulture . NumberFormat . NumberGroupSeparator ;
317+ return systemGroupSeparator ;
318+ }
319+
320+ // When a custom decimal separator is used,
321+ // use the system's group separator unless it conflicts with the custom decimal separator.
322+ if ( decimalSeparator == systemGroupSeparator )
323+ {
324+ // Conflict: use the opposite of the decimal separator as a fallback.
325+ return decimalSeparator == Dot ? Comma : Dot ;
256326 }
257327
258- // This logic is now independent of the system's group separator
259- // to ensure consistent output when a specific separator is chosen.
260- return decimalSeparator == Dot ? Comma : Dot ;
328+ return systemGroupSeparator ;
261329 }
262330
263331 private string GetDecimalSeparator ( )
0 commit comments