概括
在 [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 中:
- 将当前缓冲区写入临时文件,
- 在该临时文件上运行外部命令,
- 如果有错误,处理它
- 决定是否应该替换缓冲区的行。
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
因此至少任何失败的外部命令都不会破坏缓冲区中的文本。