Как в [Neo]Vim отображать сообщения об ошибках внешней команды, используемой в качестве фильтра визуального выбора?
Краткое содержание
В [Neo]Vim, если я вызываю внешнюю команду для текущего визуального выделения (ожидая, что эта команда преобразует выделенный текст, который Vim затем обновляет в буфере), и команда завершается с ошибкой с ненулевым значением выхода, то появляется сообщение об ошибке в stderr команды не отображается. Как сделать так, чтобы оно отображалось автоматически?
Фон
Легко вручную сделать визуальное выделение, затем вызвать внешнюю команду и заставить Vim использовать ее стандартный вывод для замены выделенного текста.
xnoremap <Leader>d :!mycommand<CR>
Это определяет сопоставление клавиш, так что нажатие ведущей клавиши, а затем «d» запускает внешний скрипт «mycommand» для визуального выбора, как описано выше. Использование «xnoremap», в отличие от других команд «-remap», означает, что это происходит только тогда, когда в данный момент существует визуальное выделение, автоматически передавая выделенный текст команде и т. д.
Проблема
Но если mycommand завершится неудачно (например, возможно, текст в визуальном выделении не отформатирован так, как ожидает mycommand), то mycommand ничего не выведет на стандартный вывод, сообщение об ошибке на 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 " помощь !".
2 ответа
Использование «xnoremap», в отличие от других команд «-remap», означает, что это происходит только тогда, когда в данный момент существует визуальное выделение, автоматически передавая выделенный текст команде и т. д.
Обратите внимание, что не существует такого понятия, как «команды '-remap'». Все, что у вас есть, это команды «-map», сxnoremap
читается так:
| |
+|----|------- visual mode
|++++|------- non-recursive
| |+++---- mapping
x|nore|map
| |
The re
частьnore
, а не воображаемыйremap
.
Теперь, возможно, между Vim и Neovim есть разница в обработке ошибок, потому что Vim всегда заменял отфильтрованные строки на и , что, я согласен, далеко не элегантно:
- Первая попытка заменяет текст на «ничто», полученное из и , следовательно, эффективно только на ,
- вторая попытка подавляет и заменяет текст «ничем», полученным из ,
- третья попытка заменяет текст обоими
stdout
иstderr
.
В Vim обычный упрощенный обходной путь — позволить фильтру делать свое дело, и в случае возникновения ошибки (как по умолчанию)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
.
В своей конфигурации я нашел следующий фрагмент, который вроде бы частично решает проблему, но я не помню, откуда он взялся:
augroup FILTER_ERROR
au!
autocmd ShellFilterPost * if v:shell_error | silent undo | endif
augroup END
Таким образом, по крайней мере, любые внешние команды, которые завершаются неудачно, не будут затирать текст в буфере.