4

I currently use scratch buffers a lot for various uses such as when I read from STDIN to quickly analyze some data or take some very temporary notes. That way I don't have to worry about a swap file or the quit message when I leave the buffer. However, once in a while I decide to save the file for future reference. I'm wondering how to cause the buffer to switch to that new file automatically after saving instead of staying in my scratch buffer.

When I am referring to the scratch buffer, these are the commands I'm using to turn on and off the scratch buffer settings:

    command! TurnOnScratchBuffer setlocal buftype=nofile bufhidden=hide noswapfile
    command! TurnOffScratchBuffer setlocal buftype="" bufhidden="" swapfile

When I don't set the buffer as a scratch buffer in the first place or when I turn the scratch buffer on then off again, vim seems to already have the behavior I desire by immediately switching to the newly created file. However when I try to automate this with autocmds, then I get weird results that I believe have something to do with when various things trigger but I'm not sure. Here is an example of the current autocmds that I'm trying to use:

" Turn on scratch buffer settings when reading from STDIN or working with 
autocmd StdinReadPre,StdinReadPost * :TurnOnScratchBuffer
autocmd VimEnter,BufNew * if @% == "" && &buftype == "" | execute ':TurnOnScratchBuffer' | endif
autocmd BufWrite * if (@% == "" || @% == "-stdin-") && &buftype == "nofile" | execute ':TurnOffScratchBuffer' | endif

An as a pure example of how I would like it to work, say I run ls | vim - then what should happen is a scratch buffer should open with the listings of the current directory in a scratch buffer. If I then save this to a file :w test, then it will smoothly move to a buffer that is for the test file. If I save again, it should save directly to the test file. If I try to exit after making some changes, it should prompt me to save as you would expect when editing any normal file in vim.

The first part of that example I can get to work just fine, but the saving part is not behaving how I would like and I'm unsure why.

Let me know if there is another way of going about this too! Thanks for the help.

UPDATE

Finally got everything working thanks to the help of the wonderful people here. I learned so much from this little change that I can honestly say I'm better for it. I'm adding the final changes I made to simplify it for others that see this going forward.

command! -bar TurnOnScratchBuffer setlocal buftype=nofile bufhidden=hide noswapfile
command! -bar TurnOffScratchBuffer setlocal buftype= bufhidden= swapfile
command! -bar NewScratch new | TurnOnScratchBuffer

augroup scratch_buffers 
    autocmd!
    autocmd StdinReadPre * TurnOnScratchBuffer
    autocmd VimEnter * 
        \   if @% == '' && &buftype == ''
        \ |     TurnOnScratchBuffer
        \ | endif
    autocmd BufWritePost * ++nested
        \   if (empty(bufname()) || bufname() == '-stdin-') && &buftype == 'nofile'
        \ |     TurnOffScratchBuffer
        \ |     setlocal nomodified
        \ |     edit <afile>
        \ | endif
augroup END
  • 1
    Not that it should matter, but you dont need execute in that code. What if you try to use BufWritePre? – D. Ben Knoble Dec 22 '19 at 4:06
  • @D.BenKnoble. Without the execute in the autocmds with if statements, I get errors when sourcing the file: Error detected while processing VimEnter Autocommands for E488: Trailing characters: ScratchBuffer | endif. As for BufWritePre, it has the same behavior as BufWrite (and, from what I can tell from the autocommand documentation BufWrite and BufWritePre are synonymous?) – Samuel Jackson Dec 22 '19 at 6:02
  • 4
    @D.BenKnoble and Samuel Jackson: define the commands with :command! -bar ... so you don't need the :execute. See vi.stackexchange.com/a/22319/18609 – filbranden Dec 22 '19 at 8:00
3

Some notes

  • You don't need both StdinReadPre and StdinReadPost, one is enough.

  • TurnOnScratchBuffer and TurnOffScratchBuffer are valid command, they can be executed directly without execute, the leading : is also redundant.

  • Don't use " with :set, :h :quote is used to comment, everything after it is ignored.

  • autocmd should always be created inside :h :augroup .

Save a scratch buffer

I thought i can use :w path or :saveas path, but they have side effects:

  • :w path write your scratch buffer content to a new file, a normal unlisted buffer is also created for that file, it becomes your alternate buffer.
  • :saveas path change your buffer filename, write to it, a normal blank buffer is also created, it becomes your alternate buffer.

Note that current buffer doesn't change in both case, their buffer type is still nofile, an extra buffer is also created in both case, it's annoying.

Another way is to use :file:

com -nargs=1 -complete=file SaveScratch file <args> | set buftype= swapfile | w

It's clean, no extra buffers.


Another way to view stdin and quit without error message is to use the -R option

echo 123 | vim -R -

You can create an alias for this:

alias vless="vim -R -"

At last I prefer to create scratch buffer with a customized command:

nnoremap <c-n><c-s> :NewScratch<cr>
com NewScratch new | setlocal buftype=nofile bufhidden=hide noswapfile nobuflisted

Update

If you prefer :w path, you can use <c-^> to switch to alternate buffer, it's a normal buffer of the saved file.

If you want to switch to it automatically:

augroup scratch_auto_switch | au!
  autocmd BufWritePost * call s:switch_scratch()
augroup end

function s:switch_scratch() abort
  if &buftype ==# 'nofile' && expand('<afile>') == bufname('#')
    b #
  endif
endfunction

It relies on the fact that after w path, alternate file is the same as <afile>.

| improve this answer | |
  • For executing TurnOnScratchBuffer or TurnOffScratchBuffer in the autocmds while using if conditions, I found I needed the execute else it would throw an error along the lines of: Error detected while processing VimEnter Autocommands for E488: Trailing characters: ScratchBuffer | endif. As for saving, I usually save with :w but the minor frustration is that after saving the buffer doesn't change to being attached to the new file. I'll update the description with more details about desired behavior and examples. – Samuel Jackson Dec 22 '19 at 5:18
  • The error message says ScratchBuffer..., but it didn't say TurnOnScratchBuffer..., it's weird, what's your exact command without execute? – dedowsdi Dec 22 '19 at 5:54
  • 1
    @SamuelJackson See update. – dedowsdi Dec 22 '19 at 6:59
  • 3
    Use command! -bar to define the commands, so you don't need the :execute. See here: vi.stackexchange.com/a/22319/18609 – filbranden Dec 22 '19 at 8:02
  • 1
    Finally got everything working!! The only thing that was missing from this answer was adding the nested flag to the autocmd so that the other autocmds for plugins and whatnot run when switching buffers. – Samuel Jackson Dec 22 '19 at 18:39
1

That should be enough:

augroup scratch_write | au!
    autocmd BufWrite *
        \ if &bt ==# 'nofile' && (empty(bufname()) || bufname() ==# '-stdin-') |
            \ set nomodified
            \ TurnOffScratchBuffer |
            \ silent file <afile> |
        \ endif
augroup end

Also don't use quote inside :set and add :h command-bar

command! -bar TurnOffScratchBuffer setlocal buftype= bufhidden= swapfile
| improve this answer | |

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.