mirror of
https://github.com/microsoft/PowerToys
synced 2025-08-22 10:07:37 +00:00
[cmdpal] Port v1 calculator extension (#38629)
* init * update * Remove duplicated cp command * Change the long desc * Update notice.md * Use the same icon for fallback item * Add Rappl to expect list * update notice.md * Move the original order back. * Make Radians become default choice * Fix empty result * Remove unused settings. Move history back. Refactory the query logic * fix typo * merge main * CmdPal: minor calc updates (#38914) A bunch of calc updates * maintain the visibility of the history * add other formats to the context menu #38708 * some other icon tidying --------- Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com>
This commit is contained in:
parent
4f9e829155
commit
6cf73ce839
1
.github/actions/spell-check/expect.txt
vendored
1
.github/actions/spell-check/expect.txt
vendored
@ -1299,6 +1299,7 @@ QUNS
|
||||
QXZ
|
||||
RAII
|
||||
RAlt
|
||||
Rappl
|
||||
randi
|
||||
Rasterization
|
||||
Rasterize
|
||||
|
34
NOTICE.md
34
NOTICE.md
@ -75,6 +75,40 @@ OTHER DEALINGS IN THE SOFTWARE.
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
```
|
||||
|
||||
## Utility: Command Palette Built-in Extensions
|
||||
|
||||
### Calculator
|
||||
|
||||
#### Mages
|
||||
|
||||
We use the Mages NuGet package for calculating the result of expression.
|
||||
|
||||
**Source**: [https://github.com/FlorianRappl/Mages](https://github.com/FlorianRappl/Mages)
|
||||
|
||||
```
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 - 2025 Florian Rappl
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
|
||||
## Utility: File Explorer Add-ins
|
||||
|
||||
### Monaco Editor
|
||||
|
@ -2,176 +2,34 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
using Microsoft.CmdPal.Ext.Calc.Pages;
|
||||
using Microsoft.CmdPal.Ext.Calc.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc;
|
||||
|
||||
public partial class CalculatorCommandProvider : CommandProvider
|
||||
{
|
||||
private readonly ListItem _listItem = new(new CalculatorListPage()) { Subtitle = Resources.calculator_top_level_subtitle };
|
||||
private readonly FallbackCalculatorItem _fallback = new();
|
||||
private readonly ListItem _listItem = new(new CalculatorListPage(settings))
|
||||
{
|
||||
Subtitle = Resources.calculator_top_level_subtitle,
|
||||
MoreCommands = [new CommandContextItem(settings.Settings.SettingsPage)],
|
||||
};
|
||||
|
||||
private readonly FallbackCalculatorItem _fallback = new(settings);
|
||||
private static SettingsManager settings = new();
|
||||
|
||||
public CalculatorCommandProvider()
|
||||
{
|
||||
Id = "Calculator";
|
||||
DisplayName = Resources.calculator_display_name;
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\Calculator.svg");
|
||||
Icon = CalculatorIcons.ProviderIcon;
|
||||
Settings = settings.Settings;
|
||||
}
|
||||
|
||||
public override ICommandItem[] TopLevelCommands() => [_listItem];
|
||||
|
||||
public override IFallbackCommandItem[] FallbackCommands() => [_fallback];
|
||||
}
|
||||
|
||||
// The calculator page is a dynamic list page
|
||||
// * The first command is where we display the results. Title=result, Subtitle=query
|
||||
// - The default command is `SaveCommand`.
|
||||
// - When you save, insert into list at spot 1
|
||||
// - change SearchText to the result
|
||||
// - MoreCommands: a single `CopyCommand` to copy the result to the clipboard
|
||||
// * The rest of the items are previously saved results
|
||||
// - Command is a CopyCommand
|
||||
// - Each item also sets the TextToSuggest to the result
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "This is sample code")]
|
||||
public sealed partial class CalculatorListPage : DynamicListPage
|
||||
{
|
||||
private readonly List<ListItem> _items = [];
|
||||
private readonly SaveCommand _saveCommand = new();
|
||||
private readonly CopyTextCommand _copyContextCommand;
|
||||
private readonly CommandContextItem _copyContextMenuItem;
|
||||
private static readonly CompositeFormat ErrorMessage = System.Text.CompositeFormat.Parse(Properties.Resources.calculator_error);
|
||||
|
||||
public CalculatorListPage()
|
||||
{
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\Calculator.svg");
|
||||
Name = Resources.calculator_title;
|
||||
PlaceholderText = Resources.calculator_placeholder_text;
|
||||
Id = "com.microsoft.cmdpal.calculator";
|
||||
|
||||
_copyContextCommand = new CopyTextCommand(string.Empty);
|
||||
_copyContextMenuItem = new CommandContextItem(_copyContextCommand);
|
||||
|
||||
_items.Add(new(_saveCommand) { Icon = new IconInfo("\uE94E") });
|
||||
|
||||
UpdateSearchText(string.Empty, string.Empty);
|
||||
|
||||
_saveCommand.SaveRequested += HandleSave;
|
||||
}
|
||||
|
||||
private void HandleSave(object sender, object args)
|
||||
{
|
||||
var lastResult = _items[0].Title;
|
||||
if (!string.IsNullOrEmpty(lastResult))
|
||||
{
|
||||
var li = new ListItem(new CopyTextCommand(lastResult))
|
||||
{
|
||||
Title = _items[0].Title,
|
||||
Subtitle = _items[0].Subtitle,
|
||||
TextToSuggest = lastResult,
|
||||
};
|
||||
_items.Insert(1, li);
|
||||
_items[0].Subtitle = string.Empty;
|
||||
SearchText = lastResult;
|
||||
this.RaiseItemsChanged(this._items.Count);
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateSearchText(string oldSearch, string newSearch)
|
||||
{
|
||||
var firstItem = _items[0];
|
||||
if (string.IsNullOrEmpty(newSearch))
|
||||
{
|
||||
firstItem.Title = Resources.calculator_placeholder_text;
|
||||
firstItem.Subtitle = string.Empty;
|
||||
firstItem.MoreCommands = [];
|
||||
}
|
||||
else
|
||||
{
|
||||
_copyContextCommand.Text = ParseQuery(newSearch, out var result) ? result : string.Empty;
|
||||
firstItem.Title = result;
|
||||
firstItem.Subtitle = newSearch;
|
||||
firstItem.MoreCommands = [_copyContextMenuItem];
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool ParseQuery(string equation, out string result)
|
||||
{
|
||||
try
|
||||
{
|
||||
var resultNumber = new DataTable().Compute(equation, null);
|
||||
result = resultNumber.ToString() ?? string.Empty;
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
result = string.Format(CultureInfo.CurrentCulture, ErrorMessage, e.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems() => _items.ToArray();
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "This is sample code")]
|
||||
public sealed partial class SaveCommand : InvokableCommand
|
||||
{
|
||||
public event TypedEventHandler<object, object> SaveRequested;
|
||||
|
||||
public SaveCommand()
|
||||
{
|
||||
Name = Resources.calculator_save_command_name;
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
SaveRequested?.Invoke(this, this);
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "This is sample code")]
|
||||
internal sealed partial class FallbackCalculatorItem : FallbackCommandItem
|
||||
{
|
||||
private readonly CopyTextCommand _copyCommand = new(string.Empty);
|
||||
private static readonly IconInfo _cachedIcon = IconHelpers.FromRelativePath("Assets\\Calculator.svg");
|
||||
|
||||
public FallbackCalculatorItem()
|
||||
: base(new NoOpCommand(), Resources.calculator_title)
|
||||
{
|
||||
Command = _copyCommand;
|
||||
_copyCommand.Name = string.Empty;
|
||||
Title = string.Empty;
|
||||
Subtitle = Resources.calculator_placeholder_text;
|
||||
Icon = _cachedIcon;
|
||||
}
|
||||
|
||||
public override void UpdateQuery(string query)
|
||||
{
|
||||
if (CalculatorListPage.ParseQuery(query, out var result))
|
||||
{
|
||||
_copyCommand.Text = result;
|
||||
_copyCommand.Name = string.IsNullOrWhiteSpace(query) ? string.Empty : Resources.calculator_copy_command_name;
|
||||
Title = result;
|
||||
|
||||
// we have to make the subtitle the equation,
|
||||
// so that we will still string match the original query
|
||||
// Otherwise, something like 1+2 will have a title of "3" and not match
|
||||
Subtitle = query;
|
||||
}
|
||||
else
|
||||
{
|
||||
_copyCommand.Text = string.Empty;
|
||||
_copyCommand.Name = string.Empty;
|
||||
Title = string.Empty;
|
||||
Subtitle = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
|
||||
public static class BracketHelper
|
||||
{
|
||||
public static bool IsBracketComplete(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var valueTuples = query
|
||||
.Select(BracketTrail)
|
||||
.Where(r => r != default);
|
||||
|
||||
var trailTest = new Stack<TrailType>();
|
||||
|
||||
foreach (var (direction, type) in valueTuples)
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case TrailDirection.Open:
|
||||
trailTest.Push(type);
|
||||
break;
|
||||
case TrailDirection.Close:
|
||||
// Try to get item out of stack
|
||||
if (!trailTest.TryPop(out var popped))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type != popped)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
default:
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Can't process value (Parameter direction: {direction})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return trailTest.Count == 0;
|
||||
}
|
||||
|
||||
private static (TrailDirection Direction, TrailType Type) BracketTrail(char @char)
|
||||
{
|
||||
switch (@char)
|
||||
{
|
||||
case '(':
|
||||
return (TrailDirection.Open, TrailType.Round);
|
||||
case ')':
|
||||
return (TrailDirection.Close, TrailType.Round);
|
||||
case '[':
|
||||
return (TrailDirection.Open, TrailType.Bracket);
|
||||
case ']':
|
||||
return (TrailDirection.Close, TrailType.Bracket);
|
||||
default:
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
private enum TrailDirection
|
||||
{
|
||||
None,
|
||||
Open,
|
||||
Close,
|
||||
}
|
||||
|
||||
private enum TrailType
|
||||
{
|
||||
None,
|
||||
Bracket,
|
||||
Round,
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Mages.Core;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
|
||||
public static class CalculateEngine
|
||||
{
|
||||
private static readonly Engine _magesEngine = new Engine(new Configuration
|
||||
{
|
||||
Scope = new Dictionary<string, object>
|
||||
{
|
||||
{ "e", Math.E }, // e is not contained in the default mages engine
|
||||
},
|
||||
});
|
||||
|
||||
public const int RoundingDigits = 10;
|
||||
|
||||
public enum TrigMode
|
||||
{
|
||||
Radians,
|
||||
Degrees,
|
||||
Gradians,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interpret
|
||||
/// </summary>
|
||||
/// <param name="cultureInfo">Use CultureInfo.CurrentCulture if something is user facing</param>
|
||||
public static CalculateResult Interpret(SettingsManager settings, string input, CultureInfo cultureInfo, out string error)
|
||||
{
|
||||
error = default;
|
||||
|
||||
if (!CalculateHelper.InputValid(input))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
// check for division by zero
|
||||
// We check if the string contains a slash followed by space (optional) and zero. Whereas the zero must not be followed by a dot, comma, 'b', 'o' or 'x' as these indicate a number with decimal digits or a binary/octal/hexadecimal value respectively. The zero must also not be followed by other digits.
|
||||
if (new Regex("\\/\\s*0(?!(?:[,\\.0-9]|[box]0*[1-9a-f]))", RegexOptions.IgnoreCase).Match(input).Success)
|
||||
{
|
||||
error = Properties.Resources.calculator_division_by_zero;
|
||||
return default;
|
||||
}
|
||||
|
||||
// mages has quirky log representation
|
||||
// mage has log == ln vs log10
|
||||
input = input.
|
||||
Replace("log(", "log10(", true, CultureInfo.CurrentCulture).
|
||||
Replace("ln(", "log(", true, CultureInfo.CurrentCulture);
|
||||
|
||||
input = CalculateHelper.FixHumanMultiplicationExpressions(input);
|
||||
|
||||
// Get the user selected trigonometry unit
|
||||
TrigMode trigMode = settings.TrigUnit;
|
||||
|
||||
// Modify trig functions depending on angle unit setting
|
||||
input = CalculateHelper.UpdateTrigFunctions(input, trigMode);
|
||||
|
||||
// Expand conversions between trig units
|
||||
input = CalculateHelper.ExpandTrigConversions(input, trigMode);
|
||||
|
||||
var result = _magesEngine.Interpret(input);
|
||||
|
||||
// This could happen for some incorrect queries, like pi(2)
|
||||
if (result == null)
|
||||
{
|
||||
error = Properties.Resources.calculator_expression_not_complete;
|
||||
return default;
|
||||
}
|
||||
|
||||
result = TransformResult(result);
|
||||
if (result is string)
|
||||
{
|
||||
error = result as string;
|
||||
return default;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(result?.ToString()))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var decimalResult = Convert.ToDecimal(result, cultureInfo);
|
||||
var roundedResult = Round(decimalResult);
|
||||
|
||||
return new CalculateResult()
|
||||
{
|
||||
Result = decimalResult,
|
||||
RoundedResult = roundedResult,
|
||||
};
|
||||
}
|
||||
|
||||
public static decimal Round(decimal value)
|
||||
{
|
||||
return Math.Round(value, RoundingDigits, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
private static dynamic TransformResult(object result)
|
||||
{
|
||||
if (result.ToString() == "NaN")
|
||||
{
|
||||
return Properties.Resources.calculator_not_a_number;
|
||||
}
|
||||
|
||||
if (result is Function)
|
||||
{
|
||||
return Properties.Resources.calculator_expression_not_complete;
|
||||
}
|
||||
|
||||
if (result is double[,])
|
||||
{
|
||||
// '[10,10]' is interpreted as array by mages engine
|
||||
return Properties.Resources.calculator_double_array_returned;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,328 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
|
||||
public static class CalculateHelper
|
||||
{
|
||||
private static readonly Regex RegValidExpressChar = new Regex(
|
||||
@"^(" +
|
||||
@"%|" +
|
||||
@"ceil\s*\(|floor\s*\(|exp\s*\(|max\s*\(|min\s*\(|abs\s*\(|log(?:2|10)?\s*\(|ln\s*\(|sqrt\s*\(|pow\s*\(|" +
|
||||
@"factorial\s*\(|sign\s*\(|round\s*\(|rand\s*\(\)|randi\s*\([^\)]|" +
|
||||
@"sin\s*\(|cos\s*\(|tan\s*\(|arcsin\s*\(|arccos\s*\(|arctan\s*\(|" +
|
||||
@"sinh\s*\(|cosh\s*\(|tanh\s*\(|arsinh\s*\(|arcosh\s*\(|artanh\s*\(|" +
|
||||
@"rad\s*\(|deg\s*\(|grad\s*\(|" + /* trigonometry unit conversion macros */
|
||||
@"pi|" +
|
||||
@"==|~=|&&|\|\||" +
|
||||
@"((-?(\d+(\.\d*)?)|-?(\.\d+))[Ee](-?\d+))|" + /* expression from CheckScientificNotation between parenthesis */
|
||||
@"e|[0-9]|0[xX][0-9a-fA-F]+|0[bB][01]+|0[oO][0-7]+|[\+\-\*\/\^\., ""]|[\(\)\|\!\[\]]" +
|
||||
@")+$",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
private const string DegToRad = "(pi / 180) * ";
|
||||
private const string DegToGrad = "(10 / 9) * ";
|
||||
private const string GradToRad = "(pi / 200) * ";
|
||||
private const string GradToDeg = "(9 / 10) * ";
|
||||
private const string RadToDeg = "(180 / pi) * ";
|
||||
private const string RadToGrad = "(200 / pi) * ";
|
||||
|
||||
public static bool InputValid(string input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(input));
|
||||
}
|
||||
|
||||
if (!RegValidExpressChar.IsMatch(input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!BracketHelper.IsBracketComplete(input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the input ends with a binary operator then it is not a valid input to mages and the Interpret function would throw an exception. Because we expect here that the user has not finished typing we block those inputs.
|
||||
var trimmedInput = input.TrimEnd();
|
||||
if (trimmedInput.EndsWith('+') || trimmedInput.EndsWith('-') || trimmedInput.EndsWith('*') || trimmedInput.EndsWith('|') || trimmedInput.EndsWith('\\') || trimmedInput.EndsWith('^') || trimmedInput.EndsWith('=') || trimmedInput.EndsWith('&') || trimmedInput.EndsWith('/') || trimmedInput.EndsWith('%'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string FixHumanMultiplicationExpressions(string input)
|
||||
{
|
||||
var output = CheckScientificNotation(input);
|
||||
output = CheckNumberOrConstantThenParenthesisExpr(output);
|
||||
output = CheckNumberOrConstantThenFunc(output);
|
||||
output = CheckParenthesisExprThenFunc(output);
|
||||
output = CheckParenthesisExprThenParenthesisExpr(output);
|
||||
output = CheckNumberThenConstant(output);
|
||||
output = CheckConstantThenConstant(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
private static string CheckScientificNotation(string input)
|
||||
{
|
||||
/**
|
||||
* NOTE: By the time the expression gets to us, it's already in English format.
|
||||
*
|
||||
* Regex explanation:
|
||||
* (-?(\d+({0}\d*)?)|-?({0}\d+)): Used to capture one of two types:
|
||||
* -?(\d+({0}\d*)?): Captures a decimal number starting with a number (e.g. "-1.23")
|
||||
* -?({0}\d+): Captures a decimal number without leading number (e.g. ".23")
|
||||
* e: Captures 'e' or 'E'
|
||||
* (-?\d+): Captures an integer number (e.g. "-1" or "23")
|
||||
*/
|
||||
var p = @"(-?(\d+(\.\d*)?)|-?(\.\d+))e(-?\d+)";
|
||||
return Regex.Replace(input, p, "($1 * 10^($5))", RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
/*
|
||||
* num (exp)
|
||||
* const (exp)
|
||||
*/
|
||||
private static string CheckNumberOrConstantThenParenthesisExpr(string input)
|
||||
{
|
||||
var output = input;
|
||||
do
|
||||
{
|
||||
input = output;
|
||||
output = Regex.Replace(input, @"(\d+|pi|e)\s*(\()", m =>
|
||||
{
|
||||
if (m.Index > 0 && char.IsLetter(input[m.Index - 1]))
|
||||
{
|
||||
return m.Value;
|
||||
}
|
||||
|
||||
return $"{m.Groups[1].Value} * {m.Groups[2].Value}";
|
||||
});
|
||||
}
|
||||
while (output != input);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* num func
|
||||
* const func
|
||||
*/
|
||||
private static string CheckNumberOrConstantThenFunc(string input)
|
||||
{
|
||||
var output = input;
|
||||
do
|
||||
{
|
||||
input = output;
|
||||
output = Regex.Replace(input, @"(\d+|pi|e)\s*([a-zA-Z]+[0-9]*\s*\()", m =>
|
||||
{
|
||||
if (input[m.Index] == 'e' && input[m.Index + 1] == 'x' && input[m.Index + 2] == 'p')
|
||||
{
|
||||
return m.Value;
|
||||
}
|
||||
|
||||
if (m.Index > 0 && char.IsLetter(input[m.Index - 1]))
|
||||
{
|
||||
return m.Value;
|
||||
}
|
||||
|
||||
return $"{m.Groups[1].Value} * {m.Groups[2].Value}";
|
||||
});
|
||||
}
|
||||
while (output != input);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* (exp) func
|
||||
* func func
|
||||
*/
|
||||
private static string CheckParenthesisExprThenFunc(string input)
|
||||
{
|
||||
var p = @"(\))\s*([a-zA-Z]+[0-9]*\s*\()";
|
||||
var r = "$1 * $2";
|
||||
return Regex.Replace(input, p, r);
|
||||
}
|
||||
|
||||
/*
|
||||
* (exp) (exp)
|
||||
* func (exp)
|
||||
*/
|
||||
private static string CheckParenthesisExprThenParenthesisExpr(string input)
|
||||
{
|
||||
var p = @"(\))\s*(\()";
|
||||
var r = "$1 * $2";
|
||||
return Regex.Replace(input, p, r);
|
||||
}
|
||||
|
||||
/*
|
||||
* num const
|
||||
*/
|
||||
private static string CheckNumberThenConstant(string input)
|
||||
{
|
||||
var output = input;
|
||||
do
|
||||
{
|
||||
input = output;
|
||||
output = Regex.Replace(input, @"(\d+)\s*(pi|e)", m =>
|
||||
{
|
||||
if (m.Index > 0 && char.IsLetter(input[m.Index - 1]))
|
||||
{
|
||||
return m.Value;
|
||||
}
|
||||
|
||||
return $"{m.Groups[1].Value} * {m.Groups[2].Value}";
|
||||
});
|
||||
}
|
||||
while (output != input);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* const const
|
||||
*/
|
||||
private static string CheckConstantThenConstant(string input)
|
||||
{
|
||||
var output = input;
|
||||
do
|
||||
{
|
||||
input = output;
|
||||
output = Regex.Replace(input, @"(pi|e)\s*(pi|e)", m =>
|
||||
{
|
||||
if (m.Index > 0 && char.IsLetter(input[m.Index - 1]))
|
||||
{
|
||||
return m.Value;
|
||||
}
|
||||
|
||||
return $"{m.Groups[1].Value} * {m.Groups[2].Value}";
|
||||
});
|
||||
}
|
||||
while (output != input);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// Gets the index of the closing bracket of a function
|
||||
private static int FindClosingBracketIndex(string input, int start)
|
||||
{
|
||||
var bracketCount = 0; // Set count to zero
|
||||
for (var i = start; i < input.Length; i++)
|
||||
{
|
||||
if (input[i] == '(')
|
||||
{
|
||||
bracketCount++;
|
||||
}
|
||||
else if (input[i] == ')')
|
||||
{
|
||||
bracketCount--;
|
||||
if (bracketCount == 0)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1; // Unmatched brackets
|
||||
}
|
||||
|
||||
private static string ModifyTrigFunction(string input, string function, string modification)
|
||||
{
|
||||
// Get the RegEx pattern to match, depending on whether the function is inverse or normal
|
||||
var pattern = function.StartsWith("arc", StringComparison.Ordinal) ? string.Empty : @"(?<!c)";
|
||||
pattern += $@"{function}\s*\(";
|
||||
|
||||
var index = 0; // Index for match to ensure that the same match is not found twice
|
||||
|
||||
Regex regex = new Regex(pattern);
|
||||
Match match;
|
||||
|
||||
while ((match = regex.Match(input, index)).Success)
|
||||
{
|
||||
index = match.Index + match.Groups[0].Length + modification.Length; // Get the next index to look from for further matches
|
||||
|
||||
var endIndex = FindClosingBracketIndex(input, match.Index + match.Groups[0].Length - 1); // Find the index of the closing bracket of the function
|
||||
|
||||
// If no valid bracket index was found, try the next match
|
||||
if (endIndex == -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var argument = input.Substring(match.Index + match.Groups[0].Length, endIndex - (match.Index + match.Groups[0].Length)); // Extract the argument between the brackets
|
||||
var replaced = function.StartsWith("arc", StringComparison.Ordinal) ? $"{modification}({match.Groups[0].Value}{argument}))" : $"{match.Groups[0].Value}{modification}({argument}))"; // The string to substitute in, handles differing formats of inverse functions
|
||||
|
||||
input = input.Remove(match.Index, endIndex - match.Index + 1); // Remove the match from the input
|
||||
input = input.Insert(match.Index, replaced); // Substitute with the new string
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
public static string UpdateTrigFunctions(string input, CalculateEngine.TrigMode mode)
|
||||
{
|
||||
var modifiedInput = input;
|
||||
if (mode == CalculateEngine.TrigMode.Degrees)
|
||||
{
|
||||
modifiedInput = ModifyTrigFunction(modifiedInput, "sin", DegToRad);
|
||||
modifiedInput = ModifyTrigFunction(modifiedInput, "cos", DegToRad);
|
||||
modifiedInput = ModifyTrigFunction(modifiedInput, "tan", DegToRad);
|
||||
modifiedInput = ModifyTrigFunction(modifiedInput, "arcsin", RadToDeg);
|
||||
modifiedInput = ModifyTrigFunction(modifiedInput, "arccos", RadToDeg);
|
||||
modifiedInput = ModifyTrigFunction(modifiedInput, "arctan", RadToDeg);
|
||||
}
|
||||
else if (mode == CalculateEngine.TrigMode.Gradians)
|
||||
{
|
||||
modifiedInput = ModifyTrigFunction(modifiedInput, "sin", GradToRad);
|
||||
modifiedInput = ModifyTrigFunction(modifiedInput, "cos", GradToRad);
|
||||
modifiedInput = ModifyTrigFunction(modifiedInput, "tan", GradToRad);
|
||||
modifiedInput = ModifyTrigFunction(modifiedInput, "arcsin", RadToGrad);
|
||||
modifiedInput = ModifyTrigFunction(modifiedInput, "arccos", RadToGrad);
|
||||
modifiedInput = ModifyTrigFunction(modifiedInput, "arctan", RadToGrad);
|
||||
}
|
||||
|
||||
return modifiedInput;
|
||||
}
|
||||
|
||||
private static string ModifyMathFunction(string input, string function, string modification)
|
||||
{
|
||||
// Create the pattern to match the function, opening bracket, and any spaces in between
|
||||
var pattern = $@"{function}\s*\(";
|
||||
return Regex.Replace(input, pattern, modification + "(");
|
||||
}
|
||||
|
||||
public static string ExpandTrigConversions(string input, CalculateEngine.TrigMode mode)
|
||||
{
|
||||
var modifiedInput = input;
|
||||
|
||||
// Expand "rad", "deg" and "grad" to their respective conversions for the current trig unit
|
||||
if (mode == CalculateEngine.TrigMode.Radians)
|
||||
{
|
||||
modifiedInput = ModifyMathFunction(modifiedInput, "deg", DegToRad);
|
||||
modifiedInput = ModifyMathFunction(modifiedInput, "grad", GradToRad);
|
||||
modifiedInput = ModifyMathFunction(modifiedInput, "rad", string.Empty);
|
||||
}
|
||||
else if (mode == CalculateEngine.TrigMode.Degrees)
|
||||
{
|
||||
modifiedInput = ModifyMathFunction(modifiedInput, "deg", string.Empty);
|
||||
modifiedInput = ModifyMathFunction(modifiedInput, "grad", GradToDeg);
|
||||
modifiedInput = ModifyMathFunction(modifiedInput, "rad", RadToDeg);
|
||||
}
|
||||
else if (mode == CalculateEngine.TrigMode.Gradians)
|
||||
{
|
||||
modifiedInput = ModifyMathFunction(modifiedInput, "deg", DegToGrad);
|
||||
modifiedInput = ModifyMathFunction(modifiedInput, "grad", string.Empty);
|
||||
modifiedInput = ModifyMathFunction(modifiedInput, "rad", RadToGrad);
|
||||
}
|
||||
|
||||
return modifiedInput;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
|
||||
public struct CalculateResult : IEquatable<CalculateResult>
|
||||
{
|
||||
public decimal? Result { get; set; }
|
||||
|
||||
public decimal? RoundedResult { get; set; }
|
||||
|
||||
public bool Equals(CalculateResult other)
|
||||
{
|
||||
return Result == other.Result && RoundedResult == other.RoundedResult;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is CalculateResult other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Result, RoundedResult);
|
||||
}
|
||||
|
||||
public static bool operator ==(CalculateResult left, CalculateResult right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(CalculateResult left, CalculateResult right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
|
||||
public static class CalculatorIcons
|
||||
{
|
||||
public static IconInfo ResultIcon => new("\uE94E");
|
||||
|
||||
public static IconInfo SaveIcon => new("\uE74E");
|
||||
|
||||
public static IconInfo ErrorIcon => new("\uE783");
|
||||
|
||||
public static IconInfo ProviderIcon => IconHelpers.FromRelativePath("Assets\\Calculator.svg");
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
|
||||
internal static class ErrorHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Method to handles errors while calculating
|
||||
/// </summary>
|
||||
/// <param name="isFallbackSearch">Bool to indicate if it is a fallback query.</param>
|
||||
/// <param name="queryInput">User input as string including the action keyword.</param>
|
||||
/// <param name="errorMessage">Error message if applicable.</param>
|
||||
/// <param name="exception">Exception if applicable.</param>
|
||||
/// <returns>List of results to show. Either an error message or an empty list.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="errorMessage"/> and <paramref name="exception"/> are both filled with their default values.</exception>
|
||||
internal static ListItem OnError(bool isFallbackSearch, string queryInput, string errorMessage, Exception exception = default)
|
||||
{
|
||||
string userMessage;
|
||||
|
||||
if (errorMessage != default)
|
||||
{
|
||||
Logger.LogError($"Failed to calculate <{queryInput}>: {errorMessage}");
|
||||
userMessage = errorMessage;
|
||||
}
|
||||
else if (exception != default)
|
||||
{
|
||||
Logger.LogError($"Exception when query for <{queryInput}>", exception);
|
||||
userMessage = exception.Message;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("The arguments error and exception have default values. One of them has to be filled with valid error data (error message/exception)!");
|
||||
}
|
||||
|
||||
return isFallbackSearch ? null : CreateErrorResult(userMessage);
|
||||
}
|
||||
|
||||
private static ListItem CreateErrorResult(string errorMessage)
|
||||
{
|
||||
return new ListItem(new NoOpCommand())
|
||||
{
|
||||
Title = Properties.Resources.calculator_calculation_failed_title,
|
||||
Subtitle = errorMessage,
|
||||
Icon = CalculatorIcons.ErrorIcon,
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to convert all numbers in a text from one culture format to another.
|
||||
/// </summary>
|
||||
public class NumberTranslator
|
||||
{
|
||||
private readonly CultureInfo sourceCulture;
|
||||
private readonly CultureInfo targetCulture;
|
||||
private readonly Regex splitRegexForSource;
|
||||
private readonly Regex splitRegexForTarget;
|
||||
|
||||
private NumberTranslator(CultureInfo sourceCulture, CultureInfo targetCulture)
|
||||
{
|
||||
this.sourceCulture = sourceCulture;
|
||||
this.targetCulture = targetCulture;
|
||||
|
||||
splitRegexForSource = GetSplitRegex(this.sourceCulture);
|
||||
splitRegexForTarget = GetSplitRegex(this.targetCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="NumberTranslator"/>.
|
||||
/// </summary>
|
||||
/// <param name="sourceCulture">source culture</param>
|
||||
/// <param name="targetCulture">target culture</param>
|
||||
/// <returns>Number translator for target culture</returns>
|
||||
public static NumberTranslator Create(CultureInfo sourceCulture, CultureInfo targetCulture)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(sourceCulture);
|
||||
|
||||
ArgumentNullException.ThrowIfNull(targetCulture);
|
||||
|
||||
return new NumberTranslator(sourceCulture, targetCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translate from source to target culture.
|
||||
/// </summary>
|
||||
/// <param name="input">input string to translate</param>
|
||||
/// <returns>translated string</returns>
|
||||
public string Translate(string input)
|
||||
{
|
||||
return Translate(input, sourceCulture, targetCulture, splitRegexForSource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translate from target to source culture.
|
||||
/// </summary>
|
||||
/// <param name="input">input string to translate back to source culture</param>
|
||||
/// <returns>source culture string</returns>
|
||||
public string TranslateBack(string input)
|
||||
{
|
||||
return Translate(input, targetCulture, sourceCulture, splitRegexForTarget);
|
||||
}
|
||||
|
||||
private static string Translate(string input, CultureInfo cultureFrom, CultureInfo cultureTo, Regex splitRegex)
|
||||
{
|
||||
var outputBuilder = new StringBuilder();
|
||||
var hexRegex = new Regex(@"(?:(0x[\da-fA-F]+))");
|
||||
|
||||
var hexTokens = hexRegex.Split(input);
|
||||
|
||||
foreach (var hexToken in hexTokens)
|
||||
{
|
||||
if (hexToken.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
// Mages engine has issues processing large hex number (larger than 7 hex digits + 0x prefix = 9 characters). So we convert it to decimal and pass it to the engine.
|
||||
if (hexToken.Length > 9)
|
||||
{
|
||||
try
|
||||
{
|
||||
var num = Convert.ToInt64(hexToken, 16);
|
||||
var numStr = num.ToString(cultureFrom);
|
||||
outputBuilder.Append(numStr);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
outputBuilder.Append(hexToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
outputBuilder.Append(hexToken);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var tokens = splitRegex.Split(hexToken);
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
var leadingZeroCount = 0;
|
||||
|
||||
// Count leading zero characters.
|
||||
foreach (var c in token)
|
||||
{
|
||||
if (c != '0')
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
leadingZeroCount++;
|
||||
}
|
||||
|
||||
// number is all zero characters. no need to add zero characters at the end.
|
||||
if (token.Length == leadingZeroCount)
|
||||
{
|
||||
leadingZeroCount = 0;
|
||||
}
|
||||
|
||||
decimal number;
|
||||
|
||||
outputBuilder.Append(
|
||||
decimal.TryParse(token, NumberStyles.Number, cultureFrom, out number)
|
||||
? (new string('0', leadingZeroCount) + number.ToString(cultureTo))
|
||||
: token.Replace(cultureFrom.TextInfo.ListSeparator, cultureTo.TextInfo.ListSeparator));
|
||||
}
|
||||
}
|
||||
|
||||
return outputBuilder.ToString();
|
||||
}
|
||||
|
||||
private static Regex GetSplitRegex(CultureInfo culture)
|
||||
{
|
||||
var splitPattern = $"((?:\\d|{Regex.Escape(culture.NumberFormat.NumberDecimalSeparator)}";
|
||||
if (!string.IsNullOrEmpty(culture.NumberFormat.NumberGroupSeparator))
|
||||
{
|
||||
splitPattern += $"|{Regex.Escape(culture.NumberFormat.NumberGroupSeparator)}";
|
||||
}
|
||||
|
||||
splitPattern += ")+)";
|
||||
return new Regex(splitPattern);
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
|
||||
public static partial class QueryHelper
|
||||
{
|
||||
public static ListItem Query(string query, SettingsManager settings, bool isFallbackSearch, TypedEventHandler<object, object> handleSave = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(query);
|
||||
if (!isFallbackSearch)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(handleSave);
|
||||
}
|
||||
|
||||
CultureInfo inputCulture = settings.InputUseEnglishFormat ? new CultureInfo("en-us") : CultureInfo.CurrentCulture;
|
||||
CultureInfo outputCulture = settings.OutputUseEnglishFormat ? new CultureInfo("en-us") : CultureInfo.CurrentCulture;
|
||||
|
||||
// Happens if the user has only typed the action key so far
|
||||
if (string.IsNullOrEmpty(query))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
NumberTranslator translator = NumberTranslator.Create(inputCulture, new CultureInfo("en-US"));
|
||||
var input = translator.Translate(query.Normalize(NormalizationForm.FormKC));
|
||||
|
||||
if (!CalculateHelper.InputValid(input))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Using CurrentUICulture since this is user facing
|
||||
var result = CalculateEngine.Interpret(settings, input, outputCulture, out var errorMessage);
|
||||
|
||||
// This could happen for some incorrect queries, like pi(2)
|
||||
if (result.Equals(default(CalculateResult)))
|
||||
{
|
||||
// If errorMessage is not default then do error handling
|
||||
return errorMessage == default ? null : ErrorHandler.OnError(isFallbackSearch, query, errorMessage);
|
||||
}
|
||||
|
||||
if (isFallbackSearch)
|
||||
{
|
||||
// Fallback search
|
||||
return ResultHelper.CreateResult(result.RoundedResult, inputCulture, outputCulture, query);
|
||||
}
|
||||
|
||||
return ResultHelper.CreateResult(result.RoundedResult, inputCulture, outputCulture, query, handleSave);
|
||||
}
|
||||
catch (Mages.Core.ParseException)
|
||||
{
|
||||
// Invalid input
|
||||
return ErrorHandler.OnError(isFallbackSearch, query, Properties.Resources.calculator_expression_not_complete);
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
// Result to big to convert to decimal
|
||||
return ErrorHandler.OnError(isFallbackSearch, query, Properties.Resources.calculator_not_covert_to_decimal);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Any other crash occurred
|
||||
// We want to keep the process alive if any the mages library throws any exceptions.
|
||||
return ErrorHandler.OnError(isFallbackSearch, query, default, e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
|
||||
public static class ResultHelper
|
||||
{
|
||||
public static ListItem CreateResult(decimal? roundedResult, CultureInfo inputCulture, CultureInfo outputCulture, string query, TypedEventHandler<object, object> handleSave)
|
||||
{
|
||||
// Return null when the expression is not a valid calculator query.
|
||||
if (roundedResult == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = roundedResult?.ToString(outputCulture);
|
||||
|
||||
// Create a SaveCommand and subscribe to the SaveRequested event
|
||||
// This can append the result to the history list.
|
||||
var saveCommand = new SaveCommand(result);
|
||||
saveCommand.SaveRequested += handleSave;
|
||||
|
||||
var copyCommandItem = CreateResult(roundedResult, inputCulture, outputCulture, query);
|
||||
|
||||
return new ListItem(saveCommand)
|
||||
{
|
||||
// Using CurrentCulture since this is user facing
|
||||
Icon = CalculatorIcons.ResultIcon,
|
||||
Title = result,
|
||||
Subtitle = query,
|
||||
TextToSuggest = result,
|
||||
MoreCommands = [
|
||||
new CommandContextItem(copyCommandItem.Command)
|
||||
{
|
||||
Icon = copyCommandItem.Icon,
|
||||
Title = copyCommandItem.Title,
|
||||
Subtitle = copyCommandItem.Subtitle,
|
||||
},
|
||||
..copyCommandItem.MoreCommands,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
public static ListItem CreateResult(decimal? roundedResult, CultureInfo inputCulture, CultureInfo outputCulture, string query)
|
||||
{
|
||||
// Return null when the expression is not a valid calculator query.
|
||||
if (roundedResult == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var decimalResult = roundedResult?.ToString(outputCulture);
|
||||
|
||||
List<CommandContextItem> context = [];
|
||||
|
||||
if (decimal.IsInteger((decimal)roundedResult))
|
||||
{
|
||||
var i = decimal.ToInt64((decimal)roundedResult);
|
||||
try
|
||||
{
|
||||
var hexResult = "0x" + i.ToString("X", outputCulture);
|
||||
context.Add(new CommandContextItem(new CopyTextCommand(hexResult) { Name = Properties.Resources.calculator_copy_hex })
|
||||
{
|
||||
Title = hexResult,
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error parsing hex format", ex);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var binaryResult = "0b" + i.ToString("B", outputCulture);
|
||||
context.Add(new CommandContextItem(new CopyTextCommand(binaryResult) { Name = Properties.Resources.calculator_copy_binary })
|
||||
{
|
||||
Title = binaryResult,
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error parsing binary format", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return new ListItem(new CopyTextCommand(decimalResult))
|
||||
{
|
||||
// Using CurrentCulture since this is user facing
|
||||
Title = decimalResult,
|
||||
Subtitle = query,
|
||||
TextToSuggest = decimalResult,
|
||||
MoreCommands = context.ToArray(),
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Ext.Calc.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
|
||||
public sealed partial class SaveCommand : InvokableCommand
|
||||
{
|
||||
private readonly string _result;
|
||||
|
||||
public event TypedEventHandler<object, object> SaveRequested;
|
||||
|
||||
public SaveCommand(string result)
|
||||
{
|
||||
Name = Resources.calculator_save_command_name;
|
||||
Icon = CalculatorIcons.SaveIcon;
|
||||
_result = result;
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
SaveRequested?.Invoke(this, this);
|
||||
ClipboardHelper.SetText(_result);
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
|
||||
public class SettingsManager : JsonSettingsManager
|
||||
{
|
||||
private static readonly string _namespace = "calculator";
|
||||
|
||||
private static string Namespaced(string propertyName) => $"{_namespace}.{propertyName}";
|
||||
|
||||
private static readonly List<ChoiceSetSetting.Choice> _trigUnitChoices = new()
|
||||
{
|
||||
new ChoiceSetSetting.Choice(Properties.Resources.calculator_settings_trig_unit_radians, "0"),
|
||||
new ChoiceSetSetting.Choice(Properties.Resources.calculator_settings_trig_unit_degrees, "1"),
|
||||
new ChoiceSetSetting.Choice(Properties.Resources.calculator_settings_trig_unit_gradians, "2"),
|
||||
};
|
||||
|
||||
private readonly ChoiceSetSetting _trigUnit = new(
|
||||
Namespaced(nameof(TrigUnit)),
|
||||
Properties.Resources.calculator_settings_trig_unit_mode,
|
||||
Properties.Resources.calculator_settings_trig_unit_mode_description,
|
||||
_trigUnitChoices);
|
||||
|
||||
private readonly ToggleSetting _inputUseEnNumberFormat = new(
|
||||
Namespaced(nameof(InputUseEnglishFormat)),
|
||||
Properties.Resources.calculator_settings_in_en_format,
|
||||
Properties.Resources.calculator_settings_in_en_format_description,
|
||||
false);
|
||||
|
||||
private readonly ToggleSetting _outputUseEnNumberFormat = new(
|
||||
Namespaced(nameof(OutputUseEnglishFormat)),
|
||||
Properties.Resources.calculator_settings_out_en_format,
|
||||
Properties.Resources.calculator_settings_out_en_format_description,
|
||||
false);
|
||||
|
||||
public CalculateEngine.TrigMode TrigUnit
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_trigUnit.Value == null || string.IsNullOrEmpty(_trigUnit.Value))
|
||||
{
|
||||
return CalculateEngine.TrigMode.Radians;
|
||||
}
|
||||
|
||||
var success = int.TryParse(_trigUnit.Value, out var result);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
return CalculateEngine.TrigMode.Radians;
|
||||
}
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case 0:
|
||||
return CalculateEngine.TrigMode.Radians;
|
||||
case 1:
|
||||
return CalculateEngine.TrigMode.Degrees;
|
||||
case 2:
|
||||
return CalculateEngine.TrigMode.Gradians;
|
||||
default:
|
||||
return CalculateEngine.TrigMode.Radians;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool InputUseEnglishFormat => _inputUseEnNumberFormat.Value;
|
||||
|
||||
public bool OutputUseEnglishFormat => _outputUseEnNumberFormat.Value;
|
||||
|
||||
internal static string SettingsJsonPath()
|
||||
{
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
// now, the state is just next to the exe
|
||||
return Path.Combine(directory, "settings.json");
|
||||
}
|
||||
|
||||
public SettingsManager()
|
||||
{
|
||||
FilePath = SettingsJsonPath();
|
||||
|
||||
Settings.Add(_trigUnit);
|
||||
Settings.Add(_inputUseEnNumberFormat);
|
||||
Settings.Add(_outputUseEnNumberFormat);
|
||||
|
||||
// Load settings from file upon initialization
|
||||
LoadSettings();
|
||||
|
||||
Settings.SettingsChanged += (s, a) => this.SaveSettings();
|
||||
}
|
||||
}
|
@ -9,9 +9,14 @@
|
||||
<ProjectPriFileName>Microsoft.CmdPal.Ext.Calc.pri</ProjectPriFileName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Mages" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
|
@ -0,0 +1,127 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
using Microsoft.CmdPal.Ext.Calc.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.Pages;
|
||||
|
||||
// The calculator page is a dynamic list page
|
||||
// * The first command is where we display the results. Title=result, Subtitle=query
|
||||
// - The default command is `SaveCommand`.
|
||||
// - When you save, insert into list at spot 1
|
||||
// - change SearchText to the result
|
||||
// - MoreCommands: a single `CopyCommand` to copy the result to the clipboard
|
||||
// * The rest of the items are previously saved results
|
||||
// - Command is a CopyCommand
|
||||
// - Each item also sets the TextToSuggest to the result
|
||||
public sealed partial class CalculatorListPage : DynamicListPage
|
||||
{
|
||||
private readonly Lock _resultsLock = new();
|
||||
private readonly SettingsManager _settingsManager;
|
||||
private readonly List<ListItem> _items = [];
|
||||
private readonly List<ListItem> history = [];
|
||||
private readonly ListItem _emptyItem;
|
||||
|
||||
// This is the text that saved when the user click the result.
|
||||
// We need to avoid the double calculation. This may cause some wierd behaviors.
|
||||
private string skipQuerySearchText = string.Empty;
|
||||
|
||||
public CalculatorListPage(SettingsManager settings)
|
||||
{
|
||||
_settingsManager = settings;
|
||||
Icon = CalculatorIcons.ProviderIcon;
|
||||
Name = Resources.calculator_title;
|
||||
PlaceholderText = Resources.calculator_placeholder_text;
|
||||
Id = "com.microsoft.cmdpal.calculator";
|
||||
|
||||
_emptyItem = new ListItem(new NoOpCommand())
|
||||
{
|
||||
Title = Resources.calculator_placeholder_text,
|
||||
Icon = CalculatorIcons.ResultIcon,
|
||||
};
|
||||
EmptyContent = new CommandItem(new NoOpCommand())
|
||||
{
|
||||
Icon = CalculatorIcons.ProviderIcon,
|
||||
Title = Resources.calculator_placeholder_text,
|
||||
};
|
||||
|
||||
UpdateSearchText(string.Empty, string.Empty);
|
||||
}
|
||||
|
||||
public override void UpdateSearchText(string oldSearch, string newSearch)
|
||||
{
|
||||
if (oldSearch == newSearch)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(skipQuerySearchText) && newSearch == skipQuerySearchText)
|
||||
{
|
||||
// only skip once.
|
||||
skipQuerySearchText = string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
skipQuerySearchText = string.Empty;
|
||||
|
||||
_emptyItem.Subtitle = newSearch;
|
||||
|
||||
var result = QueryHelper.Query(newSearch, _settingsManager, false, HandleSave);
|
||||
UpdateResult(result);
|
||||
}
|
||||
|
||||
private void UpdateResult(ListItem result)
|
||||
{
|
||||
lock (_resultsLock)
|
||||
{
|
||||
this._items.Clear();
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
this._items.Add(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
_items.Add(_emptyItem);
|
||||
}
|
||||
|
||||
this._items.AddRange(history);
|
||||
}
|
||||
|
||||
RaiseItemsChanged(this._items.Count);
|
||||
}
|
||||
|
||||
private void HandleSave(object sender, object args)
|
||||
{
|
||||
var lastResult = _items[0].Title;
|
||||
if (!string.IsNullOrEmpty(lastResult))
|
||||
{
|
||||
var li = new ListItem(new CopyTextCommand(lastResult))
|
||||
{
|
||||
Title = _items[0].Title,
|
||||
Subtitle = _items[0].Subtitle,
|
||||
TextToSuggest = lastResult,
|
||||
};
|
||||
|
||||
history.Insert(0, li);
|
||||
_items.Insert(1, li);
|
||||
|
||||
// Why we need to clean the query record? Removed, but if necessary, please move it back.
|
||||
// _items[0].Subtitle = string.Empty;
|
||||
|
||||
// this change will call the UpdateSearchText again.
|
||||
// We need to avoid it.
|
||||
skipQuerySearchText = lastResult;
|
||||
SearchText = lastResult;
|
||||
this.RaiseItemsChanged(this._items.Count);
|
||||
}
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems() => _items.ToArray();
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Ext.Calc.Helper;
|
||||
using Microsoft.CmdPal.Ext.Calc.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.Pages;
|
||||
|
||||
public sealed partial class FallbackCalculatorItem : FallbackCommandItem
|
||||
{
|
||||
private readonly CopyTextCommand _copyCommand = new(string.Empty);
|
||||
private readonly SettingsManager _settings;
|
||||
|
||||
public FallbackCalculatorItem(SettingsManager settings)
|
||||
: base(new NoOpCommand(), Resources.calculator_title)
|
||||
{
|
||||
Command = _copyCommand;
|
||||
_copyCommand.Name = string.Empty;
|
||||
Title = string.Empty;
|
||||
Subtitle = Resources.calculator_placeholder_text;
|
||||
Icon = CalculatorIcons.ProviderIcon;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public override void UpdateQuery(string query)
|
||||
{
|
||||
var result = QueryHelper.Query(query, _settings, true, null);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
_copyCommand.Text = string.Empty;
|
||||
_copyCommand.Name = string.Empty;
|
||||
Title = string.Empty;
|
||||
Subtitle = string.Empty;
|
||||
MoreCommands = [];
|
||||
return;
|
||||
}
|
||||
|
||||
_copyCommand.Text = result.Title;
|
||||
_copyCommand.Name = string.IsNullOrWhiteSpace(query) ? string.Empty : Resources.calculator_copy_command_name;
|
||||
Title = result.Title;
|
||||
|
||||
// we have to make the subtitle the equation,
|
||||
// so that we will still string match the original query
|
||||
// Otherwise, something like 1+2 will have a title of "3" and not match
|
||||
Subtitle = query;
|
||||
|
||||
MoreCommands = result.MoreCommands;
|
||||
}
|
||||
}
|
@ -60,6 +60,24 @@ namespace Microsoft.CmdPal.Ext.Calc.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Failed to calculate the input.
|
||||
/// </summary>
|
||||
public static string calculator_calculation_failed_title {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_calculation_failed_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Copy binary.
|
||||
/// </summary>
|
||||
public static string calculator_copy_binary {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_copy_binary", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Copy.
|
||||
/// </summary>
|
||||
@ -69,6 +87,15 @@ namespace Microsoft.CmdPal.Ext.Calc.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Copy hexadecimal.
|
||||
/// </summary>
|
||||
public static string calculator_copy_hex {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_copy_hex", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Calculator.
|
||||
/// </summary>
|
||||
@ -78,6 +105,24 @@ namespace Microsoft.CmdPal.Ext.Calc.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Expression contains division by zero.
|
||||
/// </summary>
|
||||
public static string calculator_division_by_zero {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_division_by_zero", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unsupported use of square brackets.
|
||||
/// </summary>
|
||||
public static string calculator_double_array_returned {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_double_array_returned", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Error: {0}.
|
||||
/// </summary>
|
||||
@ -87,6 +132,33 @@ namespace Microsoft.CmdPal.Ext.Calc.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Expression wrong or incomplete.
|
||||
/// </summary>
|
||||
public static string calculator_expression_not_complete {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_expression_not_complete", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Calculation result is not a valid number (NaN).
|
||||
/// </summary>
|
||||
public static string calculator_not_a_number {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_not_a_number", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Result value was either too large or too small for a decimal number.
|
||||
/// </summary>
|
||||
public static string calculator_not_covert_to_decimal {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_not_covert_to_decimal", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Type an equation....
|
||||
/// </summary>
|
||||
@ -105,6 +177,105 @@ namespace Microsoft.CmdPal.Ext.Calc.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Use English (United States) number format for input.
|
||||
/// </summary>
|
||||
public static string calculator_settings_in_en_format {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_settings_in_en_format", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Ignores your system setting and expects numbers in the format '{0}'..
|
||||
/// </summary>
|
||||
public static string calculator_settings_in_en_format_description {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_settings_in_en_format_description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Use English (United States) number format for output.
|
||||
/// </summary>
|
||||
public static string calculator_settings_out_en_format {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_settings_out_en_format", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Ignores your system setting and returns numbers in the format '{0}'..
|
||||
/// </summary>
|
||||
public static string calculator_settings_out_en_format_description {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_settings_out_en_format_description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Replace input if query ends with '='.
|
||||
/// </summary>
|
||||
public static string calculator_settings_replace_input {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_settings_replace_input", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to When using direct activation, appending '=' to the expression will replace the input with the calculated result (e.g. '=5*3-2=' will change the query to '=13')..
|
||||
/// </summary>
|
||||
public static string calculator_settings_replace_input_description {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_settings_replace_input_description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Degrees.
|
||||
/// </summary>
|
||||
public static string calculator_settings_trig_unit_degrees {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_settings_trig_unit_degrees", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Gradians.
|
||||
/// </summary>
|
||||
public static string calculator_settings_trig_unit_gradians {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_settings_trig_unit_gradians", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Trigonometry Unit.
|
||||
/// </summary>
|
||||
public static string calculator_settings_trig_unit_mode {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_settings_trig_unit_mode", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Specifies the angle unit to use for trigonometry operations.
|
||||
/// </summary>
|
||||
public static string calculator_settings_trig_unit_mode_description {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_settings_trig_unit_mode_description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Radians.
|
||||
/// </summary>
|
||||
public static string calculator_settings_trig_unit_radians {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_settings_trig_unit_radians", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Calculator.
|
||||
/// </summary>
|
||||
|
@ -140,4 +140,63 @@
|
||||
<data name="calculator_copy_command_name" xml:space="preserve">
|
||||
<value>Copy</value>
|
||||
</data>
|
||||
<data name="calculator_calculation_failed_title" xml:space="preserve">
|
||||
<value>Failed to calculate the input</value>
|
||||
</data>
|
||||
<data name="calculator_division_by_zero" xml:space="preserve">
|
||||
<value>Expression contains division by zero</value>
|
||||
</data>
|
||||
<data name="calculator_expression_not_complete" xml:space="preserve">
|
||||
<value>Expression wrong or incomplete</value>
|
||||
</data>
|
||||
<data name="calculator_not_a_number" xml:space="preserve">
|
||||
<value>Calculation result is not a valid number (NaN)</value>
|
||||
</data>
|
||||
<data name="calculator_double_array_returned" xml:space="preserve">
|
||||
<value>Unsupported use of square brackets</value>
|
||||
</data>
|
||||
<data name="calculator_settings_trig_unit_gradians" xml:space="preserve">
|
||||
<value>Gradians</value>
|
||||
</data>
|
||||
<data name="calculator_settings_trig_unit_degrees" xml:space="preserve">
|
||||
<value>Degrees</value>
|
||||
</data>
|
||||
<data name="calculator_settings_trig_unit_radians" xml:space="preserve">
|
||||
<value>Radians</value>
|
||||
</data>
|
||||
<data name="calculator_settings_trig_unit_mode" xml:space="preserve">
|
||||
<value>Trigonometry Unit</value>
|
||||
</data>
|
||||
<data name="calculator_settings_trig_unit_mode_description" xml:space="preserve">
|
||||
<value>Specifies the angle unit to use for trigonometry operations</value>
|
||||
</data>
|
||||
<data name="calculator_settings_out_en_format" xml:space="preserve">
|
||||
<value>Use English (United States) number format for output</value>
|
||||
</data>
|
||||
<data name="calculator_settings_out_en_format_description" xml:space="preserve">
|
||||
<value>Ignores your system setting and returns numbers in the format '{0}'.</value>
|
||||
<comment>{0} is a placeholder and will be replaced in code.</comment>
|
||||
</data>
|
||||
<data name="calculator_settings_in_en_format" xml:space="preserve">
|
||||
<value>Use English (United States) number format for input</value>
|
||||
</data>
|
||||
<data name="calculator_settings_in_en_format_description" xml:space="preserve">
|
||||
<value>Ignores your system setting and expects numbers in the format '{0}'.</value>
|
||||
<comment>{0} is a placeholder and will be replaced in code.</comment>
|
||||
</data>
|
||||
<data name="calculator_settings_replace_input" xml:space="preserve">
|
||||
<value>Replace input if query ends with '='</value>
|
||||
</data>
|
||||
<data name="calculator_settings_replace_input_description" xml:space="preserve">
|
||||
<value>When using direct activation, appending '=' to the expression will replace the input with the calculated result (e.g. '=5*3-2=' will change the query to '=13').</value>
|
||||
</data>
|
||||
<data name="calculator_not_covert_to_decimal" xml:space="preserve">
|
||||
<value>Result value was either too large or too small for a decimal number</value>
|
||||
</data>
|
||||
<data name="calculator_copy_hex" xml:space="preserve">
|
||||
<value>Copy hexadecimal</value>
|
||||
</data>
|
||||
<data name="calculator_copy_binary" xml:space="preserve">
|
||||
<value>Copy binary</value>
|
||||
</data>
|
||||
</root>
|
Loading…
x
Reference in New Issue
Block a user