Carapace 在 PowerShell 上的空补全显示额外的 ^[[21;22;23;24;25;29m^[[39;49m

carapace-bin 1.5.5 上,当等待 完全 补全时,即什么都没输入,期待看到所有补全可能性的时候,首次 Tab 没有出现补全结果,反而是一串 ^[[21;22;23;24;25;29m^[[39;49m 序列。
作为例子,这里输入了 carapace <TAB> 来触发完全补全,出现了这个问题。

在输入 carapace 后按 Tab 键
$ carapace ^[[21;22;23;24;25;29m^[[39;49m
Display all 669 possibilities? (y or n) _
$ pip ^[[21;22;23;24;25;29m^[[39;49m
cache (Inspect and manage pip's wheel cache)
check (Verify installed packages have compatible dependencies)
completion (A helper command to be used for command completion)
config (Manage local and global configuration)
debug (Display debug information)
download (Download packages)
freeze (Output installed packages in requirements format)
hash (Compute a hash of a local package archive)
help (Show help for commands)
install (Install packages)
list (List installed packages)
search (Search for PyPI packages)
show (Show information about one or more installed packages)
uninstall (Uninstall packages)
wheel (Build Wheel archives for your requirements and dependencies)

输出这序列的代码在 internal/shell/powershell/action.go,序列代表 清除特定的文本样式并重置前景色和背景色 ,然后输出内容。另外有时候这个序列显示到 ^[[21;22;23;24;25;29m^[[ 就停止了。然而实际的内容应该是类似 `e[21;22;23;24;25;29m`e[39;49mversion`e[21;22;23;24;25;29;39;49m`e[2m `e[2m(show version number)`e[21;22;23;24;25;29;39;49m`e[0m; 这么一长串(`e[ 在 PowerShell 中写作 ^[)。

通过 trial and error,定位到与 PSReadLine 的 Set-PSReadLineOption -EditMode Emacs 设置有关,不设置、设置为其他值 Vi Windows 都没事。

更进一步的分析,和 Emacs 模式下默认给 Tab 绑定到 Complete 功能有关,其他模式下绑定的都是像 MenuComplete/PossibleCompletions 这样的功能,所以没事。 Complete 动作的含义是 “Complete the input if there is a single completion, otherwise complete the input with common prefix for all completions. Show possible completions if pressed a second time.”,即首次 Tab 显示所有可选项的最长前缀,再次 Tab 的行为类似 MenuComplete。

同时要触发这个问题,PSNativeCommandArgumentPassing 需要设置成 StandardWindows ,这在 PowerShell 7.3 之后是标配。不能设置为 Legacy,那样在首次 Tab 时什么都不会发生。这个变化影响的是空字符串的处理方式。

尝试寻找根因,翻到 PSReadLine 的 Completion.cs,在 GetUnambiguousPrefix 的另一个 for 循环中重新赋值了 replacementText = firstResult.ListItemText。而由 carapace 输出的 ListItemText 长这样,它们都有共同的前缀 `e[21;22;23;24;25;29m`e[39;49

$ carapace adb powershell adb '' | ConvertFrom-Json | ForEach-Object { [CompletionResult]::new($_.CompletionText, $_.ListItemText, [CompletionResultType]::ParameterValue, $_.ToolTip.replace('`e[', "`e[")) }
CompletionText ListItemText                                                                                                                                 ResultType ToolTip
-------------- ------------                                                                                                                                 ---------- -------
bugreport      `e[21;22;23;24;25;29m`e[39;49mbugreport`e[21;22;23;24;25;29;39;49m`e[2m `e[2m(write bugreport go given path)`e[21;22;23;24;25;29;39;49m`e[0m ParameterValue
connect        `e[21;22;23;24;25;29m`e[39;49mconnect`e[21;22;23;24;25;29;39;49m`e[2m `e[2m(connect to a device via TCP/IP)`e[21;22;23;24;25;29;39;49m`e[0m  ParameterValue
devices        `e[21;22;23;24;25;29m`e[39;49mdevices`e[21;22;23;24;25;29;39;49m`e[2m `e[2m(list connected devices)`e[21;22;23;24;25;29;39;49m`e[0m          ParameterValue

所以整体流程是这样:

  1. 使用 PowerShell 7.3+,并通过设置 Set-PSReadLineOption -EditMode Emacs 或是 Set-PSReadLineKeyHandler -Chord Tab -Function Complete 启用了 PSReadLine 的 Complete 补全功能
  2. 使用 carapace 或调用 Register-ArgumentCompleter 注册命令补全处理器,处理器在特定情况下返回的所有 CompletionResult 中的 CompletionText 缺少共同前缀,但 ListItemText 却有
  3. 用户输入命令名称后,不带任何参数地首次按下 Tab 键尝试补全, PSReadLine 会尝试以最长前缀补全,先遍历 CompletionText 再遍历 ListItemText
  4. 刚好 ListItemText 有共同前缀就展示了出来

所以这不像是 PSReadLine 的 Bug,毕竟它的表现就是展示前缀。更像是使用补全器的客户端的行为问题。可以用这个补全器来验证。

Set-PSReadLineKeyHandler -Chord Tab -Function Complete
Register-ArgumentCompleter -Native -ScriptBlock {
    param($wordToComplete, $commandAst, $cursorPosition)
    @(
        [System.Management.Automation.CompletionResult]::new("aa", "`e[2maa", [System.Management.Automation.CompletionResultType]::ParameterValue, "1"),
        [System.Management.Automation.CompletionResult]::new("bb", "`e[2mbb", [System.Management.Automation.CompletionResultType]::ParameterValue, "2")
    )
} -CommandName 'bash'

虽然没有搜索到 Issue,但似乎 carapace 是知道这个问题的,所以在 PowerShell 的设置说明 上有一段 Set-PSReadlineKeyHandler -Key Tab -Function MenuComplete,没错这就是解决方案。
哦,找到了 A bug in Powershell using the tab key · Issue #997 · carapace-sh/carapace,但没有定位到原因,我去补一个 。