如何在外部编辑器中编辑命令行?

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

tl;博士

我想找到 bash edit-and-execute-command 小部件或 zsh edit-command-line 小部件的 Powershell 版本。

背景

短命令直接在命令行上执行,长而复杂的命令从脚本中执行。但是,在它们变得“长”之前,能够在命令行上测试中等长度的命令会有所帮助。为了协助完成这项工作,在外部编辑器中编辑命令变得非常有用。 AFAIK Powershell 本身不支持这个,例如bashzsh 可以。

我目前的尝试

我是 Powershell 的新手,所以我肯定会犯很多错误,但我已经使用 [Microsoft.Powershell.PSConsoleReadLine] 的功能提出了一个可行的解决方案class。我可以将当​​前命令行复制到文件,编辑文件,然后将编辑后的版本重新注入命令行:

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)
}

问题

我目前的解决方案感觉笨拙而且有些脆弱。 还有其他解决方案吗?目前的解决方案可以改进吗?

环境

我采用的解决方案

创建类似于 mklement0 所示的“烘焙”可执行文件。我更喜欢 vim 而不是 `gvim,因为它直接在控制台中运行:

'@vim -f %*' > psvim.cmd
$env:EDITOR = "psvim"
Set-PSReadLineKeyHandler -Chord "Alt+e" -Function ViEditVisually

这段代码有一些问题:

  • 临时路径是硬编码的,它应该使用 $env:temp 或更好的 [IO.Path]::GetTempPath()(为了 cross-platform 兼容性)。
  • 编辑行后,不会替换整行,只替换光标左侧的文本。如 mklement0, we can simply replace 所述,现有缓冲区而不是擦除它,这解决了问题。
  • 当为编辑器使用正确的参数时,不需要创建作业来等待它。对于 VSCode,这是 --wait (-w) and for gvim this is --nofork (-f),它可以防止这些进程与控制台进程分离,因此 PowerShell 代码会一直等到用户关闭编辑器。
  • 关闭编辑器后临时文件没有被删除

这是我修复代码的尝试。我不用gvim,所以用VSCodecode.exe测试了一下。下面的代码也包含 gvim 的注释行(已由 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
}

更新:

GitHub Gist 包括代码的更新版本。新功能包括 saving/restoring 光标位置、将光标位置传递给 VSCode 以及在控制台被阻止时显示消息。

备注:

  • VSCode 的默认安装将 VSCode 二进制文件的目录添加到 $env:PATH,这使我们能够只写 code 来启动编辑器。
  • 虽然 UTF-8 是 PowerShell CoreSet-Content 等 cmdlet 的默认编码,但对于 Windows PowerShell 参数 -Encoding utf8 是正确保存包含 Unicode 字符的命令所必需的。对于 Get-Content,不需要指定编码,因为 Windows PowerShell 添加了一个 BOM,Get-Content 检测到并且 PowerShell Core 再次默认为 UTF-8。
  • 要让 Alt+E 在您打开控制台时始终可用,只需将此代码添加到您的 $profile file。轻而易举地快速测试和编辑小代码示例。
  • VSCode 设置 - 为了获得最流畅的体验,请启用“自动保存:onWindowChange”。允许您通过单击 Ctrl+W.
  • 来关闭编辑器并保存文件(更新控制台行)

tl;dr

  • PSReadLine 附带您正在寻找的功能,即 ViEditVisually 功能,在 Unix-like 平台上它甚至有一个默认的键绑定。

  • 要使该功能起作用,您需要将 $env:VISUAL$env:EDITOR 设置为编辑器可执行文件的名称/路径。

  • 从 PSReadLine v2.1 开始,如果您还需要为您的编辑器包含 选项 - 例如 -f 用于 gvim - 你需要创建一个 helper 可执行文件,它有选项“baked in”。

    • 由于创建辅助可执行文件很麻烦,自定义模拟该功能,正如您在 zett42's helpful answer 中尝试和改进的那样,目前可能是更简单的解决方案。 zett42 的答案还链接到一个要点,该要点通过保留光标位置来改进原始功能。

    • GitHub issue #3214 建议在这些环境变量中添加识别可执行文件 及其选项 的支持。

详情如下


  • PSReadLine module ships with such a feature, namely the ViEditVisually函数.

  • 它的默认键绑定,如果有的话,取决于PSReadLine的edit mode,你可以用Set-PSReadLineOption -EditMode <mode>设置:

    • Windows 模式(默认为 Windows):未绑定
    • Emacs 模式(macOS 和 Linux 上的默认模式):Ctrl-xCtrl-e
    • Vi模式:v命令模式(按Esc进入)
  • 使用Set-PSReadLineKeyHandler建立自定义键绑定,类似于问题中的方法;例如,绑定 Alt-e:

    • Set-PSReadLineKeyHandler -Chord Alt+e -Function ViEditVisually
  • 要使该功能正常工作,您必须通过以下任一环境变量定义要使用的编辑器可执行文件,按优先顺序排列:$env:VISUAL$env:EDITOR;如果未定义(有效的)编辑器,则会发出警告蜂鸣声,并且在调用该函数时不采取任何操作。

    • 例如,要在 macOS 上使用 nano 编辑器:$env:VISUAL = 'nano'

    • 值必须引用 可执行文件 - 通过完整路径或更常见的名称仅,在这种情况下,它必须位于 $env:PATH

      中列出的目录中
      • 注意:.ps1 脚本 符合可执行文件的条件,但批处理文件 Windows 和shebang-line-based shell Unix-like 平台上的脚本 可以。
    • 自 PSReadLine 2.1 起,包括可执行文件的 选项 - 例如 --newindow --wait 用于 code ( Visual Studio 代码) 支持

      • 因此,现在,如果您选择的编辑器需要选项,您需要创建一个辅助可执行文件 具有这些选项“内置”;请参阅下面的示例。

      • GitHub issue #3214 提议添加支持以允许指定编辑器可执行文件 plus options 作为 environment-variable 值,这是Git(识别相同的变量)已经支持;例如,您可以定义:
        $env:VISUAL = 'code --new-window --wait'


用于 code(Visual Studio 代码)的辅助可执行文件 的示例配置:

  • 在此示例中,在用户的主目录中创建一个辅助可执行文件:

    • 在Windows:

      '@code --new-window --wait %*' > "$HOME\codewait.cmd"
      
    • 在 Unix-like 平台上:

      "#!/bin/sh`ncode --new-window --wait `"$@`"" > "$HOME/codewait"; chmod a+x "$HOME/codewait"
      
  • 将指向辅助可执行文件的 $env:VISUAL 的定义添加到您的 $PROFILE 文件,如果需要,还可以定义自定义键绑定:

$env:VISUAL = "$HOME/codewait"

# Custom key binding
Set-PSReadLineKeyHandler -Chord Alt+e -Function ViEditVisually