Skip to content

Instantly share code, notes, and snippets.

@UrsaDK
Last active April 5, 2023 17:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save UrsaDK/1fed18c3aeb933c80d36 to your computer and use it in GitHub Desktop.
Save UrsaDK/1fed18c3aeb933c80d36 to your computer and use it in GitHub Desktop.
A function to remove trailing spaces from Vim buffers

I've been maintaining my own version of Strip Trailing Spaces function (attached at the bottom of this post) since I first started using vim. It grew out of a simple :%s/\s*$// and now includes a number of features:

  • it preserves search history and the last search string
  • it doesn't change editor's '', '. and '^ marks
  • it doesn't add a new jumplist and changelist record
  • it preserves editor's view and cursor position

However, over the years I've noticed that I never needed to undo the results of stripping trailing spaces from a file. In fact, I would go as far as to say that when undoing changes in a file, the function produces an understandable but somewhat undesirable cursor jump (to the top-most trailing space it strippes) which disrupts my work flow. So, I set about to exclude the results of this function from the undo history all together.

After reading Restore the cursor position after undoing text change made by a script, I got an idea that I could do this by merging undo record created by the function with the previous change in the buffer (if it exists). 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:

set undolevels=10

normal ggiline one is bull of aaaa
set undolevels=10 " used to break a change into separate undo blocks
normal Goline two is full of bbbb
set undolevels=10 " used to break a change into separate undo blocks
normal Goline three is full of cccc
set undolevels=10 " used to break a change into separate undo blocks

undojoin
keepjumps %s/aaaa/zzzz/
normal u

You can recreate my test by starting with a clean buffer and entering the above commands manually, or by saving the above block as a file and sourcing it via vim +"source your-filename-here".

The test worked successfully, so I've implemented the change into my function.

Unfortunately, this is where I run into a bit of a brick wall. Merging undo record created by the function with the previous item in the undo history (via undojoin) was trivial. However, when testing I've discovered that once the merged change is undone, the cursor jumps to the top most change in the file (which often happens to be a stripped space) and not to the position of the original change, as defined by the change list.

Can anyone think of why this would be? Better yet, can anyone suggest a fix?

--

Related discussions:

""
" Strip trailing white spaces
"
" This function is designed to parse the content of a buffer and remove
" trailing spaces from the end of all lines. Normally, this is done just
" before the contents of a buffer is written out to a file.
"
" Eg: autocmd BufWritePre,FileWritePre,FileAppendPre,FilterWritePre <buffer>
" \ :silent! keepjumps call UmkaDK#StripTrailingSpaces(0)
"
" @param integer a:allow The number of trailing spaces to be allowed.
"
function! UmkaDK#StripTrailingSpaces(...) range
let l:num_of_spaces_to_keep = a:0 ? a:1 : 0
" Define a range of lines to be processed
let l:range = a:firstline != a:lastline
\ ? a:firstline . ',' . a:lastline
\ : '1,$'
" Match all trailing spaces in a file
let l:regex = [
\ '\^\zs\s\{1,\}\$',
\ '\S\s\{' . l:num_of_spaces_to_keep . '\}\zs\s\{1,\}\$',
\ ]
" Join trailing spaces regex into a single string
let l:regex_str = '\(' . join(l:regex, '\|') . '\)'
" Save current window state
let l:last_search=@/
let l:winview = winsaveview()
" try
" " Append this 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 ' . l:range . 's/\V' . l:regex_str . '//e'
else
execute 'keepjumps ' . l:range . 's/\V' . l:regex_str . '//e'
call histdel('search', -1)
endif
" Restore current window state
call winrestview(l:winview)
let @/=l:last_search
endfunction
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment