tpope / vim-obsession

obsession.vim: continuously updated session files
http://www.vim.org/scripts/script.php?script_id=4472
1.76k stars 70 forks source link

Store the "alternative buffer" in the session #45

Closed Dbz closed 6 years ago

Dbz commented 6 years ago

Hello tpope,

I'd like to store the alternative buffer in the session so that I can close vim, open it back up and type <c-^> and automatically switch to the previous buffer that I was using.

I am also using vim-prosession and made a similar issue and dhruvasagar was super kind to help me get working vimrc code that will achieve my goal, but I would love to share this functionality in vim-obsession.

To me, this seems like a relatively harmless addition that just adds a slight UX benefit to using vim-obsession, but you will undoubtably know best.

Here is the code as it currently stands:

" https://github.com/dhruvasagar/vim-prosession/issues/50

function! s:preserve_alt_buffer()
  let l:alternate = fnameescape(expand('#'))
  if !empty(l:alternate)
    call system('echo "let @#=\"'.l:alternate.'\"" >> '.v:this_session)
  endif
endfunction

augroup ProsessionPreserveAlt
  au!

  autocmd VimLeave * call s:preserve_alt_buffer()
augroup END
tpope commented 6 years ago

Every window has an alternate buffer. I think preserving only one would be more confusing than helpful. But if you can figure out a more general solution I'd be interested.

Dbz commented 6 years ago

Hey tpope, I'm pretty thrilled that you're interested. I'll get back to you with some new code.

dhruvasagar commented 6 years ago

I don't think trying to preserve the alt buffers for every window will be advisable.

@Dbz Instead of using the VimLeave autocmd, you can (& should) use the User Obsession custom autocmd provided by vim-obsession. It will look like this :

autocmd User Obsession call s:preserve_alt_buffer()

Edit: Figured this out a little later, mentioning here for reference.

Dbz commented 6 years ago

@dhruvasagar Thank you for the refactor and your thoughts!

I was thinking of looping through each window and recording its alternative buffer if it has one. How come you don't think preserving the alt buffers for each window is a good idea?

dhruvasagar commented 6 years ago

@Dbz it's just going to be very awkward and tedious.

Dbz commented 6 years ago

How come you think it is going to be tedious? Do you mind sharing how you would do it, and I can try giving the code a shot?

Even though this is a really small UX change, I feel like it'll make the plugin just a little bit better, and I'd love to make a contribution.

dhruvasagar commented 6 years ago

Setting the register @# on each window is where the awkwardness is, compiling the list of buffers & their alternate buffers is easy, but then adding the command to the session file to ensure it's rebuild within it is a bit awkward and tedious. I tried to implement something for sometime yesterday, might give it some more thought over the weekend, but I am not convinced if it's worth it :)

dhruvasagar commented 6 years ago

@Dbz Also I noticed another thing, the obsession user autocmd fires a bunch of times during a vim session, so it may actually be more optimal to do the work for ensuring alt buffer(s) are setup correctly on VimLeave as opposed to User Obsession.

Dbz commented 6 years ago

That is really nice of you to put in that effort. I apologize for not being more knowledgeable, experienced, and helpful here, but if I can bounce ideas around to help come up with a solution I'd love to play that part.

