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
This commit is contained in:
Jiří Polášek
2026-02-09 20:37:59 +01:00
committed by GitHub
parent 740dbf5699
commit 7477b561a1
38 changed files with 2626 additions and 138 deletions

View File

@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.CmdPal.Core.Common.Helpers;
using Microsoft.CmdPal.Core.Common.Text;
using Microsoft.CmdPal.Ext.Apps.Commands;
using Microsoft.CmdPal.Ext.Apps.Helpers;
using Microsoft.CommandPalette.Extensions;
@@ -14,7 +15,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
public sealed partial class AppListItem : ListItem
public sealed partial class AppListItem : ListItem, IPrecomputedListItem
{
private readonly AppCommand _appCommand;
private readonly AppItem _app;
@@ -25,6 +26,35 @@ public sealed partial class AppListItem : ListItem
private InterlockedBoolean _isLoadingIcon;
private InterlockedBoolean _isLoadingDetails;
private FuzzyTargetCache _titleCache;
private FuzzyTargetCache _subtitleCache;
public override string Title
{
get => base.Title;
set
{
if (!string.Equals(base.Title, value, StringComparison.Ordinal))
{
base.Title = value;
_titleCache.Invalidate();
}
}
}
public override string Subtitle
{
get => base.Subtitle;
set
{
if (!string.Equals(value, base.Subtitle, StringComparison.Ordinal))
{
base.Subtitle = value;
_subtitleCache.Invalidate();
}
}
}
public override IDetails? Details
{
get
@@ -259,4 +289,10 @@ public sealed partial class AppListItem : ListItem
return null;
}).ConfigureAwait(false);
}
public FuzzyTarget GetTitleTarget(IPrecomputedFuzzyMatcher matcher)
=> _titleCache.GetOrUpdate(matcher, Title);
public FuzzyTarget GetSubtitleTarget(IPrecomputedFuzzyMatcher matcher)
=> _subtitleCache.GetOrUpdate(matcher, Subtitle);
}