mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 10:46:33 +02:00
CmdPal: Make Calculator Great Again (#44594)
## Summary of the Pull Request This PR continues the tradition of alphabetical progress. After [MBGA](#41961), we move on to **MCBA — Make Calculator Better Again!** - Introduces limited automatic correction and completion of expressions. - The goal is to allow uninterrupted typing and avoid disruptions when a partially entered expression is temporarily invalid (which previously caused the result to be replaced by an error message or hidden by the fallback). - The implementation intentionally aims for a sweet spot: - Ignores trailing binary operators. - Automatically closes all opened parentheses. - It is not exhaustive; for example, incomplete constants or functions may still result in an invalid query. - Copy current result to the search bar. - Adds an option to copy the current result to the search bar when the user types `=` at the end of the expression. - Adds a new menu item for the same action. - Fixes the **Save** command to also copy the result to the query. - Adds support for the `factorial(x)` function and the `x!` expression. - Factorial calculations are supported up to `170!` (limited by `double`), but display is constrained by decimal conversion and allows direct display of results up to `20!`. - Adds support for the `sign(x)` function. - Adds support for the `π` symbol as an alternative to the `pi` constant. - Adds a context menu item to the result list item and fallback that displays the octal representation of the result. - Implements beautification of the query: - Converts technical symbols such as `*` or `/` to `×` or `÷`, respectively. - Not enabled for fallbacks for now, since the item text should match the query to keep the score intact. - Implements additional normalization of symbols in the query: - Percent: `%`, `%`, `﹪` - Minus: `−`, `-`, `–`, `—` - Factorial: `!`, `!` - Multiplication: `*`, `×`, `∗`, `·`, `⋅`, `✕`, `✖`, `\u2062` (invisible times) - Division: `/`, `÷`, `➗`, `:` - Allows use of `²` and `³` as alternatives to `^2` and `^3`. - Updates the unit test that was culture sensitive to force en-US output (not an actual fix, but at least it clears false positive for now) - Fixes pre-parsing of scientific notation to prevent capturing minus sign as part of it. - Fixes normalization/rounding of the result, so it can display small values (the current solution turned it into a string with scientific notation and couldn't parse it back). - Updates test with new cases ## Pictures? Moving! Previous behavior: https://github.com/user-attachments/assets/ebcdcd85-797a-44f9-a8b1-a0f2f33c6b42 New behavior: https://github.com/user-attachments/assets/5bd94663-a0d0-4d7d-8032-1030e79926c3 <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #43481 - [x] Closes: #43460 - [x] Closes: #42078 - [x] Closes: #41839 - [x] Closes: #39659 - [x] Closes: #40502 - [x] Related to: #41715 <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed
This commit is contained in:
1
.github/actions/spell-check/expect.txt
vendored
1
.github/actions/spell-check/expect.txt
vendored
@@ -1826,6 +1826,7 @@ TEXTBOXNEWLINE
|
|||||||
textextractor
|
textextractor
|
||||||
TEXTINCLUDE
|
TEXTINCLUDE
|
||||||
tfopen
|
tfopen
|
||||||
|
tgamma
|
||||||
tgz
|
tgz
|
||||||
THEMECHANGED
|
THEMECHANGED
|
||||||
themeresources
|
themeresources
|
||||||
|
|||||||
@@ -3,9 +3,27 @@
|
|||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <cmath>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
namespace ExprtkCalculator::internal
|
namespace ExprtkCalculator::internal
|
||||||
{
|
{
|
||||||
|
static double factorial(const double n)
|
||||||
|
{
|
||||||
|
// Only allow non-negative integers
|
||||||
|
if (n < 0.0 || std::floor(n) != n)
|
||||||
|
{
|
||||||
|
return std::numeric_limits<double>::quiet_NaN();
|
||||||
|
}
|
||||||
|
return std::tgamma(n + 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static double sign(const double n)
|
||||||
|
{
|
||||||
|
if (n > 0.0) return 1.0;
|
||||||
|
if (n < 0.0) return -1.0;
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
std::wstring ToWStringFullPrecision(double value)
|
std::wstring ToWStringFullPrecision(double value)
|
||||||
{
|
{
|
||||||
@@ -25,6 +43,9 @@ namespace ExprtkCalculator::internal
|
|||||||
symbol_table.add_constant(name, value);
|
symbol_table.add_constant(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
symbol_table.add_function("factorial", factorial);
|
||||||
|
symbol_table.add_function("sign", sign);
|
||||||
|
|
||||||
exprtk::expression<double> expression;
|
exprtk::expression<double> expression;
|
||||||
expression.register_symbol_table(symbol_table);
|
expression.register_symbol_table(symbol_table);
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public class CloseOnEnterTests
|
|||||||
{
|
{
|
||||||
var settings = new Settings(closeOnEnter: true);
|
var settings = new Settings(closeOnEnter: true);
|
||||||
TypedEventHandler<object, object> handleSave = (s, e) => { };
|
TypedEventHandler<object, object> handleSave = (s, e) => { };
|
||||||
|
TypedEventHandler<object, object> handleReplace = (s, e) => { };
|
||||||
|
|
||||||
var item = ResultHelper.CreateResult(
|
var item = ResultHelper.CreateResult(
|
||||||
4m,
|
4m,
|
||||||
@@ -26,7 +27,8 @@ public class CloseOnEnterTests
|
|||||||
CultureInfo.CurrentCulture,
|
CultureInfo.CurrentCulture,
|
||||||
"2+2",
|
"2+2",
|
||||||
settings,
|
settings,
|
||||||
handleSave);
|
handleSave,
|
||||||
|
handleReplace);
|
||||||
|
|
||||||
Assert.IsNotNull(item);
|
Assert.IsNotNull(item);
|
||||||
Assert.IsInstanceOfType(item.Command, typeof(CopyTextCommand));
|
Assert.IsInstanceOfType(item.Command, typeof(CopyTextCommand));
|
||||||
@@ -41,6 +43,7 @@ public class CloseOnEnterTests
|
|||||||
{
|
{
|
||||||
var settings = new Settings(closeOnEnter: false);
|
var settings = new Settings(closeOnEnter: false);
|
||||||
TypedEventHandler<object, object> handleSave = (s, e) => { };
|
TypedEventHandler<object, object> handleSave = (s, e) => { };
|
||||||
|
TypedEventHandler<object, object> handleReplace = (s, e) => { };
|
||||||
|
|
||||||
var item = ResultHelper.CreateResult(
|
var item = ResultHelper.CreateResult(
|
||||||
4m,
|
4m,
|
||||||
@@ -48,7 +51,8 @@ public class CloseOnEnterTests
|
|||||||
CultureInfo.CurrentCulture,
|
CultureInfo.CurrentCulture,
|
||||||
"2+2",
|
"2+2",
|
||||||
settings,
|
settings,
|
||||||
handleSave);
|
handleSave,
|
||||||
|
handleReplace);
|
||||||
|
|
||||||
Assert.IsNotNull(item);
|
Assert.IsNotNull(item);
|
||||||
Assert.IsInstanceOfType(item.Command, typeof(SaveCommand));
|
Assert.IsInstanceOfType(item.Command, typeof(SaveCommand));
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ public class ExtendedCalculatorParserTests : CommandPaletteUnitTestBase
|
|||||||
["log10(3)", 0.47712125471966M],
|
["log10(3)", 0.47712125471966M],
|
||||||
["ln(e)", 1M],
|
["ln(e)", 1M],
|
||||||
["cosh(0)", 1M],
|
["cosh(0)", 1M],
|
||||||
|
["1*10^(-5)", 0.00001M],
|
||||||
|
["1*10^(-15)", 0.0000000000000001M],
|
||||||
|
["1*10^(-16)", 0M],
|
||||||
];
|
];
|
||||||
|
|
||||||
[DataTestMethod]
|
[DataTestMethod]
|
||||||
@@ -192,9 +195,11 @@ public class ExtendedCalculatorParserTests : CommandPaletteUnitTestBase
|
|||||||
private static IEnumerable<object[]> Interpret_MustReturnExpectedResult_WhenCalled_Data =>
|
private static IEnumerable<object[]> Interpret_MustReturnExpectedResult_WhenCalled_Data =>
|
||||||
[
|
[
|
||||||
|
|
||||||
// ["factorial(5)", 120M], ToDo: this don't support now
|
["factorial(5)", 120M],
|
||||||
// ["sign(-2)", -1M],
|
["5!", 120M],
|
||||||
// ["sign(2)", +1M],
|
["(2+3)!", 120M],
|
||||||
|
["sign(-2)", -1M],
|
||||||
|
["sign(2)", +1M],
|
||||||
["abs(-2)", 2M],
|
["abs(-2)", 2M],
|
||||||
["abs(2)", 2M],
|
["abs(2)", 2M],
|
||||||
["0+(1*2)/(0+1)", 2M], // Validate that division by "(0+1)" is not interpret as division by zero.
|
["0+(1*2)/(0+1)", 2M], // Validate that division by "(0+1)" is not interpret as division by zero.
|
||||||
@@ -221,6 +226,9 @@ public class ExtendedCalculatorParserTests : CommandPaletteUnitTestBase
|
|||||||
[
|
[
|
||||||
["0.2E1", "en-US", 2M],
|
["0.2E1", "en-US", 2M],
|
||||||
["0,2E1", "pt-PT", 2M],
|
["0,2E1", "pt-PT", 2M],
|
||||||
|
["3.5e3 + 2.5E2", "en-US", 3750M],
|
||||||
|
["3,5e3 + 2,5E2", "fr-FR", 3750M],
|
||||||
|
["1E3-1E3/1.5", "en-US", 333.333333333333371M],
|
||||||
];
|
];
|
||||||
|
|
||||||
[DataTestMethod]
|
[DataTestMethod]
|
||||||
@@ -389,4 +397,17 @@ public class ExtendedCalculatorParserTests : CommandPaletteUnitTestBase
|
|||||||
Assert.IsNotNull(result);
|
Assert.IsNotNull(result);
|
||||||
Assert.AreEqual(expectedResult, result);
|
Assert.AreEqual(expectedResult, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DataTestMethod]
|
||||||
|
[DataRow("171!")]
|
||||||
|
[DataRow("1000!")]
|
||||||
|
public void Interpret_ReturnsError_WhenValueOverflowsDecimal(string input)
|
||||||
|
{
|
||||||
|
var settings = new Settings();
|
||||||
|
|
||||||
|
CalculateEngine.Interpret(settings, input, CultureInfo.InvariantCulture, out var error);
|
||||||
|
|
||||||
|
Assert.IsFalse(string.IsNullOrEmpty(error));
|
||||||
|
Assert.AreNotEqual(null, error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// 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.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Calc.UnitTests;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class IncompleteQueryTests
|
||||||
|
{
|
||||||
|
[DataTestMethod]
|
||||||
|
[DataRow("2+2+", "2+2")]
|
||||||
|
[DataRow("2+2*", "2+2")]
|
||||||
|
[DataRow("sin(30", "sin(30)")]
|
||||||
|
[DataRow("((1+2)", "((1+2))")]
|
||||||
|
[DataRow("2*(3+4", "2*(3+4)")]
|
||||||
|
[DataRow("(1+2", "(1+2)")]
|
||||||
|
[DataRow("2*(", "2")]
|
||||||
|
[DataRow("2*(((", "2")]
|
||||||
|
public void TestTryGetIncompleteQuerySuccess(string input, string expected)
|
||||||
|
{
|
||||||
|
var result = QueryHelper.TryGetIncompleteQuery(input, out var newQuery);
|
||||||
|
Assert.IsTrue(result);
|
||||||
|
Assert.AreEqual(expected, newQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataTestMethod]
|
||||||
|
[DataRow("")]
|
||||||
|
[DataRow(" ")]
|
||||||
|
public void TestTryGetIncompleteQueryFail(string input)
|
||||||
|
{
|
||||||
|
var result = QueryHelper.TryGetIncompleteQuery(input, out var newQuery);
|
||||||
|
Assert.IsFalse(result);
|
||||||
|
Assert.AreEqual(input, newQuery);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// 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.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Calc.UnitTests;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class QueryHelperTests
|
||||||
|
{
|
||||||
|
[DataTestMethod]
|
||||||
|
[DataRow("2²", "4")]
|
||||||
|
[DataRow("2³", "8")]
|
||||||
|
[DataRow("2!", "2")]
|
||||||
|
[DataRow("2\u00A0*\u00A02", "4")] // Non-breaking space
|
||||||
|
[DataRow("20:10", "2")] // Colon as division
|
||||||
|
public void Interpret_HandlesNormalizedInputs(string input, string expected)
|
||||||
|
{
|
||||||
|
var settings = new Settings();
|
||||||
|
var result = QueryHelper.Query(input, settings, false, out _, (_, _) => { });
|
||||||
|
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(expected, result.Title);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ using System.Linq;
|
|||||||
using Microsoft.CmdPal.Ext.Calc.Helper;
|
using Microsoft.CmdPal.Ext.Calc.Helper;
|
||||||
using Microsoft.CmdPal.Ext.Calc.Pages;
|
using Microsoft.CmdPal.Ext.Calc.Pages;
|
||||||
using Microsoft.CmdPal.Ext.UnitTestBase;
|
using Microsoft.CmdPal.Ext.UnitTestBase;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Calc.UnitTests;
|
namespace Microsoft.CmdPal.Ext.Calc.UnitTests;
|
||||||
@@ -72,7 +71,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
|||||||
[DataRow("sin(60)", "0.809016", CalculateEngine.TrigMode.Gradians)]
|
[DataRow("sin(60)", "0.809016", CalculateEngine.TrigMode.Gradians)]
|
||||||
public void TrigModeSettingsTest(string input, string expected, CalculateEngine.TrigMode trigMode)
|
public void TrigModeSettingsTest(string input, string expected, CalculateEngine.TrigMode trigMode)
|
||||||
{
|
{
|
||||||
var settings = new Settings(trigUnit: trigMode);
|
var settings = new Settings(trigUnit: trigMode, outputUseEnglishFormat: true);
|
||||||
|
|
||||||
var page = new CalculatorListPage(settings);
|
var page = new CalculatorListPage(settings);
|
||||||
|
|
||||||
|
|||||||
@@ -12,17 +12,26 @@ public class Settings : ISettingsInterface
|
|||||||
private readonly bool inputUseEnglishFormat;
|
private readonly bool inputUseEnglishFormat;
|
||||||
private readonly bool outputUseEnglishFormat;
|
private readonly bool outputUseEnglishFormat;
|
||||||
private readonly bool closeOnEnter;
|
private readonly bool closeOnEnter;
|
||||||
|
private readonly bool copyResultToSearchBarIfQueryEndsWithEqualSign;
|
||||||
|
private readonly bool autoFixQuery;
|
||||||
|
private readonly bool inputNormalization;
|
||||||
|
|
||||||
public Settings(
|
public Settings(
|
||||||
CalculateEngine.TrigMode trigUnit = CalculateEngine.TrigMode.Radians,
|
CalculateEngine.TrigMode trigUnit = CalculateEngine.TrigMode.Radians,
|
||||||
bool inputUseEnglishFormat = false,
|
bool inputUseEnglishFormat = false,
|
||||||
bool outputUseEnglishFormat = false,
|
bool outputUseEnglishFormat = false,
|
||||||
bool closeOnEnter = true)
|
bool closeOnEnter = true,
|
||||||
|
bool copyResultToSearchBarIfQueryEndsWithEqualSign = true,
|
||||||
|
bool autoFixQuery = true,
|
||||||
|
bool inputNormalization = true)
|
||||||
{
|
{
|
||||||
this.trigUnit = trigUnit;
|
this.trigUnit = trigUnit;
|
||||||
this.inputUseEnglishFormat = inputUseEnglishFormat;
|
this.inputUseEnglishFormat = inputUseEnglishFormat;
|
||||||
this.outputUseEnglishFormat = outputUseEnglishFormat;
|
this.outputUseEnglishFormat = outputUseEnglishFormat;
|
||||||
this.closeOnEnter = closeOnEnter;
|
this.closeOnEnter = closeOnEnter;
|
||||||
|
this.copyResultToSearchBarIfQueryEndsWithEqualSign = copyResultToSearchBarIfQueryEndsWithEqualSign;
|
||||||
|
this.autoFixQuery = autoFixQuery;
|
||||||
|
this.inputNormalization = inputNormalization;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CalculateEngine.TrigMode TrigUnit => trigUnit;
|
public CalculateEngine.TrigMode TrigUnit => trigUnit;
|
||||||
@@ -32,4 +41,10 @@ public class Settings : ISettingsInterface
|
|||||||
public bool OutputUseEnglishFormat => outputUseEnglishFormat;
|
public bool OutputUseEnglishFormat => outputUseEnglishFormat;
|
||||||
|
|
||||||
public bool CloseOnEnter => closeOnEnter;
|
public bool CloseOnEnter => closeOnEnter;
|
||||||
|
|
||||||
|
public bool CopyResultToSearchBarIfQueryEndsWithEqualSign => copyResultToSearchBarIfQueryEndsWithEqualSign;
|
||||||
|
|
||||||
|
public bool AutoFixQuery => autoFixQuery;
|
||||||
|
|
||||||
|
public bool InputNormalization => inputNormalization;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,56 @@ public static class BracketHelper
|
|||||||
return trailTest.Count == 0;
|
return trailTest.Count == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string BalanceBrackets(string query)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(query))
|
||||||
|
{
|
||||||
|
return query ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var openBrackets = new Stack<TrailType>();
|
||||||
|
|
||||||
|
for (var i = 0; i < query.Length; i++)
|
||||||
|
{
|
||||||
|
var (direction, type) = BracketTrail(query[i]);
|
||||||
|
|
||||||
|
if (direction == TrailDirection.None)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction == TrailDirection.Open)
|
||||||
|
{
|
||||||
|
openBrackets.Push(type);
|
||||||
|
}
|
||||||
|
else if (direction == TrailDirection.Close)
|
||||||
|
{
|
||||||
|
// Only pop if we have a matching open bracket
|
||||||
|
if (openBrackets.Count > 0 && openBrackets.Peek() == type)
|
||||||
|
{
|
||||||
|
openBrackets.Pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openBrackets.Count == 0)
|
||||||
|
{
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build closing brackets in LIFO order
|
||||||
|
var closingBrackets = new char[openBrackets.Count];
|
||||||
|
var index = 0;
|
||||||
|
|
||||||
|
while (openBrackets.Count > 0)
|
||||||
|
{
|
||||||
|
var type = openBrackets.Pop();
|
||||||
|
closingBrackets[index++] = type == TrailType.Round ? ')' : ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
return query + new string(closingBrackets);
|
||||||
|
}
|
||||||
|
|
||||||
private static (TrailDirection Direction, TrailType Type) BracketTrail(char @char)
|
private static (TrailDirection Direction, TrailType Type) BracketTrail(char @char)
|
||||||
{
|
{
|
||||||
switch (@char)
|
switch (@char)
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
// Copyright (c) Microsoft Corporation
|
||||||
// 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 System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using CalculatorEngineCommon;
|
using CalculatorEngineCommon;
|
||||||
@@ -16,6 +15,7 @@ public static class CalculateEngine
|
|||||||
private static readonly PropertySet _constants = new()
|
private static readonly PropertySet _constants = new()
|
||||||
{
|
{
|
||||||
{ "pi", Math.PI },
|
{ "pi", Math.PI },
|
||||||
|
{ "π", Math.PI },
|
||||||
{ "e", Math.E },
|
{ "e", Math.E },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,6 +59,8 @@ public static class CalculateEngine
|
|||||||
|
|
||||||
input = CalculateHelper.FixHumanMultiplicationExpressions(input);
|
input = CalculateHelper.FixHumanMultiplicationExpressions(input);
|
||||||
|
|
||||||
|
input = CalculateHelper.UpdateFactorialFunctions(input);
|
||||||
|
|
||||||
// Get the user selected trigonometry unit
|
// Get the user selected trigonometry unit
|
||||||
TrigMode trigMode = settings.TrigUnit;
|
TrigMode trigMode = settings.TrigUnit;
|
||||||
|
|
||||||
@@ -77,6 +79,13 @@ public static class CalculateEngine
|
|||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we're out of bounds
|
||||||
|
if (result is "inf" or "-inf")
|
||||||
|
{
|
||||||
|
error = Properties.Resources.calculator_not_covert_to_decimal;
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(result))
|
if (string.IsNullOrEmpty(result))
|
||||||
{
|
{
|
||||||
return default;
|
return default;
|
||||||
@@ -110,15 +119,19 @@ public static class CalculateEngine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static decimal FormatMax15Digits(decimal value, CultureInfo cultureInfo)
|
public static decimal FormatMax15Digits(decimal value, CultureInfo cultureInfo)
|
||||||
{
|
{
|
||||||
|
const int maxDisplayDigits = 15;
|
||||||
|
|
||||||
|
if (value == 0m)
|
||||||
|
{
|
||||||
|
return 0m;
|
||||||
|
}
|
||||||
|
|
||||||
var absValue = Math.Abs(value);
|
var absValue = Math.Abs(value);
|
||||||
var integerDigits = absValue >= 1 ? (int)Math.Floor(Math.Log10((double)absValue)) + 1 : 1;
|
var integerDigits = absValue >= 1 ? (int)Math.Floor(Math.Log10((double)absValue)) + 1 : 1;
|
||||||
|
|
||||||
var maxDecimalDigits = Math.Max(0, 15 - integerDigits);
|
var maxDecimalDigits = Math.Max(0, maxDisplayDigits - integerDigits);
|
||||||
|
|
||||||
var rounded = Math.Round(value, maxDecimalDigits, MidpointRounding.AwayFromZero);
|
var rounded = Math.Round(value, maxDecimalDigits, MidpointRounding.AwayFromZero);
|
||||||
|
return rounded / 1.000000000000000000000000000000000m;
|
||||||
var formatted = rounded.ToString("G29", cultureInfo);
|
|
||||||
|
|
||||||
return Convert.ToDecimal(formatted, cultureInfo);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,12 @@
|
|||||||
// 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 System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||||
|
|
||||||
public static class CalculateHelper
|
public static partial class CalculateHelper
|
||||||
{
|
{
|
||||||
private static readonly Regex RegValidExpressChar = new Regex(
|
private static readonly Regex RegValidExpressChar = new Regex(
|
||||||
@"^(" +
|
@"^(" +
|
||||||
@@ -19,7 +20,7 @@ public static class CalculateHelper
|
|||||||
@"rad\s*\(|deg\s*\(|grad\s*\(|" + /* trigonometry unit conversion macros */
|
@"rad\s*\(|deg\s*\(|grad\s*\(|" + /* trigonometry unit conversion macros */
|
||||||
@"pi|" +
|
@"pi|" +
|
||||||
@"==|~=|&&|\|\||" +
|
@"==|~=|&&|\|\||" +
|
||||||
@"((-?(\d+(\.\d*)?)|-?(\.\d+))[Ee](-?\d+))|" + /* expression from CheckScientificNotation between parenthesis */
|
@"((\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]+|[\+\-\*\/\^\., ""]|[\(\)\|\!\[\]]" +
|
@"e|[0-9]|0[xX][0-9a-fA-F]+|0[bB][01]+|0[oO][0-7]+|[\+\-\*\/\^\., ""]|[\(\)\|\!\[\]]" +
|
||||||
@")+$",
|
@")+$",
|
||||||
RegexOptions.Compiled);
|
RegexOptions.Compiled);
|
||||||
@@ -31,6 +32,94 @@ public static class CalculateHelper
|
|||||||
private const string RadToDeg = "(180 / pi) * ";
|
private const string RadToDeg = "(180 / pi) * ";
|
||||||
private const string RadToGrad = "(200 / pi) * ";
|
private const string RadToGrad = "(200 / pi) * ";
|
||||||
|
|
||||||
|
// replacements from the user input to displayed query
|
||||||
|
private static readonly Dictionary<string, string> QueryReplacements = new()
|
||||||
|
{
|
||||||
|
{ "%", "%" }, { "﹪", "%" },
|
||||||
|
{ "−", "-" }, { "–", "-" }, { "—", "-" },
|
||||||
|
{ "!", "!" },
|
||||||
|
{ "*", "×" }, { "∗", "×" }, { "·", "×" }, { "⊗", "×" }, { "⋅", "×" }, { "✕", "×" }, { "✖", "×" }, { "\u2062", "×" },
|
||||||
|
{ "/", "÷" }, { "∕", "÷" }, { "➗", "÷" }, { ":", "÷" },
|
||||||
|
};
|
||||||
|
|
||||||
|
// replacements from a query to engine input
|
||||||
|
private static readonly Dictionary<string, string> EngineReplacements = new()
|
||||||
|
{
|
||||||
|
{ "×", "*" },
|
||||||
|
{ "÷", "/" },
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly Dictionary<string, string> SuperscriptReplacements = new()
|
||||||
|
{
|
||||||
|
{ "²", "^2" }, { "³", "^3" },
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly HashSet<char> StandardOperators = [
|
||||||
|
|
||||||
|
// binary operators; doesn't make sense for them to be at the end of a query
|
||||||
|
'+', '-', '*', '/', '%', '^', '=', '&', '|', '\\',
|
||||||
|
|
||||||
|
// parentheses
|
||||||
|
'(', '[',
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly HashSet<char> SuffixOperators = [
|
||||||
|
|
||||||
|
// unary operators; can appear at the end of a query
|
||||||
|
')', ']', '!',
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly Regex ReplaceScientificNotationRegex = CreateReplaceScientificNotationRegex();
|
||||||
|
|
||||||
|
public static char[] GetQueryOperators()
|
||||||
|
{
|
||||||
|
var ops = new HashSet<char>(StandardOperators);
|
||||||
|
ops.ExceptWith(SuffixOperators);
|
||||||
|
return [.. ops];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Normalizes the query for display
|
||||||
|
/// This replaces standard operators with more visually appealing ones (e.g., '*' -> '×') if enabled.
|
||||||
|
/// Always applies safe normalizations (standardizing variants like minus, percent, etc.).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">The query string to normalize.</param>
|
||||||
|
public static string NormalizeCharsForDisplayQuery(string input)
|
||||||
|
{
|
||||||
|
// 1. Safe/Trivial replacements (Variant -> Standard)
|
||||||
|
// These are always applied to ensure consistent behavior for non-math symbols (spaces) and
|
||||||
|
// operator variants like minus, percent, and exclamation mark.
|
||||||
|
foreach (var (key, value) in QueryReplacements)
|
||||||
|
{
|
||||||
|
input = input.Replace(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Normalizes the query for the calculation engine.
|
||||||
|
/// This replaces all supported operator variants (visual or standard) with the specific
|
||||||
|
/// ASCII operators required by the engine (e.g., '×' -> '*').
|
||||||
|
/// It duplicates and expands upon replacements in NormalizeQuery to ensure the engine
|
||||||
|
/// receives valid input regardless of whether NormalizeQuery was executed.
|
||||||
|
/// </summary>
|
||||||
|
public static string NormalizeCharsToEngine(string input)
|
||||||
|
{
|
||||||
|
foreach (var (key, value) in EngineReplacements)
|
||||||
|
{
|
||||||
|
input = input.Replace(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace superscript characters with their engine equivalents (e.g., '²' -> '^2')
|
||||||
|
foreach (var (key, value) in SuperscriptReplacements)
|
||||||
|
{
|
||||||
|
input = input.Replace(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
public static bool InputValid(string input)
|
public static bool InputValid(string input)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(input))
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
@@ -50,7 +139,7 @@ public static class CalculateHelper
|
|||||||
|
|
||||||
// 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.
|
// 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();
|
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('%'))
|
if (EndsWithBinaryOperator(trimmedInput))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -58,6 +147,18 @@ public static class CalculateHelper
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool EndsWithBinaryOperator(string input)
|
||||||
|
{
|
||||||
|
var operators = GetQueryOperators();
|
||||||
|
if (string.IsNullOrEmpty(input))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastChar = input[^1];
|
||||||
|
return Array.Exists(operators, op => op == lastChar);
|
||||||
|
}
|
||||||
|
|
||||||
public static string FixHumanMultiplicationExpressions(string input)
|
public static string FixHumanMultiplicationExpressions(string input)
|
||||||
{
|
{
|
||||||
var output = CheckScientificNotation(input);
|
var output = CheckScientificNotation(input);
|
||||||
@@ -72,18 +173,7 @@ public static class CalculateHelper
|
|||||||
|
|
||||||
private static string CheckScientificNotation(string input)
|
private static string CheckScientificNotation(string input)
|
||||||
{
|
{
|
||||||
/**
|
return ReplaceScientificNotationRegex.Replace(input, "($1 * 10^($2))");
|
||||||
* NOTE: By the time that 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -292,6 +382,86 @@ public static class CalculateHelper
|
|||||||
return modifiedInput;
|
return modifiedInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string UpdateFactorialFunctions(string input)
|
||||||
|
{
|
||||||
|
// Handle n! -> factorial(n)
|
||||||
|
int startSearch = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var index = input.IndexOf('!', startSearch);
|
||||||
|
if (index == -1)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore !=
|
||||||
|
if (index + 1 < input.Length && input[index + 1] == '=')
|
||||||
|
{
|
||||||
|
startSearch = index + 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index == 0)
|
||||||
|
{
|
||||||
|
startSearch = index + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan backwards
|
||||||
|
var endArg = index - 1;
|
||||||
|
while (endArg >= 0 && char.IsWhiteSpace(input[endArg]))
|
||||||
|
{
|
||||||
|
endArg--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endArg < 0)
|
||||||
|
{
|
||||||
|
startSearch = index + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var startArg = endArg;
|
||||||
|
if (input[endArg] == ')')
|
||||||
|
{
|
||||||
|
// Find matching '('
|
||||||
|
startArg = FindOpeningBracketIndexInFrontOfIndex(input, endArg);
|
||||||
|
if (startArg == -1)
|
||||||
|
{
|
||||||
|
startSearch = index + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Scan back for number or word
|
||||||
|
while (startArg >= 0 && (char.IsLetterOrDigit(input[startArg]) || input[startArg] == '.'))
|
||||||
|
{
|
||||||
|
startArg--;
|
||||||
|
}
|
||||||
|
|
||||||
|
startArg++; // Move back to first valid char
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startArg > endArg)
|
||||||
|
{
|
||||||
|
// No argument found
|
||||||
|
startSearch = index + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract argument
|
||||||
|
var arg = input.Substring(startArg, endArg - startArg + 1);
|
||||||
|
|
||||||
|
// Replace <arg><whitespace>! with factorial(<arg>)
|
||||||
|
input = input.Remove(startArg, index - startArg + 1);
|
||||||
|
input = input.Insert(startArg, $"factorial({arg})");
|
||||||
|
|
||||||
|
startSearch = 0; // Reset search because string changed
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
private static string ModifyMathFunction(string input, string function, string modification)
|
private static string ModifyMathFunction(string input, string function, string modification)
|
||||||
{
|
{
|
||||||
// Create the pattern to match the function, opening bracket, and any spaces in between
|
// Create the pattern to match the function, opening bracket, and any spaces in between
|
||||||
@@ -325,4 +495,43 @@ public static class CalculateHelper
|
|||||||
|
|
||||||
return modifiedInput;
|
return modifiedInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int FindOpeningBracketIndexInFrontOfIndex(string input, int end)
|
||||||
|
{
|
||||||
|
var bracketCount = 0;
|
||||||
|
for (var i = end; i >= 0; i--)
|
||||||
|
{
|
||||||
|
switch (input[i])
|
||||||
|
{
|
||||||
|
case ')':
|
||||||
|
bracketCount++;
|
||||||
|
break;
|
||||||
|
case '(':
|
||||||
|
{
|
||||||
|
bracketCount--;
|
||||||
|
if (bracketCount == 0)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NOTE: By the time that 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")
|
||||||
|
*/
|
||||||
|
[GeneratedRegex(@"(\d+(?:\.\d*)?|\.\d+)e(-?\d+)", RegexOptions.IgnoreCase, "en-US")]
|
||||||
|
private static partial Regex CreateReplaceScientificNotationRegex();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
// 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 Microsoft.CmdPal.Ext.Calc.Helper;
|
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||||
|
|
||||||
public interface ISettingsInterface
|
public interface ISettingsInterface
|
||||||
@@ -15,4 +13,8 @@ public interface ISettingsInterface
|
|||||||
public bool OutputUseEnglishFormat { get; }
|
public bool OutputUseEnglishFormat { get; }
|
||||||
|
|
||||||
public bool CloseOnEnter { get; }
|
public bool CloseOnEnter { get; }
|
||||||
|
|
||||||
|
public bool CopyResultToSearchBarIfQueryEndsWithEqualSign { get; }
|
||||||
|
|
||||||
|
public bool AutoFixQuery { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
|||||||
|
|
||||||
public static partial class QueryHelper
|
public static partial class QueryHelper
|
||||||
{
|
{
|
||||||
public static ListItem Query(string query, ISettingsInterface settings, bool isFallbackSearch, TypedEventHandler<object, object> handleSave = null)
|
public static ListItem Query(
|
||||||
|
string query,
|
||||||
|
ISettingsInterface settings,
|
||||||
|
bool isFallbackSearch,
|
||||||
|
out string displayQuery,
|
||||||
|
TypedEventHandler<object, object> handleSave = null,
|
||||||
|
TypedEventHandler<object, object> handleReplace = null)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(query);
|
ArgumentNullException.ThrowIfNull(query);
|
||||||
if (!isFallbackSearch)
|
if (!isFallbackSearch)
|
||||||
@@ -20,26 +26,50 @@ public static partial class QueryHelper
|
|||||||
ArgumentNullException.ThrowIfNull(handleSave);
|
ArgumentNullException.ThrowIfNull(handleSave);
|
||||||
}
|
}
|
||||||
|
|
||||||
CultureInfo inputCulture = settings.InputUseEnglishFormat ? new CultureInfo("en-us") : CultureInfo.CurrentCulture;
|
CultureInfo inputCulture =
|
||||||
CultureInfo outputCulture = settings.OutputUseEnglishFormat ? new CultureInfo("en-us") : CultureInfo.CurrentCulture;
|
settings.InputUseEnglishFormat ? new CultureInfo("en-us") : CultureInfo.CurrentCulture;
|
||||||
|
CultureInfo outputCulture =
|
||||||
|
settings.OutputUseEnglishFormat ? new CultureInfo("en-us") : CultureInfo.CurrentCulture;
|
||||||
|
|
||||||
// In case the user pastes a query with a leading =
|
// In case the user pastes a query with a leading =
|
||||||
query = query.TrimStart('=');
|
query = query.TrimStart('=').TrimStart();
|
||||||
|
|
||||||
|
// Enables better looking characters for multiplication and division (e.g., '×' and '÷')
|
||||||
|
displayQuery = CalculateHelper.NormalizeCharsForDisplayQuery(query);
|
||||||
|
|
||||||
// Happens if the user has only typed the action key so far
|
// Happens if the user has only typed the action key so far
|
||||||
if (string.IsNullOrEmpty(query))
|
if (string.IsNullOrEmpty(displayQuery))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
NumberTranslator translator = NumberTranslator.Create(inputCulture, new CultureInfo("en-US"));
|
// Normalize query to engine format (e.g., replace '×' with '*', converts superscripts to functions)
|
||||||
var input = translator.Translate(query.Normalize(NormalizationForm.FormKC));
|
// This must be done before any further normalization to avoid losing information
|
||||||
|
var engineQuery = CalculateHelper.NormalizeCharsToEngine(displayQuery);
|
||||||
|
|
||||||
|
// Cleanup rest of the Unicode characters, whitespace
|
||||||
|
var queryForEngine2 = engineQuery.Normalize(NormalizationForm.FormKC);
|
||||||
|
|
||||||
|
// Translate numbers from input culture to en-US culture for the calculation engine
|
||||||
|
var translator = NumberTranslator.Create(inputCulture, new CultureInfo("en-US"));
|
||||||
|
|
||||||
|
// Translate the input query
|
||||||
|
var input = translator.Translate(queryForEngine2);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(input))
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
{
|
{
|
||||||
return ErrorHandler.OnError(isFallbackSearch, query, Properties.Resources.calculator_expression_empty);
|
return ErrorHandler.OnError(isFallbackSearch, query, Properties.Resources.calculator_expression_empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// normalize again to engine chars after translation
|
||||||
|
input = CalculateHelper.NormalizeCharsToEngine(input);
|
||||||
|
|
||||||
|
// Auto fix incomplete queries (if enabled)
|
||||||
|
if (settings.AutoFixQuery && TryGetIncompleteQuery(input, out var newInput))
|
||||||
|
{
|
||||||
|
input = newInput;
|
||||||
|
}
|
||||||
|
|
||||||
if (!CalculateHelper.InputValid(input))
|
if (!CalculateHelper.InputValid(input))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@@ -60,10 +90,10 @@ public static partial class QueryHelper
|
|||||||
if (isFallbackSearch)
|
if (isFallbackSearch)
|
||||||
{
|
{
|
||||||
// Fallback search
|
// Fallback search
|
||||||
return ResultHelper.CreateResult(result.RoundedResult, inputCulture, outputCulture, query);
|
return ResultHelper.CreateResult(result.RoundedResult, inputCulture, outputCulture, displayQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResultHelper.CreateResult(result.RoundedResult, inputCulture, outputCulture, query, settings, handleSave);
|
return ResultHelper.CreateResult(result.RoundedResult, inputCulture, outputCulture, displayQuery, settings, handleSave, handleReplace);
|
||||||
}
|
}
|
||||||
catch (OverflowException)
|
catch (OverflowException)
|
||||||
{
|
{
|
||||||
@@ -77,4 +107,32 @@ public static partial class QueryHelper
|
|||||||
return ErrorHandler.OnError(isFallbackSearch, query, default, e);
|
return ErrorHandler.OnError(isFallbackSearch, query, default, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool TryGetIncompleteQuery(string query, out string newQuery)
|
||||||
|
{
|
||||||
|
newQuery = query;
|
||||||
|
|
||||||
|
var trimmed = query.TrimEnd();
|
||||||
|
if (string.IsNullOrEmpty(trimmed))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Trim trailing operators
|
||||||
|
var operators = CalculateHelper.GetQueryOperators();
|
||||||
|
while (trimmed.Length > 0 && Array.IndexOf(operators, trimmed[^1]) > -1)
|
||||||
|
{
|
||||||
|
trimmed = trimmed[..^1].TrimEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmed.Length == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Fix brackets
|
||||||
|
newQuery = BracketHelper.BalanceBrackets(trimmed);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// 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;
|
||||||
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
using Windows.Foundation;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||||
|
|
||||||
|
public sealed partial class ReplaceQueryCommand : InvokableCommand
|
||||||
|
{
|
||||||
|
public event TypedEventHandler<object, object> ReplaceRequested;
|
||||||
|
|
||||||
|
public ReplaceQueryCommand()
|
||||||
|
{
|
||||||
|
Name = "Replace query";
|
||||||
|
Icon = new IconInfo("\uE70F"); // Edit icon
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ICommandResult Invoke()
|
||||||
|
{
|
||||||
|
ReplaceRequested?.Invoke(this, null);
|
||||||
|
return CommandResult.KeepOpen();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
|
using Microsoft.CommandPalette.Extensions;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
using Windows.Foundation;
|
using Windows.Foundation;
|
||||||
|
|
||||||
@@ -13,7 +14,14 @@ namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
|||||||
|
|
||||||
public static class ResultHelper
|
public static class ResultHelper
|
||||||
{
|
{
|
||||||
public static ListItem CreateResult(decimal? roundedResult, CultureInfo inputCulture, CultureInfo outputCulture, string query, ISettingsInterface settings, TypedEventHandler<object, object> handleSave)
|
public static ListItem CreateResult(
|
||||||
|
decimal? roundedResult,
|
||||||
|
CultureInfo inputCulture,
|
||||||
|
CultureInfo outputCulture,
|
||||||
|
string query,
|
||||||
|
ISettingsInterface settings,
|
||||||
|
TypedEventHandler<object, object> handleSave,
|
||||||
|
TypedEventHandler<object, object> handleReplace)
|
||||||
{
|
{
|
||||||
// Return null when the expression is not a valid calculator query.
|
// Return null when the expression is not a valid calculator query.
|
||||||
if (roundedResult is null)
|
if (roundedResult is null)
|
||||||
@@ -28,6 +36,9 @@ public static class ResultHelper
|
|||||||
var saveCommand = new SaveCommand(result);
|
var saveCommand = new SaveCommand(result);
|
||||||
saveCommand.SaveRequested += handleSave;
|
saveCommand.SaveRequested += handleSave;
|
||||||
|
|
||||||
|
var replaceCommand = new ReplaceQueryCommand();
|
||||||
|
replaceCommand.ReplaceRequested += handleReplace;
|
||||||
|
|
||||||
var copyCommandItem = CreateResult(roundedResult, inputCulture, outputCulture, query);
|
var copyCommandItem = CreateResult(roundedResult, inputCulture, outputCulture, query);
|
||||||
|
|
||||||
// No TextToSuggest on the main save command item. We don't want to keep suggesting what the result is,
|
// No TextToSuggest on the main save command item. We don't want to keep suggesting what the result is,
|
||||||
@@ -40,6 +51,7 @@ public static class ResultHelper
|
|||||||
Subtitle = query,
|
Subtitle = query,
|
||||||
MoreCommands = [
|
MoreCommands = [
|
||||||
new CommandContextItem(settings.CloseOnEnter ? saveCommand : copyCommandItem.Command),
|
new CommandContextItem(settings.CloseOnEnter ? saveCommand : copyCommandItem.Command),
|
||||||
|
new CommandContextItem(replaceCommand) { RequestedShortcut = KeyChords.CopyResultToSearchBox, },
|
||||||
..copyCommandItem.MoreCommands,
|
..copyCommandItem.MoreCommands,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -55,11 +67,15 @@ public static class ResultHelper
|
|||||||
|
|
||||||
var decimalResult = roundedResult?.ToString(outputCulture);
|
var decimalResult = roundedResult?.ToString(outputCulture);
|
||||||
|
|
||||||
List<CommandContextItem> context = [];
|
List<IContextItem> context = [];
|
||||||
|
|
||||||
if (decimal.IsInteger((decimal)roundedResult))
|
if (decimal.IsInteger((decimal)roundedResult))
|
||||||
{
|
{
|
||||||
|
context.Add(new Separator());
|
||||||
|
|
||||||
var i = decimal.ToInt64((decimal)roundedResult);
|
var i = decimal.ToInt64((decimal)roundedResult);
|
||||||
|
|
||||||
|
// hexadecimal
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var hexResult = "0x" + i.ToString("X", outputCulture);
|
var hexResult = "0x" + i.ToString("X", outputCulture);
|
||||||
@@ -70,9 +86,10 @@ public static class ResultHelper
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError("Error parsing hex format", ex);
|
Logger.LogError("Error converting to hex format", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// binary
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var binaryResult = "0b" + i.ToString("B", outputCulture);
|
var binaryResult = "0b" + i.ToString("B", outputCulture);
|
||||||
@@ -83,7 +100,21 @@ public static class ResultHelper
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError("Error parsing binary format", ex);
|
Logger.LogError("Error converting to binary format", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// octal
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var octalResult = "0o" + Convert.ToString(i, 8);
|
||||||
|
context.Add(new CommandContextItem(new CopyTextCommand(octalResult) { Name = Properties.Resources.calculator_copy_octal })
|
||||||
|
{
|
||||||
|
Title = octalResult,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError("Error converting to octal format", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,18 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
|||||||
Properties.Resources.calculator_settings_close_on_enter_description,
|
Properties.Resources.calculator_settings_close_on_enter_description,
|
||||||
true);
|
true);
|
||||||
|
|
||||||
|
private readonly ToggleSetting _copyResultToSearchBarIfQueryEndsWithEqualSign = new(
|
||||||
|
Namespaced(nameof(CopyResultToSearchBarIfQueryEndsWithEqualSign)),
|
||||||
|
Properties.Resources.calculator_settings_copy_result_to_search_bar,
|
||||||
|
Properties.Resources.calculator_settings_copy_result_to_search_bar_description,
|
||||||
|
false);
|
||||||
|
|
||||||
|
private readonly ToggleSetting _autoFixQuery = new(
|
||||||
|
Namespaced(nameof(AutoFixQuery)),
|
||||||
|
Properties.Resources.calculator_settings_auto_fix_query,
|
||||||
|
Properties.Resources.calculator_settings_auto_fix_query_description,
|
||||||
|
true);
|
||||||
|
|
||||||
public CalculateEngine.TrigMode TrigUnit
|
public CalculateEngine.TrigMode TrigUnit
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -81,6 +93,10 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
|||||||
|
|
||||||
public bool CloseOnEnter => _closeOnEnter.Value;
|
public bool CloseOnEnter => _closeOnEnter.Value;
|
||||||
|
|
||||||
|
public bool CopyResultToSearchBarIfQueryEndsWithEqualSign => _copyResultToSearchBarIfQueryEndsWithEqualSign.Value;
|
||||||
|
|
||||||
|
public bool AutoFixQuery => _autoFixQuery.Value;
|
||||||
|
|
||||||
internal static string SettingsJsonPath()
|
internal static string SettingsJsonPath()
|
||||||
{
|
{
|
||||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||||
@@ -98,6 +114,8 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
|||||||
Settings.Add(_inputUseEnNumberFormat);
|
Settings.Add(_inputUseEnNumberFormat);
|
||||||
Settings.Add(_outputUseEnNumberFormat);
|
Settings.Add(_outputUseEnNumberFormat);
|
||||||
Settings.Add(_closeOnEnter);
|
Settings.Add(_closeOnEnter);
|
||||||
|
Settings.Add(_copyResultToSearchBarIfQueryEndsWithEqualSign);
|
||||||
|
Settings.Add(_autoFixQuery);
|
||||||
|
|
||||||
// Load settings from file upon initialization
|
// Load settings from file upon initialization
|
||||||
LoadSettings();
|
LoadSettings();
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// 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;
|
||||||
|
using Windows.System;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Calc;
|
||||||
|
|
||||||
|
internal static class KeyChords
|
||||||
|
{
|
||||||
|
internal static KeyChord CopyResultToSearchBox { get; } = new(VirtualKeyModifiers.Control | VirtualKeyModifiers.Shift, (int)VirtualKey.Enter, 0);
|
||||||
|
}
|
||||||
@@ -25,12 +25,12 @@ public sealed partial class CalculatorListPage : DynamicListPage
|
|||||||
private readonly Lock _resultsLock = new();
|
private readonly Lock _resultsLock = new();
|
||||||
private readonly ISettingsInterface _settingsManager;
|
private readonly ISettingsInterface _settingsManager;
|
||||||
private readonly List<ListItem> _items = [];
|
private readonly List<ListItem> _items = [];
|
||||||
private readonly List<ListItem> history = [];
|
private readonly List<ListItem> _history = [];
|
||||||
private readonly ListItem _emptyItem;
|
private readonly ListItem _emptyItem;
|
||||||
|
|
||||||
// This is the text that saved when the user click the result.
|
// 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.
|
// We need to avoid the double calculation. This may cause some wierd behaviors.
|
||||||
private string skipQuerySearchText = string.Empty;
|
private string _skipQuerySearchText = string.Empty;
|
||||||
|
|
||||||
public CalculatorListPage(ISettingsInterface settings)
|
public CalculatorListPage(ISettingsInterface settings)
|
||||||
{
|
{
|
||||||
@@ -54,6 +54,17 @@ public sealed partial class CalculatorListPage : DynamicListPage
|
|||||||
UpdateSearchText(string.Empty, string.Empty);
|
UpdateSearchText(string.Empty, string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleReplaceQuery(object sender, object args)
|
||||||
|
{
|
||||||
|
var lastResult = _items[0].Title;
|
||||||
|
if (!string.IsNullOrEmpty(lastResult))
|
||||||
|
{
|
||||||
|
_skipQuerySearchText = lastResult;
|
||||||
|
SearchText = lastResult;
|
||||||
|
OnPropertyChanged(nameof(SearchText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void UpdateSearchText(string oldSearch, string newSearch)
|
public override void UpdateSearchText(string oldSearch, string newSearch)
|
||||||
{
|
{
|
||||||
if (oldSearch == newSearch)
|
if (oldSearch == newSearch)
|
||||||
@@ -61,19 +72,37 @@ public sealed partial class CalculatorListPage : DynamicListPage
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(skipQuerySearchText) && newSearch == skipQuerySearchText)
|
if (!string.IsNullOrEmpty(_skipQuerySearchText) && newSearch == _skipQuerySearchText)
|
||||||
{
|
{
|
||||||
// only skip once.
|
// only skip once.
|
||||||
skipQuerySearchText = string.Empty;
|
_skipQuerySearchText = string.Empty;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
skipQuerySearchText = string.Empty;
|
var copyResultToSearchText = false;
|
||||||
|
if (_settingsManager.CopyResultToSearchBarIfQueryEndsWithEqualSign && newSearch.EndsWith('='))
|
||||||
|
{
|
||||||
|
newSearch = newSearch.TrimEnd('=').TrimEnd();
|
||||||
|
copyResultToSearchText = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_skipQuerySearchText = string.Empty;
|
||||||
|
|
||||||
_emptyItem.Subtitle = newSearch;
|
_emptyItem.Subtitle = newSearch;
|
||||||
|
|
||||||
var result = QueryHelper.Query(newSearch, _settingsManager, false, HandleSave);
|
var result = QueryHelper.Query(newSearch, _settingsManager, isFallbackSearch: false, out var displayQuery, HandleSave, HandleReplaceQuery);
|
||||||
|
|
||||||
UpdateResult(result);
|
UpdateResult(result);
|
||||||
|
|
||||||
|
if (copyResultToSearchText && result is not null)
|
||||||
|
{
|
||||||
|
_skipQuerySearchText = result.Title;
|
||||||
|
SearchText = result.Title;
|
||||||
|
|
||||||
|
// LOAD BEARING: The SearchText setter does not raise a PropertyChanged notification,
|
||||||
|
// so we must raise it explicitly to ensure the UI updates correctly.
|
||||||
|
OnPropertyChanged(nameof(SearchText));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateResult(ListItem result)
|
private void UpdateResult(ListItem result)
|
||||||
@@ -91,7 +120,7 @@ public sealed partial class CalculatorListPage : DynamicListPage
|
|||||||
_items.Add(_emptyItem);
|
_items.Add(_emptyItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._items.AddRange(history);
|
this._items.AddRange(_history);
|
||||||
}
|
}
|
||||||
|
|
||||||
RaiseItemsChanged(this._items.Count);
|
RaiseItemsChanged(this._items.Count);
|
||||||
@@ -109,7 +138,7 @@ public sealed partial class CalculatorListPage : DynamicListPage
|
|||||||
TextToSuggest = lastResult,
|
TextToSuggest = lastResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
history.Insert(0, li);
|
_history.Insert(0, li);
|
||||||
_items.Insert(1, li);
|
_items.Insert(1, li);
|
||||||
|
|
||||||
// Why we need to clean the query record? Removed, but if necessary, please move it back.
|
// Why we need to clean the query record? Removed, but if necessary, please move it back.
|
||||||
@@ -117,9 +146,14 @@ public sealed partial class CalculatorListPage : DynamicListPage
|
|||||||
|
|
||||||
// this change will call the UpdateSearchText again.
|
// this change will call the UpdateSearchText again.
|
||||||
// We need to avoid it.
|
// We need to avoid it.
|
||||||
skipQuerySearchText = lastResult;
|
_skipQuerySearchText = lastResult;
|
||||||
SearchText = lastResult;
|
SearchText = lastResult;
|
||||||
this.RaiseItemsChanged(this._items.Count);
|
|
||||||
|
// LOAD BEARING: The SearchText setter does not raise a PropertyChanged notification,
|
||||||
|
// so we must raise it explicitly to ensure the UI updates correctly.
|
||||||
|
OnPropertyChanged(nameof(SearchText));
|
||||||
|
|
||||||
|
RaiseItemsChanged(this._items.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public sealed partial class FallbackCalculatorItem : FallbackCommandItem
|
|||||||
|
|
||||||
public override void UpdateQuery(string query)
|
public override void UpdateQuery(string query)
|
||||||
{
|
{
|
||||||
var result = QueryHelper.Query(query, _settings, true, null);
|
var result = QueryHelper.Query(query, _settings, true, out _);
|
||||||
|
|
||||||
if (result is null)
|
if (result is null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace Microsoft.CmdPal.Ext.Calc.Properties {
|
|||||||
// class via a tool like ResGen or Visual Studio.
|
// class via a tool like ResGen or Visual Studio.
|
||||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
// with the /str option, or rebuild your VS project.
|
// with the /str option, or rebuild your VS project.
|
||||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
public class Resources {
|
public class Resources {
|
||||||
@@ -96,6 +96,15 @@ namespace Microsoft.CmdPal.Ext.Calc.Properties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Copy octal.
|
||||||
|
/// </summary>
|
||||||
|
public static string calculator_copy_octal {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("calculator_copy_octal", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Calculator.
|
/// Looks up a localized string similar to Calculator.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -186,6 +195,24 @@ namespace Microsoft.CmdPal.Ext.Calc.Properties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Fix incomplete calculations automatically.
|
||||||
|
/// </summary>
|
||||||
|
public static string calculator_settings_auto_fix_query {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("calculator_settings_auto_fix_query", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Attempt to evaluate incomplete calculations by ignoring extra operators or symbols.
|
||||||
|
/// </summary>
|
||||||
|
public static string calculator_settings_auto_fix_query_description {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("calculator_settings_auto_fix_query_description", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Close on Enter.
|
/// Looks up a localized string similar to Close on Enter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -204,6 +231,24 @@ namespace Microsoft.CmdPal.Ext.Calc.Properties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Replace query with result on equals.
|
||||||
|
/// </summary>
|
||||||
|
public static string calculator_settings_copy_result_to_search_bar {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("calculator_settings_copy_result_to_search_bar", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Updates the query to the result when (=) is entered.
|
||||||
|
/// </summary>
|
||||||
|
public static string calculator_settings_copy_result_to_search_bar_description {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("calculator_settings_copy_result_to_search_bar_description", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Use English (United States) number format for input.
|
/// Looks up a localized string similar to Use English (United States) number format for input.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -222,6 +267,24 @@ namespace Microsoft.CmdPal.Ext.Calc.Properties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Handle extra operators and symbols.
|
||||||
|
/// </summary>
|
||||||
|
public static string calculator_settings_input_normalization {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("calculator_settings_input_normalization", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Enable advanced input normalization and extra symbols (e.g. ÷, ×, π).
|
||||||
|
/// </summary>
|
||||||
|
public static string calculator_settings_input_normalization_description {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("calculator_settings_input_normalization_description", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Use English (United States) number format for output.
|
/// Looks up a localized string similar to Use English (United States) number format for output.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -208,4 +208,25 @@
|
|||||||
<data name="calculator_expression_empty" xml:space="preserve">
|
<data name="calculator_expression_empty" xml:space="preserve">
|
||||||
<value>Please enter an expression</value>
|
<value>Please enter an expression</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="calculator_settings_copy_result_to_search_bar" xml:space="preserve">
|
||||||
|
<value>Replace query with result on equals</value>
|
||||||
|
</data>
|
||||||
|
<data name="calculator_settings_copy_result_to_search_bar_description" xml:space="preserve">
|
||||||
|
<value>Updates the query to the result when (=) is entered</value>
|
||||||
|
</data>
|
||||||
|
<data name="calculator_settings_auto_fix_query" xml:space="preserve">
|
||||||
|
<value>Fix incomplete calculations automatically</value>
|
||||||
|
</data>
|
||||||
|
<data name="calculator_settings_auto_fix_query_description" xml:space="preserve">
|
||||||
|
<value>Attempt to evaluate incomplete calculations by ignoring extra operators or symbols</value>
|
||||||
|
</data>
|
||||||
|
<data name="calculator_settings_input_normalization" xml:space="preserve">
|
||||||
|
<value>Handle extra operators and symbols</value>
|
||||||
|
</data>
|
||||||
|
<data name="calculator_settings_input_normalization_description" xml:space="preserve">
|
||||||
|
<value>Enable advanced input normalization and extra symbols (e.g. ÷, ×, π)</value>
|
||||||
|
</data>
|
||||||
|
<data name="calculator_copy_octal" xml:space="preserve">
|
||||||
|
<value>Copy octal</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
Reference in New Issue
Block a user