[CmdPal] Search PATH starting with ~ / \ (#40887)

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

Love that we have now environment variables expanding! 🚀 
Porting also few small features I implemented in Run Folder plugin a
long time ago and I love:
- https://github.com/microsoft/PowerToys/pull/7711
- https://github.com/microsoft/PowerToys/pull/9579

Threat `/` and `\` as root of system drive (typically `C:\`)
Threat `~` as user home directory `%USERPROFILE%`

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

- [ ] Closes: #xxx
- [ ] **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

<img width="591" height="356" alt="image"
src="https://github.com/user-attachments/assets/9c1f196a-bd4d-428c-a4e6-af9f269acd1f"
/>

<img width="591" height="356" alt="image"
src="https://github.com/user-attachments/assets/4295ebca-f12b-43b0-b3d0-c130b6faf419"
/>


<img width="591" height="356" alt="image"
src="https://github.com/user-attachments/assets/87748864-e250-4141-b366-29b45d58edcf"
/>

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

- Tested search starting with `~` `/` `\`
- Tested UNC network path starting with `\\...` and `//...`
This commit is contained in:
Davide Giacometti 2025-08-13 20:42:40 +02:00 committed by GitHub
parent 911989bac1
commit ab76dd1255
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CmdPal.Ext.Shell.Helpers;
@ -15,6 +16,8 @@ namespace Microsoft.CmdPal.Ext.Shell;
internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDisposable
{
private static readonly char[] _systemDirectoryRoots = ['\\', '/'];
private readonly Action<string>? _addToHistory;
private CancellationTokenSource? _cancellationTokenSource;
private Task? _currentUpdateTask;
@ -80,8 +83,8 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
cancellationToken.ThrowIfCancellationRequested();
var searchText = query.Trim();
var expanded = Environment.ExpandEnvironmentVariables(searchText);
searchText = expanded;
Expand(ref searchText);
if (string.IsNullOrEmpty(searchText) || string.IsNullOrWhiteSpace(searchText))
{
Command = null;
@ -184,8 +187,8 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
internal static bool SuppressFileFallbackIf(string query)
{
var searchText = query.Trim();
var expanded = Environment.ExpandEnvironmentVariables(searchText);
searchText = expanded;
Expand(ref searchText);
if (string.IsNullOrEmpty(searchText) || string.IsNullOrWhiteSpace(searchText))
{
return false;
@ -197,4 +200,57 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
return exeExists || pathIsDir;
}
private static void Expand(ref string searchText)
{
if (searchText.Length == 0)
{
return;
}
var singleCharQuery = searchText.Length == 1;
searchText = Environment.ExpandEnvironmentVariables(searchText);
if (!TryExpandHome(ref searchText))
{
TryExpandRoot(ref searchText);
}
}
private static bool TryExpandHome(ref string searchText)
{
if (searchText[0] == '~')
{
var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
if (searchText.Length == 1)
{
searchText = home;
}
else if (_systemDirectoryRoots.Contains(searchText[1]))
{
searchText = Path.Combine(home, searchText[2..]);
}
return true;
}
return false;
}
private static bool TryExpandRoot(ref string searchText)
{
if (_systemDirectoryRoots.Contains(searchText[0]) && (searchText.Length == 1 || !_systemDirectoryRoots.Contains(searchText[1])))
{
var root = Path.GetPathRoot(Environment.SystemDirectory);
if (root != null)
{
searchText = searchText.Length == 1 ? root : Path.Combine(root, searchText[1..]);
return true;
}
}
return false;
}
}