Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Substituting zero-width match in vim script

Tags:

regex

vim

I have written this script that replaces many spaces around the cursor with one space. This however doesn't work when I use it with no spaces around the cursor. It seems to me that Vim doesn't replace on a zero-width match.

function JustOneSpace()
    let save_cursor = getpos(".")
    let pos = searchpos(' \+', 'bc')
    s/\s*\%#\s*/ /e
    let save_cursor[2] = pos[1] + 1 
    call setpos('.', save_cursor)
endfunction

nmap <space> :call JustOneSpace()<cr>

Here are a few examples (pipe | is cursor):

This line

hello     |      world

becomes

hello |world

But this line

hello wo|rld

doesn't become

hello wo |rld

Update: By changing the function to the following it works for the examples above.

function JustOneSpace()
    let save_cursor = getpos(".")
    let pos = searchpos(' *', 'bc')
    s/\s*\%#\s*/ /e
    let save_cursor[2] = pos[1] + 1 
    call setpos('.', save_cursor)
endfunction

This line

hello |world

becomes

hello w|orld

The problem is that the cursors moves to the next character. It should stay in the same place.

Any pointers and or tips?

like image 707
Peter Stuifzand Avatar asked Apr 20 '26 08:04

Peter Stuifzand


2 Answers

I think that the only problem with your script is that the position saving doesn't seem correct. You can essentially do what you are trying to do with:

:s/\s*\%#\s*/ /e

which is identical to the (correct) code in your question. You could simply map this with:

:nmap <space> :s/\s*\%#\s*/ /e<CR>

If you want to save the position, it gets a little more complicated. Probably the best bet is to use something like this:

function! JustOneSpace()
    " Get the current contents of the current line
    let current_line = getline(".")
    " Get the current cursor position
    let cursor_position = getpos(".")
    " Generate a match using the column number of the current cursor position
    let matchRE = '\(\s*\)\%' . cursor_position[2] . 'c\s*'
    " Find the number of spaces that precede the cursor
    let isolate_preceding_spacesRE = '^.\{-}' . matchRE . '.*$'
    let preceding_spaces = substitute(current_line, isolate_preceding_spacesRE, '\1', "")
    " Modify the line by replacing with one space
    let modified_line = substitute(current_line, matchRE, " ", "")
    " Modify the cursor position to handle the change in string length
    let cursor_position[2] -= len(preceding_spaces) - 1
    " Set the line in the window
    call setline(".", modified_line)
    " Reset the cursor position
    call setpos(".", cursor_position)
endfunction

Most of that is comments, but the key thing is that you look at the length of the line before and after the substitution and decide on the new cursor position accordingly. You could do this with your method by comparing len(getline(".")) before and after if you prefer.

Edit

If you want the cursor to end after the space character, modify the line:

    let cursor_position[2] -= len(current_line) - len(modified_line)

such that it looks like this:

    let cursor_position[2] -= (len(current_line) - len(modified_line)) - 1

Edit (2)

I've changed the script above to consider your comments such that the cursor position is only adjusted by the number of spaces before the cursor position. This is done by creating a second regular expression that extracts the spaces preceding the cursor (and nothing else) from the line and then adjusting the cursor position by the number of spaces.

like image 78
DrAl Avatar answered Apr 21 '26 22:04

DrAl


I don't use vim, but if you want to match zero or more spaces, shouldn't you be using ' *' instead of ' \+' ?

EDIT: re the cursor positioning problem: what you're doing now is setting the position at the beginning of the whitespace before you do the substitution, then moving it forward one position so it's after the space. Try setting it at the end of the match instead, like this:

search(' *', 'bce')

That way, any additions or removals will occur before the cursor position. In most editors, the cursor position automatically moves to track such changes. You shouldn't need to do any of that getpos/setpos stuff.

like image 27
Alan Moore Avatar answered Apr 21 '26 20:04

Alan Moore



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!