From a37add8f0865588f13ffe02cc9cebad5a84b71f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1!?= Date: Mon, 8 Dec 2025 11:14:00 +0800 Subject: [PATCH] feat(cmdpal): add pinyin support for Chinese input method (#39354) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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 ## 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 ## 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 ## Validation Steps Performed --------- Signed-off-by: 舰队的偶像-岛风酱! Co-authored-by: Yu Leng --- .github/actions/spell-check/expect.txt | 1 + Directory.Packages.props | 1 + NOTICE.md | 32 +++++++++++++++ .../FuzzyStringMatcher.cs | 41 +++++++++++++++++++ ...t.CommandPalette.Extensions.Toolkit.csproj | 1 + 5 files changed, 76 insertions(+) diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 46df0235d8..d4be728886 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -1798,6 +1798,7 @@ tlbimp tlc tmain TNP +toolgood Toolhelp toolwindow TOPDOWNDIB diff --git a/Directory.Packages.props b/Directory.Packages.props index 00db38f4df..26e2c83bcc 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -122,6 +122,7 @@ + diff --git a/NOTICE.md b/NOTICE.md index 6ca3cbfceb..23efb64864 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -75,6 +75,37 @@ OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to ``` +### 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 ### Calculator @@ -1532,6 +1563,7 @@ SOFTWARE. - SkiaSharp.Views.WinUI - StreamJsonRpc - StyleCop.Analyzers +- ToolGood.Words.Pinyin - UnicodeInformation - UnitsNet - UTF.Unknown diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/FuzzyStringMatcher.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/FuzzyStringMatcher.cs index f4591bc443..7ecfc74222 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/FuzzyStringMatcher.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/FuzzyStringMatcher.cs @@ -2,6 +2,10 @@ // 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.Globalization; + +using ToolGood.Words.Pinyin; + namespace Microsoft.CommandPalette.Extensions.Toolkit; // Inspired by the fuzzy.rs from edit.exe @@ -9,6 +13,21 @@ public static class FuzzyStringMatcher { private const int NOMATCH = 0; + /// + /// Gets a value indicating whether to support Chinese PinYin. + /// Automatically enabled when the system UI culture is Simplified Chinese. + /// + 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) { var (s, _) = ScoreFuzzyWithPositions(needle, haystack, allowNonContiguousMatches); @@ -16,6 +35,28 @@ public static class FuzzyStringMatcher } public static (int Score, List Positions) ScoreFuzzyWithPositions(string needle, string haystack, bool allowNonContiguousMatches) + => ScoreAllFuzzyWithPositions(needle, haystack, allowNonContiguousMatches).MaxBy(i => i.Score); + + public static IEnumerable<(int Score, List Positions)> ScoreAllFuzzyWithPositions(string needle, string haystack, bool allowNonContiguousMatches) + { + List needles = [needle]; + List 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 Positions) ScoreFuzzyWithPositionsInternal(string needle, string haystack, bool allowNonContiguousMatches) { if (string.IsNullOrEmpty(haystack) || string.IsNullOrEmpty(needle)) { diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj index f5f8f2ccbc..eb887e9e36 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj @@ -42,6 +42,7 @@ runtime; build; native; contentfiles; analyzers +