TL;DR use autocommands, see the end of this post.
Here's the result of the investigation I've made:
Calling bn
call the ex_bnext
function which does two things:
- Go to the desired buffer via the
goto_buffer
function
- Execute any command line command passed to the
bnext
command
The goto_buffer
function apparently wrap the more generic do_buffer
function and calls it with special argument related to the action of going to a buffer (see the first argument is the action which is either DOBUF_SPLIT
or DOBUF_GOTO
).
The do_buffer
function is pretty long, but you can see the interesting part around the end. This function calls the set_curbuf
function.
The set_curbuf
function set the current buffer to the desired buffer and calls enter_buffer
.
The enter_buffer
function set/reset some options for the newly displayed buffer and calls scroll_cursor_halfway
.
Finnaly, the scroll_cursor_halfway
function changes the topline value, meaning the cursor will be displayed halfway the window.
Sooo, I think it's the designed behavior.
What you can do is, when leaving a buffer, save the current window display with winsaveview
and on entering a buffer, reset the view with autocommands:
autocmd! BufWinLeave * let b:winview = winsaveview()
autocmd! BufWinEnter * if exists('b:winview') | call winrestview(b:winview) | unlet b:winview
zz
,zt
andzb
do not move the cursor, they just move the window view. On the contrary<C-d>
is moving the cursor.