Substituting zero-width match in vim script

…衆ロ難τιáo~ 提交于 2019-12-23 19:19:56

问题


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?


回答1:


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.




回答2:


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.




回答3:


This function is based on Al's answer.

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*'
    let pos = match(current_line, matchre) + 2

    " 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] = pos

    " Set the line in the window
    call setline(".", modified_line)
    " Reset the cursor position
    call setpos(".", cursor_position)
endfunction

Instead using the difference between the normal and the modified line, I find the position of the first space that will match the regular expression of the substitution. Then I set the cursor position to that position + 1.




回答4:


This simple one I use does almost the same:

nnoremap <leader>6 d/\S<CR>

Put the cursor till where you want to remove the spaces and it removes all the spaces after the cursor and the next text.



来源:https://stackoverflow.com/questions/1228100/substituting-zero-width-match-in-vim-script

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!