Home > Enterprise >  How do you edit the command line in an external editor?
How do you edit the command line in an external editor?

Time:02-19

tl;dr

I want to find a Powershell version of the bash edit-and-execute-command widget or the zsh edit-command-line widget.

Background

Short commands get executed directly on the command-line, long complicated commands get executed from scripts. However, before they become "long", it helps to be able to test medium length commands on the command-line. To assist in this effort, editing the command in an external editor becomes very helpful. AFAIK Powershell does not support this natively as e.g. bash and zsh do.

My current attempt

I am new to Powershell, so I'm bound to make many mistakes, but I have come up with a working solution using the features of the [Microsoft.Powershell.PSConsoleReadLine] class. I am able to copy the current command-line to a file, edit the file, and then re-inject the edited version back into the command-line:

Set-PSReadLineKeyHandler -Chord "Alt e" -ScriptBlock {
  $CurrentInput = $null

  # Copy current command-line input, save it to a file and clear it
  [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref] $CurrentInput, [ref] $null)
  Set-Content -Path "C:\Temp\ps_${PID}.txt" -Value "$CurrentInput"
  [Microsoft.PowerShell.PSConsoleReadLine]::KillRegion()

  # Edit the command with gvim
  Start-Job -Name EditCMD -ScriptBlock { gvim "C:\Temp\ps_${Using:PID}.txt" }
  Wait-Job  -Name EditCMD

  # Get command back from file the temporary file and insert it into the command-line
  $NewInput  = (Get-Content -Path "C:\Temp\ps_${PID}.txt") -join "`n"
  [Microsoft.PowerShell.PSConsoleReadLine]::Insert($NewInput)
}

Questions

My current solution feels clunky and a somewhat fragile. Are there other solutions? Can the current solution be improved?

Environment

  • OS Windows 10.0.19043.0
  • Powershell version 5.1.19041.1320
  • PSReadLine version 2.0.0

CodePudding user response:

This code has some issues:

  • Temp path is hardcoded, it should use $env:temp or better yet [IO.Path]::GetTempPath() (for cross-platform compatibility).
  • After editing the line, it doesn't replace the whole line, only the text to the left of the cursor. As noted by mklement0, we can simply replace the existing buffer instead of erasing it, which fixes the problem.
  • When using the right parameters for the editor, it is not required to create a job to wait for it. For VSCode this is --wait (-w) and for gvim this is --nofork (-f), which prevents these processes to detach from the console process so the PowerShell code waits until the user has closed the editor.
  • The temporary file is not deleted after closing the editor.

Here is my attempt at fixing the code. I don't use gvim, so I tested it with VSCode code.exe. The code below contains a commented line for gvim too (confirmed working by the OP).

Set-PSReadLineKeyHandler -Chord "Alt e" -ScriptBlock {
    $CurrentInput = $null

    # Copy current console line
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref] $CurrentInput, [ref] $null)

    # Save current console line to temp file
    $tempFilePath = Join-Path ([IO.Path]::GetTempPath()) "ps_$PID.ps1"
    Set-Content $tempFilePath -Value $CurrentInput -Encoding utf8

    # Edit the console line using VSCode
    code --new-window --wait $tempFilePath

    # Uncomment for using gvim editor instead
    # gvim -f $tempFilePath

    # The console doesn't like the CR character, so rejoin lines using LF only. 
    $editedInput = ((Get-Content -LiteralPath $tempFilePath) -join "`n").Trim()
    
    # Replace current console line with the content of the temp file
    [Microsoft.PowerShell.PSConsoleReadLine]::Replace(0, $currentInput.Length, $editedInput)

    Remove-Item $tempFilePath
}

Notes:

  • A default installation of VSCode adds the directory of the VSCode binaries to $env:PATH, which enables us to write just code to launch the editor.
  • While UTF-8 is the default encoding for cmdlets like Set-Content on PowerShell Core, for Windows PowerShell the parameter -Encoding utf8 is required to correctly save commands that contain Unicode characters. For Get-Content specifying the encoding isn't necessary, because Windows PowerShell adds a BOM which Get-Content detects and PowerShell Core defaults to UTF-8 again.
  • To have Alt E always available when you open a console, just add this code to your $profile file. Makes quick testing and editing of small code samples a breeze.
  • VSCode settings - for most streamlined experience, enable "Auto Save: onWindowChange". Allows you to close the editor and save the file (update the console line) with a single press to Ctrl W.

CodePudding user response:

OP here. With the help of zett42, I have now refined the function to this:

Set-PSReadLineKeyHandler -Chord "Alt e" -ScriptBlock {
  $CurrentInput = $null

  $tmpfile = "$env:Temp\ps_${PID}.txt" 

  # Copy current command-line input, save it to a file and clear it
  [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref] $CurrentInput, [ref] $null)
  Set-Content -Path $tmpfile -Value "$CurrentInput"
  [Microsoft.PowerShell.PSConsoleReadLine]::BackwardKillLine()
  [Microsoft.PowerShell.PSConsoleReadLine]::KillLine()

  gvim -f "$tmpfile"

  # Get command back from file the temporary file and insert it into the command-line
  $NewInput  = (Get-Content -Raw -Path "$tmpfile").Trim()
  [Microsoft.PowerShell.PSConsoleReadLine]::Insert($NewInput)
}
  • Related