在 [Neo]Vim 中,如何显示用作视觉选择过滤器的外部命令的错误消息?

在 [Neo]Vim 中,如何显示用作视觉选择过滤器的外部命令的错误消息?

概括

在 [Neo]Vim 中,如果我在当前可视选择上调用外部命令(期望该命令转换所选文本,然后 Vim 在缓冲区中更新该文本),并且该命令失败并返回非零退出值,则不会显示命令的 stderr 上的错误消息。我该如何让它自动显示?

背景

可以轻松手动进行视觉选择,然后调用外部命令并让 Vim 使用该命令的标准输出来替换选定的文本。

xnoremap <Leader>d :!mycommand<CR>

这定义了一个键映射,这样按下 leader 键然后按“d”就会在可视选择上运行外部脚本“mycommand”,如上所述。与其他“-remap”命令相反,使用“xnoremap”意味着这仅在当前存在可视选择时发生,自动将选定的文本传递给命令等。

问题

但是,如果 mycommand 失败(例如,可视化选择中的文本可能未按 mycommand 预期的格式设置),则 mycommand 不会在 stdout 上打印任何内容,在 stderr 上打印错误消息,并给出非零退出值。在这种情况下,我的 Neovim 正确地保留了未修改的缓冲区,但未显示错误消息。相反,它只显示:

:'<,'>!mycommand                                                                                        
shell returned 1

当前不完善的解决方法

我又回到了看起来有点混乱的状态:

function! s:MyFunc() range
    silent '<,'>!mycommand 2>/tmp/vim.err
    if v:shell_error
        echo join(readfile("/tmp/vim.err"), "\n")
    endif
endfunction

xnoremap <Leader>d :call <sid>MyFunc()<CR>

毫无疑问,我可以使用更合理的临时存储位置(适当的临时文件、变量、寄存器等?)

但是,这真的有必要吗?我是否必须对每个作为过滤器调用的外部程序都执行此操作,同时还要查看错误消息?我最终会将上述函数推广到对任意外部命令和任意范围进行操作吗?

这似乎是一种可以预见的、常见的需求,我觉得我一定错过了一些可以自动为我做到这一点的明显的东西,但我还没有从我的网络搜索、其他 stackoverflow 答案或围绕“帮助!”的 Vim 文档中找到它。

答案1

与其他“-remap”命令相反,使用“xnoremap”意味着仅当当前存在可视选择时才会发生这种情况,并自动将选定的文本传递给命令等。

请注意,没有“-remap”命令。您只有“-map”命令,其xnoremap读法如下:

 |    |
+|----|------- visual mode
 |++++|------- non-recursive
 |    |+++---- mapping
x|nore|map
 |    |

re的一部分nore,而不是想象的 的一部分remap

现在,Vim 和 Neovim 在错误处理方面可能存在差异,因为 Vim 总是用两个都 stdout并且stderr,我同意,这远非优雅:

例子

  • 第一次尝试用“无”替换文本,结果显示stdout stderr,因此只有 才能有效stderr
  • 第二次尝试抑制stderr文本,并用“无”替换文本stdout
  • stdout第三次尝试用和替换文本stderr

在 Vim 中,通常简单的解决方法是让过滤器执行其操作,如果出现错误(如 par v:shell_error),则撤消并按合适的方式处理错误。

一个稍微好一点的方法是,仍然在 Vim 中:

  1. 将当前缓冲区写入临时文件,
  2. 在该临时文件上运行外部命令,
  3. 如果有错误,处理它
  4. 决定是否应该替换缓冲区的行。
function! Filter() range
    " generate a temporary file name
    let tempfile = tempname()

    " write the lines in the given range to that temporary file
    call getline(a:firstline, a:lastline)->writefile(tempfile)

    " pass the temporary file to the external command and grab the output
    let output = systemlist('jskdfsdsg ' .. tempfile)

    if v:shell_error
        " if there is an error, prit the error and leave the buffer alone
        echomsg v:shell_error
    else
        " if there is none, delete the given range
        execute a:firstline .. ',' .. a:lastline .. 'd_'

        " and replace it with the output of the external command
        execute a:firstline - 1 .. 'put=output'
    endif
endfunction

参见:help tempname()、、、、、、和。:help getline():help writefile():help systemlist()​​:help :d:help registers:help :put

答案2

我在我的配置中发现了以下代码片段,它似乎部分解决了该问题,但我不记得它来自哪里:

augroup FILTER_ERROR
    au!
    autocmd ShellFilterPost * if v:shell_error | silent undo | endif
augroup END

因此至少任何失败的外部命令都不会破坏缓冲区中的文本。

相关内容