EDITs:
I've simplified the function and clarified the question.
Original question is still available further down the page.Crossposted onto vim_dev mailing list: https://groups.google.com/forum/#!topic/vim_dev/_Rz3uVXbwsQ
Reported as a bug to Neovim:
https://github.com/neovim/neovim/issues/6276
Why is the cursor positioned differently in the following two examples:
[CORRECT CURSOR POSITION] The result of the substitution is joined to the previous change in the buffer (addition of line 3), the cursor position is correctly restored to the second line in the buffer.
normal ggiline one is full of aaaa set undolevels=10 " splits the change into separate undo blocks normal Goline two is full of bbbb set undolevels=10 normal Goline three is full of cccc set undolevels=10 undojoin keepjumps %s/aaaa/zzzz/ normal u
[INCORRECT CURSOR POSITION] The result of the substitution is joined to the previous change in the buffer (addition of line 4), the cursor position is incorrectly restored to the first line in the buffer (should be line 3).
normal ggiline one is bull of aaaa set undolevels=10 " splits the change into separate undo blocks normal Goline two is full of bbbb set undolevels=10 normal Goline three is full of cccc set undolevels=10 normal Goline four is full of aaaa's again set undolevels=10 undojoin keepjumps %s/aaaa/zzzz/ normal u
Original Question
The way my VIM is set up, saving a buffer to a file triggers a custom StripTrailingSpaces() function (attached at the end of the question):
autocmd BufWritePre,FileWritePre,FileAppendPre,FilterWritePre <buffer>
\ :keepjumps call StripTrailingSpaces(0)
After seeing Restore the cursor position after undoing text change made by a script, I got an idea to exclude the changes made by my StripTrailingSpaces() function from the undo history by merging undo record created by the function onto the end of the previous change in the buffer.
This way, when undoing changes, it would appear that the function didn't create it's own undo record at all.
To validate my idea I've used a simple test case: create a clean buffer and enter the following commands manually, or save the following block as a file and source it via:
vim +"source <saved-filename-here>"
normal ggiline one is full of aaaa
set undolevels=10 " splits the change into separate undo blocks
normal Goline two is full of bbbb
set undolevels=10
normal Goline three is full of cccc
set undolevels=10
undojoin
keepjumps %s/aaaa/zzzz/
normal u
As you can see, after undoing the last change in the buffer, that is creating the third line, the cursor is correctly returned to the second line in the file.
Since my test worked, I implemented an almost identical undojoin
in my StripTrailingSpaces(). However, when I undo the last change after the function has run, the cursor is returned to the top most change in the file. This is often a stripped space and is not the position of the change I undojoin
-ed to.
Can anyone think of why this would be? Better yet, can anyone suggest a fix?
function! StripTrailingSpaces(number_of_allowed_spaces)
" Match all trailing spaces in a file
let l:regex = [
\ '\^\zs\s\{1,\}\$',
\ '\S\s\{' . a:number_of_allowed_spaces . '\}\zs\s\{1,\}\$',
\ ]
" Join trailing spaces regex into a single, non-magic string
let l:regex_str = '\V\(' . join(l:regex, '\|') . '\)'
" Save current window state
let l:last_search=@/
let l:winview = winsaveview()
try
" Append the comming change onto the end of the previous change
" NOTE: Fails if previous change doesn't exist
undojoin
catch
endtry
" Substitute all trailing spaces
if v:version > 704 || v:version == 704 && has('patch155')
execute 'keepjumps keeppatterns %s/' . l:regex_str . '//e'
else
execute 'keepjumps %s/' . l:regex_str . '//e'
call histdel('search', -1)
endif
" Restore current window state
call winrestview(l:winview)
let @/=l:last_search
endfunction
:%s/\s*$/
? – Mylan''
,'.
and'^
marks, doesn't add a newjumplist
andchangelist
record, preserves your view and cursor position, and should create a smoooother undo experience. (Though the last point is subjective and is the reason this question is here.) – Leathery