CmdPal: Add precomputed fuzzy string matching to Command Palette (#44090)
## Summary of the Pull Request
This PR improves fuzzy matching in Command Palette by:
- Precomputing normalized strings to enable faster comparisons
- Reducing memory allocations during matching, effectively down to zero
It also introduces several behavioral improvements:
- Strips diacritics from the normalized search string to improve
matching across languages
- Suppresses the same-case bonus when the query consists entirely of
lowercase characters -- reflecting typical user input patterns
- Allows skipping word separators -- enabling queries like Power Point
to match PowerPoint
This implementation is currently kept internal and is used only on the
home page. For other scenarios, the `FuzzyStringMatcher` from
`Microsoft.CommandPalette.Extensions.Toolkit` is being improved instead.
`PrecomputedFuzzyMatcher` offers up to a 100× performance improvement
over the current `FuzzyStringMatcher`, and approximately 2–5× better
performance compared to the improved version.
The improvement might seem small, but it adds up and becomes quite
noticeable when filtering the entire home page—whether the user starts a
new search or changes the query non-incrementally (e.g., using
backspace).
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #45226
- [x] Closes: #44066
- [ ] **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
2026-02-09 20:37:59 +01:00
|
|
|
// 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.
|
|
|
|
|
|
2026-02-23 04:05:09 -08:00
|
|
|
using Microsoft.CmdPal.Common.Text;
|
CmdPal: Add precomputed fuzzy string matching to Command Palette (#44090)
## Summary of the Pull Request
This PR improves fuzzy matching in Command Palette by:
- Precomputing normalized strings to enable faster comparisons
- Reducing memory allocations during matching, effectively down to zero
It also introduces several behavioral improvements:
- Strips diacritics from the normalized search string to improve
matching across languages
- Suppresses the same-case bonus when the query consists entirely of
lowercase characters -- reflecting typical user input patterns
- Allows skipping word separators -- enabling queries like Power Point
to match PowerPoint
This implementation is currently kept internal and is used only on the
home page. For other scenarios, the `FuzzyStringMatcher` from
`Microsoft.CommandPalette.Extensions.Toolkit` is being improved instead.
`PrecomputedFuzzyMatcher` offers up to a 100× performance improvement
over the current `FuzzyStringMatcher`, and approximately 2–5× better
performance compared to the improved version.
The improvement might seem small, but it adds up and becomes quite
noticeable when filtering the entire home page—whether the user starts a
new search or changes the query non-incrementally (e.g., using
backspace).
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #45226
- [x] Closes: #44066
- [ ] **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
2026-02-09 20:37:59 +01:00
|
|
|
|
|
|
|
|
namespace Microsoft.CmdPal.Common.UnitTests.Text;
|
|
|
|
|
|
|
|
|
|
[TestClass]
|
|
|
|
|
public class StringFolderTests
|
|
|
|
|
{
|
|
|
|
|
private readonly StringFolder _folder = new();
|
|
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
|
[DataRow(null, "")]
|
|
|
|
|
[DataRow("", "")]
|
|
|
|
|
[DataRow("abc", "ABC")]
|
|
|
|
|
[DataRow("ABC", "ABC")]
|
|
|
|
|
[DataRow("a\\b", "A/B")]
|
|
|
|
|
[DataRow("a/b", "A/B")]
|
|
|
|
|
[DataRow("ÁBC", "ABC")] // Diacritic removal
|
|
|
|
|
[DataRow("ñ", "N")]
|
|
|
|
|
[DataRow("hello world", "HELLO WORLD")]
|
|
|
|
|
public void Fold_RemoveDiacritics_Works(string input, string expected)
|
|
|
|
|
{
|
|
|
|
|
Assert.AreEqual(expected, _folder.Fold(input, removeDiacritics: true));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
|
[DataRow("abc", "ABC")]
|
|
|
|
|
[DataRow("ÁBC", "ÁBC")] // No diacritic removal
|
|
|
|
|
[DataRow("a\\b", "A/B")]
|
|
|
|
|
public void Fold_KeepDiacritics_Works(string input, string expected)
|
|
|
|
|
{
|
|
|
|
|
Assert.AreEqual(expected, _folder.Fold(input, removeDiacritics: false));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
|
public void Fold_IsAlreadyFolded_ReturnsSameInstance()
|
|
|
|
|
{
|
|
|
|
|
var input = "ALREADY/FOLDED";
|
|
|
|
|
var result = _folder.Fold(input, removeDiacritics: true);
|
|
|
|
|
Assert.AreSame(input, result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
|
public void Fold_WithNonAsciiButNoDiacritics_ReturnsFolded()
|
|
|
|
|
{
|
|
|
|
|
// E.g. Cyrillic or other scripts that might not decompose in a simple way or just upper case
|
|
|
|
|
// "привет" -> "ПРИВЕТ"
|
|
|
|
|
var input = "привет";
|
|
|
|
|
var expected = "ПРИВЕТ";
|
|
|
|
|
Assert.AreEqual(expected, _folder.Fold(input, removeDiacritics: true));
|
|
|
|
|
}
|
|
|
|
|
}
|