question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

prevent PowerShell autocomplete from adding dot prefix

See original GitHub issue

Summary of the new feature / enhancement

Please provide an option or configuration to prevent PowerShell from prefixing every relative file path (even program arguments!) with a .\ without my asking it to

I understand why PowerShell requires that I prefix a command foo.exe with .\foo.exe when I run it. So if I am typing a command fo and hit Tab, autocomplete changes what I typed to .\foo.exe. I can live with that, even though I find it annoying and patronizing.

The problem is when foo.exe takes command-line arguments, and one of them is the bar.dat file. If I’m typing .\foo.exe ba and hit Tab, I want to get .\foo.exe bar.dat. I do not want to get .\foo.exe .\bar.dat. Not only is this irritating as heck (not to mention ugly), it actually breaks some of my commands, which don’t allow any directory (even the current directory) to be specified in the argument.

How do I turn off this PowerShell quirk of adding a .\ to every single autocomplete for a relative path? As far as I’m concerned, I’d like it to be turned off everywhere—I can remember to type .\foo.exe instead of foo.exe for the initial command. As I mentioned earlier, I can partially understand that PowerShell wants to add the .\ to the initial command, because it’s required to run a program in the current directory. But I’m dumbfounded as to why PowerShell is so unintelligent that it blindly adds the .\ prefix to all relative paths, even where it’s not required, not needed, and certainly not desired.

I opened prevent PowerShell autocomplete from adding dot prefix on Super User, but no one had any solutions.

Proposed technical implementation details (optional)

No response

Issue Analytics

  • State:closed
  • Created a year ago
  • Reactions:1
  • Comments:22 (11 by maintainers)

github_iconTop GitHub Comments

2reactions
mklement0commented, Dec 28, 2022

Nicely done, @MartinGC94. I suggest tweaking the code a bit (I’ve done it below) so as to treat ./ the same as \., and to prevent the removal of these prefixes if the token being tab-completed represents a command.