I was thinking of looping through each window and recording to the session [the window number, the current buffer, and the alternate buffer, ... ] (in a single list repeated with 3 element chunks because I'm not familiar with the data structures available). Then for the actual setting we can loop through that list and set all of the @# at the same time.

My first thought is we may have to to loop through each window to do the setting. My second though was a modified WinDo method if it exists. winDo looks like it might only modify all buffers at once.

I can do a vimscript tutorial over the weekend to learn what the hell I'm talking about. This is my first foray into vimscript.

Dbz commented 6 years ago

Okay, it looks like winDo can take a range, so we should be able to iterate through each window and set the @# register.

As for vimLeave as opposed to User Obsession, we should probably split this up into a couple methods:

I am even wondering if we can do some of this saving and restoring state async, but I don't know if this is really possible to do async and guarantee all of the buffers/alternate buffers are saved/restored correctly.

tpope commented 6 years ago

For starters, the performance issues can be addressed by appending to the file directly rather than doing a bunch of shell gymnastics (and clean up a bunch of escaping bugs in the process). Stick this in your function body and it's suitable to call on User Obsession:

if len(@#)
  call writefile(['let @# = ' . string(@#)], v:this_session, 'a')
endif

As for getting the alternate buffer of each window, I thought there had to be an API for that, but I'm not seeing one. :windo is really disruptive; you'll need throw on commands to restore the focused and previous windows afterwards, and then layer a tabdo on top of that. It would likely be easier (but certainly not easy) to keep track of alternate buffers in the BufEnter autocommand. You're welcome to spike it out if you want but at this point I think I would advise you not to bother.

I think if there's anything to be done here, it's to provide a better autocommand for manipulating the constructed session directly, rather than requiring a second write. This would address the minor issue of @# being set after SessionLoadPost, and further streamline things by avoiding 2 writes.

dhruvasagar commented 6 years ago

You can loop through getwininfo() as opposed to using :windo to get alt buffers without being disruptive, however in my attempts, I tried to map them to a bufnr / winid which then would have to resolved to lookup through the dictionary cache before setting the register @# and that proved to be difficult.

tpope commented 6 years ago

Where is the alternate buffer number in getwininfo()? I'm not seeing it.

dhruvasagar commented 6 years ago

@tpope sorry it looks like a combination of getwininfo() and others such as getbufinfo / getbufvar does not yield the results i was hoping for. Unfortunately the @# register behavior is rather elusive.

Dbz commented 6 years ago

Okay, so it sounds like because we don't want to do a windo and tabdo, we need to rely on events.

Anytime a buffer is opened / switched_to we record the current window and alternate buffer.

The setting of the alternate buffer on loading session is something I'm wondering about now because I wonder if we have an API to do this other than windo and recording the current window and alternate window.

That makes me think that again perhaps the best way to set these values is event based when we switch our focus to a new window, and then we can set the alternate buffer.

We also have to think of any edge cases such as when a window closes.

What are your thoughts?

P.S. I'm working through learning vimscript the hard way, so hopefully once we have an idea of how we should do this I can write some example code.

dhruvasagar commented 6 years ago

@Dbz I don't think it's worth the effort, I would recommend you to resort to other, more reliable ways of buffer switching such as using :b or with ctrl-p-buffer and other similar alternatives instead of figuring out an ugly hack to get this working. It is both more reliable and accurate to do this instead of trying to recreate the alternate file history which is also relying on memory.

dhruvasagar commented 6 years ago

You can use alt buffers more down the line within the current running session as you open / switch to files, but on a newly reopened session it feels more sensible to just switching to buffers directly rather than relying on alternate buffers

Dbz commented 6 years ago

I totally understand that perspective. This is a decent amount of work for a tiny benefit. I actually do rely on fzf for buffer switching, and I have a couple of handy shortcuts (learned about nnoremap during the tutorial today):

" Switch between the last two files
nnoremap <leader><leader> <c-^>
" fzf plugin
nnoremap <Leader>f :Files<CR>
nnoremap <Leader>a :Ag<CR>
nnoremap <Leader>b :Buffers<CR>
nnoremap <Leader>t :Tags <C-r><C-w><CR>
nnoremap <Leader>bt :BTags<CR>
" [Tags] Command to generate tags file
let g:fzf_tags_command = 'ctags -R'

My use case is that I'm on a git feature branch working on a file and its spec file which I switch between, and then I hop to a different git branch for other work. When I hop back to my original branch and open vim, I try to switch to the alternate buffer because I'm working on the two associated files, but I have to use the buffers command because the alternate buffer isn't set. This is not a real problem, but I think it would be really cool to solve it and provide a solution for other people even if it isn't seemingly worth the effort in terms of value that it brings.

Also, this is giving me an opportunity to learn vimscript, and I get to interact with you and tpope, so I'm very much enjoying this process. Really, I don't think you guys realize how much fun this is for me! I will understand though if this feature is eventually rejected due to too much overhead. I still will enjoy the process of making it, and if you guys are willing to share your thoughts along the way, that would be super helpful.

tpope commented 6 years ago

The unfortunate part is you have two hard problems, retrieving the alternate buffers and adding them to the session, and even if you manage to figure out one you might find the other impossible. If you want to dive in anyways as a challenge, great, but keep in mind I'm basically writing it off as untenable at this point.

Also, stop exiting Vim every time you switch branches and you might find this isn't quite as critical.

dhruvasagar commented 6 years ago

As @tpope mentions, you should avoid having to switch sessions / restart vim for branch changes. You can very well do the same from within the current vim session.

If you're using tpope/vim-fugitive, you may also edit alternate versions of the file in different branches using :Gedit <branch>:/path/to/file. There are other similar ways to look at code across git branches.

Dbz commented 6 years ago

Okay, I will heed both of your advice. This has spurred me to start learning vimscript, so that in itself is a win. And, to both of your credit, I should learn to use my plugins properly. Hopefully, vim one day will add more APIs to make this much easier, and I'll make the PR then :).

If I use :Gedit <branch>:/path/to/file, does it open that in a new session? Is there a way to keep my git branches in their own sessions so that if I later open up vim in a git branch it restores all of those buffers?