[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:
Yu Leng 2025-04-17 14:59:10 +08:00 committed by GitHub
parent 4f9e829155
commit 6cf73ce839
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 1565 additions and 154 deletions

View File

@ -1299,6 +1299,7 @@ QUNS
QXZ QXZ
RAII RAII
RAlt RAlt
Rappl
randi randi
Rasterization Rasterization
Rasterize Rasterize

View File

@ -75,6 +75,40 @@ OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/> 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 ## Utility: File Explorer Add-ins
### Monaco Editor ### Monaco Editor

View File

@ -2,176 +2,34 @@
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using Microsoft.CmdPal.Ext.Calc.Helper;
using System.Collections.Generic; using Microsoft.CmdPal.Ext.Calc.Pages;
using System.Data;
using System.Globalization;
using System.Text;
using Microsoft.CmdPal.Ext.Calc.Properties; using Microsoft.CmdPal.Ext.Calc.Properties;
using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit; using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Foundation;
namespace Microsoft.CmdPal.Ext.Calc; namespace Microsoft.CmdPal.Ext.Calc;
public partial class CalculatorCommandProvider : CommandProvider public partial class CalculatorCommandProvider : CommandProvider
{ {
private readonly ListItem _listItem = new(new CalculatorListPage()) { Subtitle = Resources.calculator_top_level_subtitle }; private readonly ListItem _listItem = new(new CalculatorListPage(settings))
private readonly FallbackCalculatorItem _fallback = new(); {
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() public CalculatorCommandProvider()
{ {
Id = "Calculator"; Id = "Calculator";
DisplayName = Resources.calculator_display_name; DisplayName = Resources.calculator_display_name;
Icon = IconHelpers.FromRelativePath("Assets\\Calculator.svg"); Icon = CalculatorIcons.ProviderIcon;
Settings = settings.Settings;
} }
public override ICommandItem[] TopLevelCommands() => [_listItem]; public override ICommandItem[] TopLevelCommands() => [_listItem];
public override IFallbackCommandItem[] FallbackCommands() => [_fallback]; 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;
}
}
}

View File

@ -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,
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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");
}

View File

@ -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,
};
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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(),
};
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -9,9 +9,14 @@
<ProjectPriFileName>Microsoft.CmdPal.Ext.Calc.pri</ProjectPriFileName> <ProjectPriFileName>Microsoft.CmdPal.Ext.Calc.pri</ProjectPriFileName>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" /> <ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Mages" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Properties\Resources.Designer.cs"> <Compile Update="Properties\Resources.Designer.cs">
<DependentUpon>Resources.resx</DependentUpon> <DependentUpon>Resources.resx</DependentUpon>

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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> /// <summary>
/// Looks up a localized string similar to Copy. /// Looks up a localized string similar to Copy.
/// </summary> /// </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> /// <summary>
/// Looks up a localized string similar to Calculator. /// Looks up a localized string similar to Calculator.
/// </summary> /// </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> /// <summary>
/// Looks up a localized string similar to Error: {0}. /// Looks up a localized string similar to Error: {0}.
/// </summary> /// </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> /// <summary>
/// Looks up a localized string similar to Type an equation.... /// Looks up a localized string similar to Type an equation....
/// </summary> /// </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 &apos;{0}&apos;..
/// </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 &apos;{0}&apos;..
/// </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 &apos;=&apos;.
/// </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 &apos;=&apos; to the expression will replace the input with the calculated result (e.g. &apos;=5*3-2=&apos; will change the query to &apos;=13&apos;)..
/// </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> /// <summary>
/// Looks up a localized string similar to Calculator. /// Looks up a localized string similar to Calculator.
/// </summary> /// </summary>

View File

@ -140,4 +140,63 @@
<data name="calculator_copy_command_name" xml:space="preserve"> <data name="calculator_copy_command_name" xml:space="preserve">
<value>Copy</value> <value>Copy</value>
</data> </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> </root>