function TabExpansion2 {
  <# Options include:
        RelativeFilePaths - [bool]
            Always resolve file paths using Resolve-Path -Relative.
            The default is to use some heuristics to guess if relative or absolute is better.

      To customize your own custom options, pass a hashtable to CompleteInput, e.g.
            return [System.Management.Automation.CommandCompletion]::CompleteInput($inputScript, $cursorColumn,
                @{ RelativeFilePaths=$false }
    #>

  [CmdletBinding(DefaultParameterSetName = 'ScriptInputSet')]
  [OutputType([System.Management.Automation.CommandCompletion])]
  Param
  (
    [Parameter(ParameterSetName = 'ScriptInputSet', Mandatory = $true, Position = 0)]
    [string] $inputScript,

    [Parameter(ParameterSetName = 'ScriptInputSet', Position = 1)]
    [int] $cursorColumn = $inputScript.Length,

    [Parameter(ParameterSetName = 'AstInputSet', Mandatory = $true, Position = 0)]
    [System.Management.Automation.Language.Ast] $ast,

    [Parameter(ParameterSetName = 'AstInputSet', Mandatory = $true, Position = 1)]
    [System.Management.Automation.Language.Token[]] $tokens,

    [Parameter(ParameterSetName = 'AstInputSet', Mandatory = $true, Position = 2)]
    [System.Management.Automation.Language.IScriptPosition] $positionOfCursor,

    [Parameter(ParameterSetName = 'ScriptInputSet', Position = 2)]
    [Parameter(ParameterSetName = 'AstInputSet', Position = 3)]
    [Hashtable] $options = $null
  )

  Set-StrictMode -Version 1

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

  # Custom post-processing that removes a .\ or ./ prefix, if present.
  $completionOutput.CompletionMatches = @(
    foreach ($item in $CompletionOutput.CompletionMatches) {
      $isFileOrDirArg =
        if ($item.ResultType -in [System.Management.Automation.CompletionResultType]::ProviderContainer, [System.Management.Automation.CompletionResultType]::ProviderItem) {
          # A file / directory (provider item / container) path, 
          # though we must rule out that it acts *as a command*,
          # given that the "./" \ "\." prefix is *required* when invoking files located in the current dir.

          # Tokenize the part of the input command line up to the cursor position.
          $pstokens = [System.Management.Automation.PSParser]::Tokenize($inputScript.Substring(0, $cursorColumn), [ref] $null)

          # Determine if the last token is an argument.
          switch ($pstokens[-1].Type) {
            'CommandArgument' {
              # An unquoted argument.
              $true; break 
            } 
            { $_ -in 'String', 'Number' } { 
              # Either:
              #  * A quoted string, which may be an argument or command;
              #    Note that in the end it is only treated as a command if preceded by '.' or '&'
              #  * A number which matches a filename that starts with a number, e.g. "7z.cmd"
              switch ($pstokens[-2].Type) {
                $null { $false; break } # Token at hand is first token? -> command.
                'Operator' { $pstokens[-2].Content -notin '.', '&', '|', '&&', '||'; break } # Preceded by a call operator or an operator that starts a new command.
                'GroupStart' { $false; break } # Preceded by "{", "(", "$(", or "@(" -> command.
                'StatementSeparator' { $false; break } # Preceded by ";", i.e. the end of the previous statement -> command
                Default { $true } # Everything else: assume an argument.
              }
            }
          }
        }
      if ($isFileOrDirArg) {
        # A file / directory (provider item / container) path acting as an *argument*
        [System.Management.Automation.CompletionResult]::new(($item.CompletionText -replace '^([''"]?)\.[/\\]', '$1'), $item.ListItemText, $item.ResultType, $item.ToolTip)
      } 
      else {
        # Otherwise, pass through.
        $item
      }
    }
  )
  return $completionOutput
}
1reaction
garretwilsoncommented, Sep 29, 2022

This [Bash] behavior is useless, as the reasonable expectation of a tab-completed value is that it works as is.

That’s a reasonable conclusion, and I’m not saying that PowerShell should remove its more helpful behavior in relation to the initial command. I was just saying it’s not necessarily “wrong” to do it the other way. Both Bash and PowerShell take a “I’ll blindly do path completion the same way for all relative paths.” I guess my point is that if it has to be all or nothing, I much prefer the Bash approach, which results in fewer things I didn’t ask for.

But we shouldn’t be stuck with two blind approaches. I’m sure PowerShell can be made smart enough to figure out if the path relates to the initial command or an argument. (And I think it’s worth noting that the logic to avoid this behavior surely would be many times shorter than the workaround.)

This [adding the current directory to the command search path] is generally ill-advised …

I don’t disagree. The point I was making is that the purported purpose of adding .\ to the command is because it is necessary for functionality, and I was noting that PowerShell is adding it even when it’s not necessary for functionality. If someone still wants PowerShell to add the prefix because it looks prettier to them, that’s a valid opinion, but that’s a different argument than functional utility.

I also want to say that your four points at the start of your comment above are an excellent summary, and I agree with them all.

Read more comments on GitHub >

github_iconTop Results From Across the Web

prevent PowerShell autocomplete from adding dot prefix
This question is about PowerShell blindly adding a dot prefix to every autocomplete I do, even if it's not a command (i.e. even...
Read more >
issue with autocompletion adding dot backslash
I am expecting powershell to autocomplete the letter 'i' to 'input.txt'. It does but I also get a dot + backslash before 'input.txt'....
Read more >
Intellisense in Windows PowerShell ISE 3.0
Intellisense provides the following added benefits over tab completion: Automatic drop-down when options are available – better discoverability ...
Read more >
maven - How to handle dots in powershell commands?
When calling an executable program in Windows PowerShell, place the stop-parsing symbol before the program arguments. This technique is much ...
Read more >
Autocomplete in PowerShell
Let's take a look at how you can configure your PowerShell profile to help you autocomplete commands!
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found