0

I am wondering what could be an efficient way to achieve what I asked in the title.

The best I came out with is the following defined in ~/.vim/ftplugin/markdown.vim:

augroup PRETTIER
    autocmd! * <buffer>
    autocmd BufWritePost <buffer> {
    b:win_view = winsaveview()
    silent exe "!prettier --prose-wrap always --print-width 80 --write " .. expand("%")
    edit %
    winrestview(b:win_view)
    echo "File prettified!"
    }
augroup END

which works very well.

However, by doing this the file is saved twice as the option --write used by prettier appear to save the file once prettified.

Also, the content in between {} inside the autocommand could be defined in a function. In-fact I did the following:

def Prettify()
    b:win_view = winsaveview()
    silent exe "!prettier --prose-wrap always --print-width 80 --write " .. expand("%")
    edit %
    winrestview(b:win_view)
    echo "File prettified!"
enddef

augroup PRETTIER
    autocmd! * <buffer>
    autocmd BufWritePost <buffer> call Prettify()
augroup END

but this end up in E127: Cannot redefine function <SNR<76_Prettify: It is in use

6
  • 2
    Can prettify work as a filter? Then you can use %!prettify … to avoid the double-write. For the function error, please share the complete code you used (with autocommand).
    – D. Ben Knoble
    Mar 23 at 13:46
  • Thanks! By using silent exe ":%!prettier --prose-wrap always --print-width 80 " .. expand("%") solved ALL the problems!
    – Barzi2001
    Mar 23 at 14:13
  • 2
    If it's solved, consider posting an answer.
    – Friedrich
    Mar 23 at 14:45
  • Related: :help 'formatprg'.
    – romainl
    Mar 23 at 15:43
  • @D.BenKnoble but now I got curious. Why I got E127 with relation to my second question?
    – Barzi2001
    Mar 23 at 21:19

2 Answers 2

1

Here is a more canonical and holistic approach.

Your question is specifically about formatting a buffer on write but formatting is a common thing that can be done in other scenarios and it turns out that Vim has a number of builtin commands and options for that. Setting the relevant options, :help 'formatprg' and/or :help 'formatexpr', AS WELL AS (not "instead of") writing your own custom functions would give you a complete formatting solution.

The built-in operator gq can be used to format the text covered by the given motion, as in gqip or gggqG. If formatprg is set, then gq uses the given external program and, if formatexpr is set, it uses the given expression, which means that you can then simply use gq manually or in your autocommand.

The whole thing would look like this, from the lower-level to the the higher-level:

" in autoload/format/markdown.vim
" this is where the formatting is actually done
" with all the filetype/context-relevant specificities
function! format#markdown#FormatExpr()
    " do your thing
endfunction

" in autoload/format.vim
" nothing filetype-specific, here,
" only use gq on the whole buffer
" and restore cursor position
" can be used as-is for any filetype
function! format#FormatWithoutMoving()
    let l:view = winsaveview()
    silent normal! gggqG
    call winrestview(l:view)
endfunction

" in after/ftplugin/markdown.vim
" this is where you tell Vim how to format that specific filetype
" and that it should do it on write
set formatexpr=format#markdown#FormatExpr()
augroup Markdown
    autocmd! * <buffer>
    autocmd BufWritePre <buffer> silent call format#FormatWithoutMoving()
augroup END

And, now that you have a dedicated format#markdown#FormatExpr(), you can fine-tune your call to prettier according to context (range, buffer-local variables, presence of prettier or not, error handling, etc.).

4
  • Thanks. after learning formatprg and formatexpr I was actually going towards the route you suggested. But what if FormatExpr function takes some argument and in a custom command I would call format#markdown#FormatExpr(my_arg)? Wouldn't I lose the win view when invoking my custom command unless I don't save/restore it inside FormatExpr, no?
    – Barzi2001
    Mar 24 at 10:15
  • Yes. My suggestion is to handle the view restoration and the formatting separately but I am not aware of all your circumstances so you might be better off handling the two at the same place. YMMV.
    – romainl
    Mar 24 at 11:40
  • @Barzi2001, note the difference in autocommand as well, BufWritePre is called before buffer is saved, you're using BufWritePost which is called after buffer is saved.
    – Maxim Kim
    Mar 25 at 0:53
  • @MaximKim yes, that was what I was initially doing which has been corrected in the self-answer I provided
    – Barzi2001
    Mar 25 at 7:13
0

As requested here is the solution thanks to the insight of @D. Ben Knoble. The trick was to use %!prettier ... . It follows the complete working code:

augroup PRETTIER
    autocmd! * <buffer>
    autocmd BufWritePre <buffer> call Prettify()
augroup END

def Prettify()
    var win_view = winsaveview()
    silent exe $":%!prettier --prose-wrap always --print-width {&l:textwidth} --stdin-filepath {shellescape(expand("%"))}"
    winrestview(win_view)
enddef
2
  • You don’t need to pass the filename when using a stdin/stdout filter, btw.
    – D. Ben Knoble
    Mar 24 at 15:58
  • 1
    Do you mean to remove shellescape(expand("%"))? If so, then the buffer is cleaned and I would get the following error: [error] No parser and no file path given, couldn't infer a parser. You should use --stdin-filepath option though. I am going to update my answer.
    – Barzi2001
    Mar 24 at 21:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.