1

I have a directory containing a large number of files:

a.json
b.dll
c.config
d.exe
...
z.exe

When I cd into this directory and type .\ then press Tab, PowerShell autocompletes the files in this order:

> .\a.json
> .\b.dll
> .\c.config
> .\d.exe
> .\z.exe

because by default, the prompt iterates through all files in the directory alphabetically.

I would like it to instead prompt me with executable files before any others, i.e.:

> .\d.exe
> .\z.exe
> .\a.json
> .\b.dll
> .\c.config

How can I do this?

Ian Kemp
  • 28,293
  • 19
  • 112
  • 138

1 Answers1

2

You need to replace the default PowerShell function that's used for tab completion, the mostly-undocumented TabExpansion2, the content of which you can obtain by running get-content function:global:tabexpansion2.

Because the content of this function may differ on your system, I'm not going to show it in its entirety, only the pertinent part which is the return of the calculated tab completion possibilities (this is from PowerShell Core 7.3.2 x64 running on Windows 10 21H2 x64):

... start of TabCompletion2...

End
{       
    if ($psCmdlet.ParameterSetName -eq 'ScriptInputSet')
    {
        return [System.Management.Automation.CommandCompletion]::CompleteInput(
            <#inputScript#>  $inputScript,
            <#cursorColumn#> $cursorColumn,
            <#options#>      $options)
    }
    else
    {
        return [System.Management.Automation.CommandCompletion]::CompleteInput(
            <#ast#>              $ast,
            <#tokens#>           $tokens,
            <#positionOfCursor#> $positionOfCursor,
            <#options#>          $options)
    }
}

Both code paths are calling the static System.Management.Automation.CommandCompletion.CompleteInput method, using different versions of that method depending on the arguments passed to TabExpansion2.

At this point you might think that we need to delve into the innards of these methods and tweak them to taste, but thankfully that's not the case. We don't actually need to change how CommandCompletion.CompleteInput works - we just want to change the order of its suggestions. Since it's already done the hard bit, we just need to do the reordering!

Hence, modify TabCompletion2 to the following:

... start of TabCompletion2...

End
{
    if ($psCmdlet.ParameterSetName -eq 'ScriptInputSet')
    {
        $completion = [System.Management.Automation.CommandCompletion]::CompleteInput(
            <#inputScript#>  $inputScript,
            <#cursorColumn#> $cursorColumn,
            <#options#>      $options)
    }
    else
    {
        $completion = [System.Management.Automation.CommandCompletion]::CompleteInput(
            <#ast#>              $ast,
            <#tokens#>           $tokens,
            <#positionOfCursor#> $positionOfCursor,
            <#options#>          $options)
    }

    $exeMatches, $nonExeMatches = $completion.CompletionMatches
        .Where({$_.CompletionText -Like "*.exe"}, "Split")
    $completion.CompletionMatches = @($exeMatches) + @($nonExeMatches)

    return $completion
}

It's actually really simple:

  • we use the magic array Where method to filter the CompletionMatches collection that CommandCompletion has helpfully already populated for us, splitting it into one part containing the found executable matches and the other part containing the remaining matches
  • overwrite the default completion matches with our sorted matches, placing the EXE matches first so they're presented first, and ensuring we use array constructors to overcome PowerShell's propensity for causing problems by silently returning a single item instead of an array
  • return the completion

With this updated TabCompletion2 installed into our profile and after reloading said profile via typing .$profile and pressing Enter, typing .\ and pressing Tab now yields the desired result:

> .\d.exe
> .\z.exe
> .\a.json
> .\b.dll
> .\c.config
Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
  • 1
    Very nice. You can simplify the split-and-join operation at the end with `$completion.CompletionMatches = @($completion.CompletionMatches.Where({$_-like'*.exe'},'Split') |Write-Output)` – Mathias R. Jessen Feb 20 '23 at 13:15
  • @MathiasR.Jessen Amazing - I didn't even know that the magic `Where` method existed until today, and I still can't find any documentation for it on MSDN. – Ian Kemp Feb 21 '23 at 11:22
  • 1
    The `.Where` intrinsic method is documented in the [`about_Arrays` help topic](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_arrays?view=powershell-7.3#where) :) – Mathias R. Jessen Feb 21 '23 at 11:24
  • Thanks again, found it now, updated answer to reflect MSDN link. – Ian Kemp Feb 21 '23 at 11:26