mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 01:36:31 +02:00
CmdPal: Fix exception when converting calc result to different bases (#46176)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request This PR fixes an exception that prevents showing result for big items: - Uses `BigInteger` and custom base converter for secondary results menu items. - Adds extra error handler to prevent exception when creating a secondary menu item from showing the main result to the user. - Adds some unit tests for the new base converter. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #46167 <!-- - [ ] 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 - [ ] **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:
@@ -0,0 +1,145 @@
|
|||||||
|
// 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.Numerics;
|
||||||
|
using Microsoft.CmdPal.Ext.Calc.Helper;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Calc.UnitTests;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class BaseConverterTests
|
||||||
|
{
|
||||||
|
// Hex tests
|
||||||
|
[DataTestMethod]
|
||||||
|
[DataRow(0L, "0x0")]
|
||||||
|
[DataRow(1L, "0x1")]
|
||||||
|
[DataRow(16L, "0x10")]
|
||||||
|
[DataRow(255L, "0xFF")]
|
||||||
|
[DataRow(-1L, "-0x1")]
|
||||||
|
[DataRow(long.MaxValue, "0x7FFFFFFFFFFFFFFF")]
|
||||||
|
public void Convert_Hex_ReturnsExpected_WhenCalled(long input, string expected)
|
||||||
|
{
|
||||||
|
var result = BaseConverter.Convert(new BigInteger(input), 16);
|
||||||
|
Assert.AreEqual(expected, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Convert_Hex_HandlesLargeValues_WhenCalled()
|
||||||
|
{
|
||||||
|
var large = BigInteger.Parse("99999999999999999999", CultureInfo.InvariantCulture);
|
||||||
|
var result = BaseConverter.Convert(large, 16);
|
||||||
|
Assert.AreEqual("0x56BC75E2D630FFFFF", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary tests
|
||||||
|
[DataTestMethod]
|
||||||
|
[DataRow(0L, "0b0")]
|
||||||
|
[DataRow(1L, "0b1")]
|
||||||
|
[DataRow(10L, "0b1010")]
|
||||||
|
[DataRow(255L, "0b11111111")]
|
||||||
|
[DataRow(-5L, "-0b101")]
|
||||||
|
public void Convert_Binary_ReturnsExpected_WhenCalled(long input, string expected)
|
||||||
|
{
|
||||||
|
var result = BaseConverter.Convert(new BigInteger(input), 2);
|
||||||
|
Assert.AreEqual(expected, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Convert_Binary_HandlesLargeValues_WhenCalled()
|
||||||
|
{
|
||||||
|
var large = BigInteger.Parse("99999999999999999999", CultureInfo.InvariantCulture);
|
||||||
|
var result = BaseConverter.Convert(large, 2);
|
||||||
|
Assert.IsTrue(result.StartsWith("0b", StringComparison.Ordinal));
|
||||||
|
Assert.IsTrue(result.Length > 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Octal tests
|
||||||
|
[DataTestMethod]
|
||||||
|
[DataRow(0L, "0o0")]
|
||||||
|
[DataRow(1L, "0o1")]
|
||||||
|
[DataRow(8L, "0o10")]
|
||||||
|
[DataRow(255L, "0o377")]
|
||||||
|
[DataRow(-1L, "-0o1")]
|
||||||
|
[DataRow(-255L, "-0o377")]
|
||||||
|
[DataRow(long.MaxValue, "0o777777777777777777777")]
|
||||||
|
public void Convert_Octal_ReturnsExpected_WhenCalled(long input, string expected)
|
||||||
|
{
|
||||||
|
var result = BaseConverter.Convert(new BigInteger(input), 8);
|
||||||
|
Assert.AreEqual(expected, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Convert_Octal_HandlesLargeValues_WhenCalled()
|
||||||
|
{
|
||||||
|
var large = (BigInteger)long.MaxValue + 1;
|
||||||
|
var result = BaseConverter.Convert(large, 8);
|
||||||
|
Assert.AreEqual("0o1000000000000000000000", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Convert_Octal_HandlesLargeNegativeValues_WhenCalled()
|
||||||
|
{
|
||||||
|
var value = -BigInteger.Parse("99999999999999999999", CultureInfo.InvariantCulture);
|
||||||
|
var result = BaseConverter.Convert(value, 8);
|
||||||
|
Assert.IsTrue(result.StartsWith("-0o", StringComparison.Ordinal));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Convert_Hex_DecimalMaxValue_WhenCalled()
|
||||||
|
{
|
||||||
|
var value = BigInteger.Parse("79228162514264337593543950335", CultureInfo.InvariantCulture);
|
||||||
|
Assert.AreEqual("0xFFFFFFFFFFFFFFFFFFFFFFFF", BaseConverter.Convert(value, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Convert_Hex_NegativeDecimalMaxValue_WhenCalled()
|
||||||
|
{
|
||||||
|
var value = -BigInteger.Parse("79228162514264337593543950335", CultureInfo.InvariantCulture);
|
||||||
|
Assert.AreEqual("-0xFFFFFFFFFFFFFFFFFFFFFFFF", BaseConverter.Convert(value, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Convert_Binary_DecimalMaxValue_WhenCalled()
|
||||||
|
{
|
||||||
|
var value = BigInteger.Parse("79228162514264337593543950335", CultureInfo.InvariantCulture);
|
||||||
|
var result = BaseConverter.Convert(value, 2);
|
||||||
|
Assert.AreEqual("0b" + new string('1', 96), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Convert_Binary_NegativeDecimalMaxValue_WhenCalled()
|
||||||
|
{
|
||||||
|
var value = -BigInteger.Parse("79228162514264337593543950335", CultureInfo.InvariantCulture);
|
||||||
|
var result = BaseConverter.Convert(value, 2);
|
||||||
|
Assert.AreEqual("-0b" + new string('1', 96), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Convert_Octal_DecimalMaxValue_WhenCalled()
|
||||||
|
{
|
||||||
|
var value = BigInteger.Parse("79228162514264337593543950335", CultureInfo.InvariantCulture);
|
||||||
|
Assert.AreEqual("0o" + new string('7', 32), BaseConverter.Convert(value, 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Convert_Octal_NegativeDecimalMaxValue_WhenCalled()
|
||||||
|
{
|
||||||
|
var value = -BigInteger.Parse("79228162514264337593543950335", CultureInfo.InvariantCulture);
|
||||||
|
Assert.AreEqual("-0o" + new string('7', 32), BaseConverter.Convert(value, 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid base
|
||||||
|
[DataTestMethod]
|
||||||
|
[DataRow(0)]
|
||||||
|
[DataRow(1)]
|
||||||
|
[DataRow(17)]
|
||||||
|
[DataRow(-1)]
|
||||||
|
public void Convert_ThrowsArgumentOutOfRange_WhenBaseInvalid(int toBase)
|
||||||
|
{
|
||||||
|
Assert.ThrowsException<ArgumentOutOfRangeException>(() => BaseConverter.Convert(BigInteger.One, toBase));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ 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;
|
||||||
@@ -82,4 +83,24 @@ public class QueryTests : CommandPaletteUnitTestBase
|
|||||||
|
|
||||||
Assert.IsTrue(result.Title.Contains(expected, System.StringComparison.Ordinal), $"Calc trigMode convert result isn't correct. Current result: {result.Title}");
|
Assert.IsTrue(result.Title.Contains(expected, System.StringComparison.Ordinal), $"Calc trigMode convert result isn't correct. Current result: {result.Title}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DataTestMethod]
|
||||||
|
[DataRow("2^64", "18446744073709551616", "0x10000000000000000")]
|
||||||
|
[DataRow("0-(2^64)", "-18446744073709551616", "-0x10000000000000000")]
|
||||||
|
public void TopLevelPageQuery_ReturnsResult_WhenIntegerExceedsInt64Bounds(string input, string expectedTitle, string expectedHexContext)
|
||||||
|
{
|
||||||
|
var settings = new Settings(outputUseEnglishFormat: true);
|
||||||
|
var page = new CalculatorListPage(settings);
|
||||||
|
|
||||||
|
page.UpdateSearchText(string.Empty, input);
|
||||||
|
var results = page.GetItems();
|
||||||
|
var result = results.FirstOrDefault();
|
||||||
|
|
||||||
|
Assert.AreEqual(1, results.Length, "Large integer results should still produce the main result item.");
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
Assert.AreEqual(expectedTitle, result!.Title);
|
||||||
|
Assert.IsTrue(
|
||||||
|
result.MoreCommands.OfType<CommandContextItem>().Any(item => item.Title == expectedHexContext),
|
||||||
|
"Large integer results should still include integer conversion context items.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// 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.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Calc.Helper;
|
||||||
|
|
||||||
|
public static class BaseConverter
|
||||||
|
{
|
||||||
|
private const string Digits = "0123456789ABCDEF";
|
||||||
|
|
||||||
|
public static string Convert(BigInteger value, int toBase)
|
||||||
|
{
|
||||||
|
var prefix = toBase switch
|
||||||
|
{
|
||||||
|
2 => "0b",
|
||||||
|
8 => "0o",
|
||||||
|
16 => "0x",
|
||||||
|
_ => string.Empty,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (toBase is < 2 or > 16)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(toBase), "Base must be between 2 and 16.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == BigInteger.Zero)
|
||||||
|
{
|
||||||
|
return prefix + "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
var abs = BigInteger.Abs(value);
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
while (abs > 0)
|
||||||
|
{
|
||||||
|
var digit = (int)(abs % toBase);
|
||||||
|
sb.Insert(0, Digits[digit]);
|
||||||
|
abs /= toBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sign = value < 0 ? "-" : string.Empty;
|
||||||
|
|
||||||
|
return sign + prefix + sb;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Numerics;
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using Microsoft.CommandPalette.Extensions;
|
using Microsoft.CommandPalette.Extensions;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
@@ -66,57 +67,65 @@ public static class ResultHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
var decimalResult = roundedResult?.ToString(outputCulture);
|
var decimalResult = roundedResult?.ToString(outputCulture);
|
||||||
|
var decimalValue = (decimal)roundedResult;
|
||||||
|
|
||||||
List<IContextItem> context = [];
|
List<IContextItem> context = [];
|
||||||
|
|
||||||
if (decimal.IsInteger((decimal)roundedResult))
|
try
|
||||||
{
|
{
|
||||||
context.Add(new Separator());
|
if (decimal.IsInteger(decimalValue))
|
||||||
|
|
||||||
var i = decimal.ToInt64((decimal)roundedResult);
|
|
||||||
|
|
||||||
// hexadecimal
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var hexResult = "0x" + i.ToString("X", outputCulture);
|
context.Add(new Separator());
|
||||||
context.Add(new CommandContextItem(new CopyTextCommand(hexResult) { Name = Properties.Resources.calculator_copy_hex })
|
|
||||||
|
var i = (BigInteger)decimalValue;
|
||||||
|
|
||||||
|
// hexadecimal
|
||||||
|
try
|
||||||
{
|
{
|
||||||
Title = hexResult,
|
var hexResult = BaseConverter.Convert(i, 16);
|
||||||
});
|
context.Add(new CommandContextItem(new CopyTextCommand(hexResult) { Name = Properties.Resources.calculator_copy_hex })
|
||||||
}
|
{
|
||||||
catch (Exception ex)
|
Title = hexResult,
|
||||||
{
|
});
|
||||||
Logger.LogError("Error converting to hex format", ex);
|
}
|
||||||
}
|
catch (Exception ex)
|
||||||
|
|
||||||
// binary
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var binaryResult = "0b" + i.ToString("B", outputCulture);
|
|
||||||
context.Add(new CommandContextItem(new CopyTextCommand(binaryResult) { Name = Properties.Resources.calculator_copy_binary })
|
|
||||||
{
|
{
|
||||||
Title = binaryResult,
|
Logger.LogError("Error converting to hex format", ex);
|
||||||
});
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError("Error converting to binary format", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// octal
|
// binary
|
||||||
try
|
try
|
||||||
{
|
|
||||||
var octalResult = "0o" + Convert.ToString(i, 8);
|
|
||||||
context.Add(new CommandContextItem(new CopyTextCommand(octalResult) { Name = Properties.Resources.calculator_copy_octal })
|
|
||||||
{
|
{
|
||||||
Title = octalResult,
|
var binaryResult = BaseConverter.Convert(i, 2);
|
||||||
});
|
context.Add(new CommandContextItem(new CopyTextCommand(binaryResult) { Name = Properties.Resources.calculator_copy_binary })
|
||||||
}
|
{
|
||||||
catch (Exception ex)
|
Title = binaryResult,
|
||||||
{
|
});
|
||||||
Logger.LogError("Error converting to octal format", ex);
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError("Error converting to binary format", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// octal
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var octalResult = BaseConverter.Convert(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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError("Error creating integer context items", ex);
|
||||||
|
}
|
||||||
|
|
||||||
return new ListItem(new CopyTextCommand(decimalResult))
|
return new ListItem(new CopyTextCommand(decimalResult))
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user