2

I have two functions tex#PDFOpen() and tex#PDFClose() to automatically open and close the pdf file created by a TeX document.

The way I assign them to autocommands is via the following autogroup in ~/.config/nvim/ftplugin/tex.vim

augroup TeXGroup
  autocmd BufEnter <buffer> call tex#PDFOpen()
  autocmd BufUnload <buffer> call tex#PDFClose()
augroup END

Now if I open a .tex file tex#PDFOpen() is called, but if edit another buffer with :e ..., then tex#PDFClose() is called, which I don't want. If I switch back to the buffer with the .tex file, tex#PDFOpen() is called again, which I also don't want.

Basically, tex#PDFOpen() should only be called

  1. on startup, if the argument of nvim is a .tex file;
  2. when editing a .tex file in a new buffer.

Similarly, tex#PDFClose() should only be called

  1. on exit, if one of the buffers contains a .tex file;
  2. when closing a buffer with a .tex file with :bd.

I've tried every event listed here, but none of them have worked.

4
+50

I see that for some reason people like to use :bdelete to close buffers. That's a very strange practice, IMO. Let's recall what is that :bdelete all about.

A buffer in Vim can 1) exist (bufexists() returns true); 2) be "loaded" (bufloaded() returns true), and 3) be "listed" (buflisted() returns true). Please, note that (2) and (3) are fully independent one of another, and any combination of (2) and (3) is legal for any buffer.

"Loaded" indicates that the buffer already has some contents attached and needs not to be read from disk (or whatever else) before showing it in some window. However, in practice one may also view an "unloaded" buffer as some sort of MRU record (a number, file name, some bookmarks and such, but no content at all).

At the same time the "listed" flag only indicates that the buffer should appear in the output of :ls command. But note that even if some buffer is unlisted, it still exists (and can even be "loaded") and can be seen in the output of :ls!.

Now :bdelete is basically a shortcut for :bunload and :set nobuflisted together. Moreover, the event called BufDelete is actually triggered when some buffer is set to nobuflisted, no matter if it's loaded or not. Read :h BufDelete carefully, it's all written there!

So :bdelete is a useless command, IMO. One "hides" buffers when he doesn't need to work on them anymore; one also "unloads" buffers to spare a bit of RAM; and on rare occasions one also "wipes" buffers to clean up some editing history. But what's good in :bdelete is beyond my reach.

So it really looks strange and counter-intuitive to require :bdelete (and to rely ultimately on buflisted) in this case. And on the other hand it seems quite okay to trap BufUnload as it was done.

Now to the question "why it doesn't work as I expect". The problem is that when your buffer gets hidden (i.e. not shown in any open window anymore) for any reason, Vim must decide what to do with the loaded content (after all, it takes some RAM and Vim is really peculiar about that).

So the first thing Vim inspects is a buffer-local :h 'bufhide' option, and if it's empty then it's a global :h 'hidden' option. By default nothing is set, and so Vim disposes the buffer content to save a couple of RAM bytes (and this is where BufUnload is triggered).

Now you want to keep some "pdf"-file open for a long time, and it's only natural to assume that you are also keen to keep "tex"-file loaded in memory too. To achieve this you should either globally :set hidden to keep all files in memory as long as possible (not a problem nowadays, is it?), or set it for tex files only:

~/.vim/after/ftplugin/tex.vim

setlocal bufhide=hidden

Now to trigger BufUnload for real you need :quit!, ZQ or :bunload (these commands always unload buffer contents, no matter what your options are). While :quit, :xit, ZZ etc. etc. will keep "tex" in memory, and so "pdf" remains open too.

17
  • But if I open a tex document and then open another file with :e , even if I use :q! the pdf remains open. I don't know if it's because the function isn't called at all, or if it's called but it fails. The idea I had (and I might very well be wrong) is that if I have 5 buffers open then quit, neovim would trigger a BufUnload for every buffer, and on the *.tex buffers that event would then execute the function to close the pdf file relative to that buffer. Btw, I've moved the TeXGroup autogroup to the main init.vim file as @filbranden suggested.
    – noibe
    Jul 5 '20 at 8:13
  • @noibe if I have 5 buffers open then quit, neovim would trigger a BufUnload for every buffer How is that so?! :q[!] quits an active buffer only. If you want to close a few specific (say, *.tex) buffers you have to write such command yourself.
    – Matt
    Jul 5 '20 at 9:02
  • Oh, I thought neovim would quit all the buffers on exit. What happens to the other (non active) ones, do they remain in memory?
    – noibe
    Jul 5 '20 at 10:59
  • @noibe I don't understand you. :quit closes the current window. And if it was the last one, it also exits Vim. In this case all buffers are unloaded, of course. That's all written in the help system.
    – Matt
    Jul 5 '20 at 11:25
  • Nevermind, I understood what's not working. The problem is that in tex#PDFClose() I use let filepath = expand('%:p') to get the filepath of the tex file, however when I quit that returns the filepath of the file in the active buffer. I need to pass the filepath of the file that triggered the autocommand to that function. Do you know how to do that?
    – noibe
    Jul 5 '20 at 14:17
0

My recommendation is that you use BufRead to open the PDF and BufDelete to close it.

Note that this should be set up at a global context, and not on the ftplugin for the tex filetype.

Furthermore, you should match it by filename on *.tex (and other filenames you might want to match), rather than adding multiple per-buffer autocmd's attached to the filetype.

For example, try adding the following to your vimrc:

augroup TeXGroup
  autocmd!
  autocmd BufRead *.tex call tex#PDFOpen()
  autocmd BufDelete *.tex call tex#PDFClose()
augroup END

Note that when the functions are called, the current buffer might no longer be the one with the *.tex file, so you might want to update your functions to take the file name as an argument and use <afile> (or <abuf>) to have them act on the correct file.

6
  • The first autocommand (the one for tex#PDFOpen()) works great, but the second one is only executed when I close a buffer with :bd. If I have, for example, 3 buffers foo.txt, bar.tex, baz.tex, when I quit vim I'd expect tex#PDFClose() to be called on bar.tex and baz.tex. Also, why is it recommendable to put that autogroup in my general .vimrc (init.vim in my case)? Isn't filetype-related stuff supposed to be grouped under ftplugin in <filetype>.vim to have a simpler vimrc?
    – noibe
    Jun 27 '20 at 9:51
  • @noibe Are you sure BufDelete isn't called for those two? I think this might be the issue with <afile> I mentioned at the end... % will not necessarily point to the buffer being deleted. :help BufDelete mentions that.
    – filbranden
    Jun 27 '20 at 10:29
  • Yes, generally you want to use ftplugin's to configure per-filetype settings, but setting additional autocmd's from there looks a little odd. It might work, but setting an autocmd for BufRead from the ftplugin won't work, since the BufRead command will already have happened at that point... You could just call PDFOpen directly from the ftplugin and add the per-buffer autocmd for BufDelete there, that might work. But I feel that matching *.tex on a pair of autocmd's is cleaner.
    – filbranden
    Jun 27 '20 at 10:37
  • If you're doing global autocmd's, they can't be defined in a per-filetype plug-in that might be loaded too late, or multiple times... It doesn't need to be in your vimrc though, you could define it on a plugin/*.vim file too, that would work too.
    – filbranden
    Jun 27 '20 at 10:39
  • 1
    BufDelete is triggered on :set nobuflisted (also after :bdelete, as it implicitly does set nobuflisted also). "Loaded" state doesn't matter.
    – Matt
    Jul 4 '20 at 14:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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