vim-stay
vim-stay copied to clipboard
Stay should not load view state when re-editing buffer in another window
The original report by @fent in #45 read:
Often when editing the same file in multiple windows, I might go to another buffer in one of the windows to reference something, and then come back. But previously when I did, the cursor would jump to where the other window is.
As stated here, the PR in the submitted form does not seem the correct way to address things; this issue seeks to pinpoint the cause of the error and to develop a less destructive approach.
@fent would you assist me in finding out what happens exactly, bearing with me if I ask stupid questions? When you speak of a “window”, what do you mean, exactly:
- a Vim window as defined in the Vim help, i.e. what is known colloquially as a split;
- or a Vim window from a window manager POV, i.e. a GUI or terminal muxer window?
When I speak of a window, I mean a vim spit window.
OK, thanks for that. Hmm, last time I looked, BufWinEnter
did not trigger on simply entering a split. The Vim 8.1 help concurs (for what that is worth), stating that it fires:
when the buffer is loaded (after processing the modelines) or when a hidden buffer is displayed in a window (and is no longer hidden).
My point is that though stay creates a view whenever leaving a window (through WinLeave
), it cannot by rights load one when you simply switch to an already open window (because BufWinEnter
does not fire); I tested for that at the time, more than once, so either Vim’s event handling changed dramatically and without warning, or there is something in your usage scenario and / or configuration triggering an unwanted load event.
Could you give me a detailed, step by step scenario of how the issue is reproducibly triggered for you? Starting with how you start (Neo)Vim and including what exact version and patch level you are using on what OS. Please make a note on how exactly you execute each step (what Vim commands you use, or if you use custom mappings to do so, the mapping code).
Also, once you did that, could you please try to reproduce using a minimal configuration, i.e. a vimrc
set up to just run stay and nothing else?
so either Vim’s event handling changed dramatically and without warning, or there is something in your usage scenario and / or configuration triggering an unwanted load event.
Could be that the commands I'm using to switch back to the buffer are causing BufWinEnter
to fire, even if the buffer is already loaded, and shown in another split.
These following 3 methods I used to switch back all reproduce the issue
-
:e#
-
:b [name]
-
:e [name]
The docs for BufWinEnter
do say that it's fired when a name is provided to the :split
command, maybe this also applies to the commands I'm using. And maybe the only time this doesn't fire is if you're splitting the currently opened file.
Also, once you did that, could you please try to reproduce using a minimal configuration, i.e. a
vimrc
set up to just run stay and nothing else?
I'm able to reproduce it when I disable all other plugins except vim-stay :/. I also disabled all of mappings and autocmds.
Could you give me a detailed, step by step scenario of how the issue is reproducibly triggered for you? Starting with how you start (Neo)Vim and including what exact version and patch level you are using on what OS.
VIM - Vi IMproved 8.0 (2016 Sep 12, compiled May 17 2018 15:09:32)
macOS version
Included patches: 1-1850
Steps to reproduce
- Open a file with vim
vim file1.txt
- Scroll halfway through file by pressing
ctrl+D
or searching for text I know is halfway - Create a split
:split
- Scroll to top of file to note different position, still on first split
gg
- Open a different file
:e file2.txt
- Switch back to first file
:e#
(or:b file1.txt
or:e file1.txt
) - Note that the position of this split when switching back matches the other split, instead of being at the top of file
Hm. So I was able to reproduce the behavior with neovim 0.3.4
using a minimal vimrc (blank, except for loading vim-stay
). I'm not sure what the best course of solution here is, but I'll dig into the code here to see if I can come up with anything.
So, thinking about this more, I'm not sure that this isn't desired behavior. Let's think about it this way: when you split the open buffer into 2 windows, it's creating a view port into that file for each window. However, there's still only one buffer. When you then switch one of those two windows to another buffer (by opening another file with :e
), you're destroying it's view port.
As far as I know, vim doesn't keep track of view ports (except for the ones open and visible), only buffers. After opening the second buffer, the first window is no longer viewing the first buffer. When you switch that window back to viewing the first buffer, vim-stay does its job and scrolls to the same place where the second window is. In other words: the most recently-scrolled-to place in that buffer (because the other window is still open and scrolled to that point).
So, thinking about this more, I'm not sure that this isn't desired behavior.
If I start vim without vim-stay, this behavior isn't reproduced. And I think it would be more expected from users that vim-stay persist state across windows, even multiple windows of the same buffer.
If I start vim without vim-stay, this behavior isn't reproduced.
Well, the whole point of stay is to add behaviour not present in Vim without it, so that is only natural.
And I think it would be more expected from users that vim-stay persist state across windows, even multiple windows of the same buffer.
Actually persisting different states for different windows is both impossible while using Vim view sessions (these map to the file loaded in a buffer, not to a window) and conceptually unsound (what window state do you want when you re-open a file in a fresh editing session; don’t say “the last” – what if there were more than one window open when you last quit?).
@zhimsel is right insofar as this is, technically speaking, expected behaviour, because the usage pattern above is not to switch to a window, but to re-edit a loaded buffer when wanting to switch. Vim recycles the existing window when one does so, so it feels equivalent, but it is not (see the :e
help, which is quite clear about the fact that using it on an open file triggers a fresh edit). Real window switching using Ctrl-W
commands or (heresy) the mouse should not trigger a re-edit.
This being said, I agree with @fent that what happens to them, technically correct or not, feels wrong, thus violating the software design principle of least surprise. An obvious out would be to get rid of theWinLeave
event to save view state (as stated before, replacing BufWinEnter
for view loading is not an option unless we want to strip the plug-in of most of its functionality). However, I am pretty sure I originally put it there for a reason, i.e. BufWinLeave
not firing in all desirable situations.
Could you both try to exercise stay with the lines 85 and 86 of plugin/stay
commented out, trying different split and quit situations (with none, one or more windows on the same buffer open at different positions, for instance) to see if the results are still satisfactory?
If this doesn’t pan out, the aternative solution I can think of ATM would be to check for the number of windows a buffer is shown in, and only save the view when that number is one. ~~The problem with that is that we would need to shim win_findbuf()
on older Vim versions, or up compatibility requirements …~~ EDIT: turns out I already did that when creating the whole window ID shim autoload module.
Could you both try to exercise stay with the lines 85 and 86 of
plugin/stay
commented out, trying different split and quit situations (with none, one or more windows on the same buffer open at different positions, for instance) to see if the results are still satisfactory?
After removing this autocmd, I'm still able to reproduce a slightly different error. Now, the view that was previously saved on the buffer is restored when switching back to it.
So for example
- I open
text1.txt
and vim-stay restores the cursor at the middle of the file - Split
- Move to the end of the file
- Open another file
- Switch back to first file
- The cursor is restored to the middle of the file
Well, I guess we all know why WinLeave
was there in the first place now. I’ll go ahead and declare this line of thinking abortive.
I am currently tending towards agreeing with Zach and declaring these “errors” expected behaviour: stay is behaving both consistently with Vim’s (convoluted and not altogether intuitive) file handling model and in a technically correct way.
I still am with @fent insofar as that technically correct behaviour runs against superficial expectations, and I‘ll ponder possibilities to alleviate the problem, but I don’t think a poor fit between a certain usage pattern and the plug-in warrants gutting its feature set. Of course, that decision is ultimately @zhimsel’s, not mine …
@fent I don’t want to give the impression I am dismissing your concerns lightly, but your usage pattern for buffer activation really is orthogonal to the plug-in’s declared intent, i.e. reliably restoring the last view state whenever a buffer is displayed in a buffer: on startup in any combination of splits and tabs, on unhiding, on cycling through with :xdo
commands. Unluckily, there is no way to differentiate between “this buffer has been loaded into a window and needs its view state reloaded” and “this buffer has been loaded into a window but should not have its view state restored” without knowing user intent.
One way to declare this intent would be to load your reference split into a preview window instead of a regular one, as stay ignores the former. This would at least provide a workaround to your problem until we can think of a better way…
I might have an idea how to handle this, thanks to an insomniac night. Because it is thanks to an insomniac night, I ’d like to run this by you both, @fent and @zhimsel.
It seems to me the issue of recognising when there is no need for a saved view state load can be reduced to 1. a buffer does not really enter a window, just technically, firing a misleading BufWinEnter
, 2. the view state of that window consequently does not change.
How about we add a window view state heuristic where, if 1. the window contains the same buffer as before BufWinEnter
fired and 2. other observable view state elements (essentially what winsaveview()
returns, I guess) are unchanged after it fired, we skip view session loading? To my admittedly sleep deprived brain, this sounds like it should resolve the issue while maintaining all of the current feature set, and that without introducing major new corner cases (except maybe a scenario where a real reload is triggered on a buffer with the cursor positioned at the beginning of the file … that one will lose fold state through the reload, as heuristics will fail to detect the change).
Please mull this over and point out the failings in my reasoning; it sounds too good to be entirely true, and I don’t trust the part of my brain that is awake right now …
if 1. the window contains the same buffer as before
BufWinEnter
fired
How is this checked? "before" as in, before switching to a different buffer and then switching back?
How is this checked?
My current thinking is to do that by setting a window scoped dictionary variable (w:_stay
) whenever we write a view session file storing the buffer name and the window state at this point. We then compare the current values to that before we load a view session.
In your scenario, what would happen would be that:
- You open file A in window 1. stay loads its view session.
- You open file A again in window 2, switching to it in the process. stay writes a view session for file A based on the state of window 1 and loads that in window 2. The status variable for window 1 is set, storing cursor position and the fact it shows file A.
- You change the view state of window 2.
- You switch back to window 1. stay repeats the above steps for window 2; your saved view session for file A now reflects the state of window 2. However, the window status variable for window 1 tells stay neither the buffer nor the cursor position have changed since we were last there. View session loading is skipped.
Any logical errors you can see?
Any logical errors you can see?
And by that I mean not just in the scenario I laid out above, but also for alternative usage patterns, e.g. switching via window commands, switching via closing windows, hiding / unhiding buffers, loading from arglist, :xdo
commands, loading multiple copies of a file on Vim start etc.
Hm. I'm not opposed to the method @kopischke described above. That seems like a reasonable way of handling this behavior change without overtly affecting "normal" usage.
@fent I can't guarantee I'll be able to implement this any time soon (my work schedule is insane right now), so I'll happily review a PR if you wanted to write one. Otherwise, I'll do my best to write this up when I have time.
@fent is this still an issue for you? Sorry it took so long for me to get back around to this, but it's possible I'll have some time very soon to work on this, if it's still relevant.
hi @zhimsel , thanks for looking into this again. i am still able to reproduce the steps from this comment above, with the latest version of this plugin, and without making the changes mentioned in the comment.
meanwhile, i've been using the version from this PR and it doesn't have the issue. although i understand that the way it's fixed in the PR may not be the best way to fix it.
Got it, thanks. I'll hopefully have some time to hack on this in the next couple weeks.
@fent now that it's well past a "couple of weeks" later 😉 : I also have an alternative idea, which would be easier to implement, and solve your particular problem (and allow you to switch back upstream). We could create a feature-flag variable the user can set which would simply switch the autocommand events to how you proposed them in #45.
As long as there's a clear explanation of what the setting does (and why it may be necessary to use), I'd be happy to merge that.
I'm not saying we can't/shouldn't modify the plugin as @kopischke; I just know I won't have the time necessary to fully test and implement it.
If you're up for creating a PR, I'd be happy to review and merge it. If not, I can make the changes myself fairly soon.
hey @zhimsel i no longer use this plugin, so i'm not as incentivized to go through with the change. plus, i'm not a huge fan of adding options to fix behavior. imo the uses case that inspired the change is a bug, but maybe the way i attempted to fix it was wrong and broke other things, and should be done the right way, not with an option that would further complicate code and confuse users.