2

Here is an attempt to write a function which will remove trashy characters (^F, ^S, ^Z, zero-width space, etc) on save, and keep cursor position.

colorscheme desert
set guifont=Consolas:h10
set encoding=utf-8
setglobal fileencoding=utf-8
set noexpandtab
set list
set listchars=tab:→\ ,space:·

set nobackup
set noswapfile
set noundofile

function! RemoveTrash()
  let l:save = winsaveview()
  keeppatterns %s/[^[:print:]\t]//g
  call winrestview(l:save)
endfun

augroup Test
  autocmd!
  autocmd BufWritePre * :call RemoveTrash()
augroup END

The overall idea about function was taken from Martin Tournoij's answer about removing whitespace: https://vi.stackexchange.com/a/456, and regex was taken lincz's answer: https://stackoverflow.com/a/16135425.

Here is the test file:

>---foo##bar##baz
>---foo##bar##baz
>---foo##bar##baz

To post it here I replaced tab characters with >--- and control characters (^F, ^S) with ##.

However, when I save the file, the cursor position isn't really saved - it slightly moves to the right side accordingly number of control characters before it. Here are two examples:

  • If you have cursor on letter a in bar - it will be moved to r.
  • If you have cursor on letter b in baz - it will be moved to z (because there are two control characters before it).

And here are screenshots for the first example:

enter image description here

How to fix it?

DJMcMayhem posted an answer at February the 5th, but it seems it doesn't properly work for tab-indented lines. That's why I started a bounty. I tried to fix it by changing getcurpos()[2] to getcurpos()[4], but my "knowledge" of Vimscript is too low.

john c. j.
  • 309
  • 3
  • 21
  • 1
    The cursor position is correctly saved, just the text moved to the left, as you deleted characters. – Ralf Feb 05 '19 at 18:35

1 Answers1

1

Like Ralf said, your function is correctly saving the cursor position. The issue is when characters to the left of your cursor get deleted.

Here's what I came up with:

function! RemoveTrash()
  let l:initialCol = getcurpos()[2]
  let l:line = getline('.')[0:initialCol-1]

  let l:numRemoved = len(l:line) - len(substitute(l:line, '[^[:print:]\t]', "", "g"))

  let l:save = winsaveview()
  keeppatterns %s/[^[:print:]\t]//ge
  call winrestview(l:save)

  if l:numRemoved != 0
    call cursor(line('.'), (l:initialCol-l:numRemoved))
  endif
endfun

It keeps track of the old cursor position, and then calculates how many characters before that column will be deleted, and compensates.


Edit: switched exec normal | to cursor(), since that function compensates for tabs whereas | does not.

Edit 2: Added the /e flag to the substitute command so that it will not throw an error if there are no unprintables to remove.

DJMcMayhem
  • 17,581
  • 5
  • 53
  • 85
  • It works, thanks. (I just want to wait 15-20 minutes before marked as "answered"). As I understand, you intentionally use two different types of quote signs (single and double)? – john c. j. Feb 05 '19 at 18:58
  • @johncj Yes. I generally default to double quotes because I like them more, but I had some issues when I tried using them with substitute. I don't remember exactly, but I know that vim treats backslashes differently in single vs double quotes – DJMcMayhem Feb 05 '19 at 19:02
  • Just in case, Google suggest to use single quoted strings: https://google.github.io/styleguide/vimscriptguide.xml?showone=Strings#Strings – john c. j. Feb 05 '19 at 20:07
  • Also, echo l:numberOfCharsRemoved oftenly shows "0" for some reason (though removing itself works correctly). – john c. j. Feb 05 '19 at 20:33
  • Oh yes, that echo shouldn't be there, I just forgot to remove it. If it's 0, that just means there won't be any unprintables on the current line, so nothing to worry about. – DJMcMayhem Feb 05 '19 at 20:49
  • Sorry, it seems the code doesn't work properly at some circumstances. See updated test in the bottom of my question (I just replaced "xxx" with "baz" for convenience). If you save the file when your cursor is on a in bar - everything works correct. However, if you save the file when your cursor is on a in baz - it will move to b. – john c. j. Feb 05 '19 at 22:27
  • @johnc.j. Yes, that makes sense. See my edit. – DJMcMayhem Feb 05 '19 at 22:47
  • It doesn't currently work for tab-indented lines, and to make it more interesting to improve, I started a bounty :-) – john c. j. Feb 07 '19 at 20:20
  • @johnc.j. I've fixed it! It works for me with tab-indented lines and your example. – DJMcMayhem Feb 07 '19 at 20:27
  • Currently it works (thanks!). The only thing I changed is //ge instead of //g to avoid "Pattern not found" message when control characters aren't exist. I will test more and then assign bounty in the end of the bounty-week. – john c. j. Feb 08 '19 at 19:52
  • @johnc.j. Thanks for the bounty! The //ge tip is a good idea, I'll edit that in too. – DJMcMayhem Feb 10 '19 at 20:32