feat(cmdpal): add pinyin support for Chinese input method (#39354)

<!-- 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

- Add ToolGood.Words.Pinyin package to support pinyin conversion
- Implement pinyin matching in StringMatcher class
- Update project dependencies and Directory.Packages.props

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #38417 #39343
- [ ] **Communication:** I've discussed this with core contributors
already. If 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

I've completed a rough implementation of pinyin support, but since I'm
currently unsure where to add the toggle for pinyin support, this
feature is enabled by default for now.



https://github.com/user-attachments/assets/59df0180-05ad-4b4a-a858-29aa15e40fd2



<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Signed-off-by: 舰队的偶像-岛风酱! <frg2089@outlook.com>
Co-authored-by: Yu Leng <yuleng@microsoft.com>
This commit is contained in:
舰队的偶像-岛风酱! 2025-12-08 11:14:00 +08:00 committed by GitHub
parent 60deec6815
commit a37add8f08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 76 additions and 0 deletions

View File

@ -1798,6 +1798,7 @@ tlbimp
tlc tlc
tmain tmain
TNP TNP
toolgood
Toolhelp Toolhelp
toolwindow toolwindow
TOPDOWNDIB TOPDOWNDIB

View File

@ -122,6 +122,7 @@
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.10" /> <PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.10" />
<PackageVersion Include="System.Text.Json" Version="9.0.10" /> <PackageVersion Include="System.Text.Json" Version="9.0.10" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" /> <PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="ToolGood.Words.Pinyin" Version="3.1.0.3" />
<PackageVersion Include="UnicodeInformation" Version="2.6.0" /> <PackageVersion Include="UnicodeInformation" Version="2.6.0" />
<PackageVersion Include="UnitsNet" Version="5.56.0" /> <PackageVersion Include="UnitsNet" Version="5.56.0" />
<PackageVersion Include="UTF.Unknown" Version="2.6.0" /> <PackageVersion Include="UTF.Unknown" Version="2.6.0" />

View File

@ -75,6 +75,37 @@ OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/> For more information, please refer to <http://unlicense.org/>
``` ```
### ToolGood.Words.Pinyin
We use the ToolGood.Words.Pinyin NuGet package for converting Chinese characters to pinyin.
**Source**: [https://github.com/toolgood/ToolGood.Words.Pinyin](https://github.com/toolgood/ToolGood.Words.Pinyin)
```
MIT License
Copyright (c) 2020 ToolGood
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: Command Palette Built-in Extensions ## Utility: Command Palette Built-in Extensions
### Calculator ### Calculator
@ -1532,6 +1563,7 @@ SOFTWARE.
- SkiaSharp.Views.WinUI - SkiaSharp.Views.WinUI
- StreamJsonRpc - StreamJsonRpc
- StyleCop.Analyzers - StyleCop.Analyzers
- ToolGood.Words.Pinyin
- UnicodeInformation - UnicodeInformation
- UnitsNet - UnitsNet
- UTF.Unknown - UTF.Unknown

View File

@ -2,6 +2,10 @@
// 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.Globalization;
using ToolGood.Words.Pinyin;
namespace Microsoft.CommandPalette.Extensions.Toolkit; namespace Microsoft.CommandPalette.Extensions.Toolkit;
// Inspired by the fuzzy.rs from edit.exe // Inspired by the fuzzy.rs from edit.exe
@ -9,6 +13,21 @@ public static class FuzzyStringMatcher
{ {
private const int NOMATCH = 0; private const int NOMATCH = 0;
/// <summary>
/// Gets a value indicating whether to support Chinese PinYin.
/// Automatically enabled when the system UI culture is Simplified Chinese.
/// </summary>
public static bool ChinesePinYinSupport { get; } = IsSimplifiedChinese();
private static bool IsSimplifiedChinese()
{
var culture = CultureInfo.CurrentUICulture;
// Detect Simplified Chinese: zh-CN, zh-Hans, zh-Hans-*
return culture.Name.StartsWith("zh-CN", StringComparison.OrdinalIgnoreCase)
|| culture.Name.StartsWith("zh-Hans", StringComparison.OrdinalIgnoreCase);
}
public static int ScoreFuzzy(string needle, string haystack, bool allowNonContiguousMatches = true) public static int ScoreFuzzy(string needle, string haystack, bool allowNonContiguousMatches = true)
{ {
var (s, _) = ScoreFuzzyWithPositions(needle, haystack, allowNonContiguousMatches); var (s, _) = ScoreFuzzyWithPositions(needle, haystack, allowNonContiguousMatches);
@ -16,6 +35,28 @@ public static class FuzzyStringMatcher
} }
public static (int Score, List<int> Positions) ScoreFuzzyWithPositions(string needle, string haystack, bool allowNonContiguousMatches) public static (int Score, List<int> Positions) ScoreFuzzyWithPositions(string needle, string haystack, bool allowNonContiguousMatches)
=> ScoreAllFuzzyWithPositions(needle, haystack, allowNonContiguousMatches).MaxBy(i => i.Score);
public static IEnumerable<(int Score, List<int> Positions)> ScoreAllFuzzyWithPositions(string needle, string haystack, bool allowNonContiguousMatches)
{
List<string> needles = [needle];
List<string> haystacks = [haystack];
if (ChinesePinYinSupport)
{
// Remove IME composition split characters.
var input = needle.Replace("'", string.Empty);
needles.Add(WordsHelper.GetPinyin(input));
if (WordsHelper.HasChinese(haystack))
{
haystacks.Add(WordsHelper.GetPinyin(haystack));
}
}
return needles.SelectMany(i => haystacks.Select(j => ScoreFuzzyWithPositionsInternal(i, j, allowNonContiguousMatches)));
}
private static (int Score, List<int> Positions) ScoreFuzzyWithPositionsInternal(string needle, string haystack, bool allowNonContiguousMatches)
{ {
if (string.IsNullOrEmpty(haystack) || string.IsNullOrEmpty(needle)) if (string.IsNullOrEmpty(haystack) || string.IsNullOrEmpty(needle))
{ {

View File

@ -42,6 +42,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="System.Drawing.Common" /> <PackageReference Include="System.Drawing.Common" />
<PackageReference Include="ToolGood.Words.Pinyin" />
<!-- This line forces the WebView2 version used by Windows App SDK to be the one we expect from Directory.Packages.props . --> <!-- This line forces the WebView2 version used by Windows App SDK to be the one we expect from Directory.Packages.props . -->
</ItemGroup> </ItemGroup>