5

I'm often editing code where others didn't care about trailing white-spaces. I'm using the TrimWhiteSpace()-function from this answer to remove white-spaces when saving the file.

However that leaves me with changes all other the file and VCS gives me many unrelated changes to revert or to commit.

Ideally I would like to automatically strip trailing white-spaces of every line I pressed ENTER while being in insert-mode. At least I think this is what I need.

How can I achieve this? Googling for some seconds did not bring up anything (not even on this site).

3
  • 1
    Why do you have trailing whitespaces when pressing <CR> in insert mode? I’m having a hard time imagining that happening in a case where you wouldn’t delete the trailing spaces before pressing enter.
    – D. Ben Knoble
    Mar 28, 2018 at 15:02
  • Hence my doubt: At least I think this is what I need. You're right, it's actually not when pressing enter, but when changing a line and leaving it with arrow-down or up where trailing spaces are staying. Or when splitting a line in the middle or to align.
    – Patrick B.
    Mar 29, 2018 at 7:05
  • Well as it stands you’ve got answers to the question you asked, but if you find you have another feel free to ask a new one obviously
    – D. Ben Knoble
    Mar 29, 2018 at 9:04

3 Answers 3

5

I'd do it with an autocommand that fires whenever you leave insert mode:

function! PreciseTrimWhiteSpace()
  " We need to save the view because the substitute command might
  " or might not move the cursor, depending on whether it finds
  " any whitespace.
  let saved_view = winsaveview()

  " Remove white space. Ignore "not found" errors. Don't change jumplist.
  keepjumps '[,']s/\s\+$//e

  " Move cursor back if necessary.
  call winrestview(saved_view)
endfunction

augroup PreciseTrimWhiteSpace
  autocmd!
  autocmd InsertLeave * call PreciseTrimWhiteSpace()
augroup end

This performs a substitute command using the range of lines between the '[ and '] marks: i.e. it removes whitespace that was introduced by the most recent insert.

To me, this seems more elegant than using an insert mapping, (but others may disagree).

For more details, see:

  • :help InsertLeave
  • :help ranges
  • :help :s_flags
  • :help :keepjumps
  • :help '[
4
  • You may also be interested by the CursorHold event. Personally, I prefer to execute the substitution on save for specific file types with BufWritePre.
    – LEI
    Mar 28, 2018 at 17:52
  • @LEI It would be quite tricky to track all the lines that have been edited if you want to do the substitution on save, though.
    – Rich
    Mar 28, 2018 at 19:35
  • Indeed I overlooked the conflict part of the question. My point is that InsertLeave does not cover all edits like pasting or moving text, until you enter in insert mode.
    – LEI
    Mar 28, 2018 at 19:59
  • @LEI Yeah, the OP's existing solution that they're looking to replace already applies the substitution to the entire file on save. Your point about non-insert edits is a very good one, though: I'll update my answer.
    – Rich
    Mar 28, 2018 at 21:17
3

I think someone is probably going to shoot me for this monstrosity. However:

inoremap <CR> <Esc>g_lDo

This maps insert mode Enter to:

  • <Esc>: return to normal mode
  • g_: move to last non-blank character in line
  • l: move one character to the right
  • D: delete to end of line
  • o: move to the next line, returning to insert mode

While I guess this accomplishes what you describe, I suspect there's a case I haven't thought of which will produce weird and wonderful behaviour! It could also prove quite nasty for any file types where whitespace is important or required.

As you note, there are tools out there which strip whitespace from end of line on save. For completeness, I'll mention that the Vim Wikia describes a number of them, this for example:

" Strip line endings for certain filetypes
autocmd FileType c,cpp,java,php autocmd BufWritePre <buffer> %s/\s\+$//e
1
  • Thanks for your idea. I agree it seems brutal. Your example for stripping trailing WS when saving is not perfect. The function used in the answer I linked is much better - especially in regards to position-history.
    – Patrick B.
    Mar 28, 2018 at 12:11
0

I'd count the number of space characters before the cursor at the moment <cr> is hit and produce as many <BS>. It can be done with substitute:

:inoremap <expr> <cr> substitute(matchstr(getline('.')[: col('.')-2], '\S\zs\s*$'), '.', "<bs>", 'g')."<cr>"

or with repeat+len:

:inoremap <expr> <cr> repeat("<bs>", len(matchstr(getline('.')[: col('.')-2], '\S\zs\s*$')))."<cr>"

Notes:

  • As there is no movement/break (with <esc>) in the flow of this solution, anything that is typed can be undone, redone , etc. However, if you start a new insert sequence to cut after a series of spaces, redoing the same insertion sequence with . elsewhere is likely to remove too many characters.
  • The \S\zs is a workaround when we're on en empty line and when each <BS> deletes &sw characters. In that case I should check getline() result and do something smarter.

A version that behaves a little bit better regarding indented empty lines is the following:

function! s:trim_ws_on_the_fly()
  let line = getline('.')
  " TODO: set and reset &bs
  if line =~ '^\s*$'
    let nb = (virtcol('.') + 2*&sw-2) / &sw - 1
    " echomsg "remove "virtcol('.').'/'.&sw.' -> '.nb
  else
    let nb = len(matchstr(line[: col('.')-2], '\s*$'))
    let line = ''
  endif
  return repeat("\<bs>", nb)."\<cr>".line
endfunction
inoremap <expr> <cr> <sid>trim_ws_on_the_fly()

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.