Как в [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, заключался бы в следующем:

  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.

В своей конфигурации я нашел следующий фрагмент, который вроде бы частично решает проблему, но я не помню, откуда он взялся:

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

Таким образом, по крайней мере, любые внешние команды, которые завершаются неудачно, не будут затирать текст в буфере.

Другие вопросы по тегам