1

I have the following two commands for making sure that * and g* move between small words containing $ in a sensible way in php. I know it's possible to just modify the iskeyword option for the buffer as a whole, but I'm looking to avoid doing that.

I'm using the following nnoremap commands to make * and g* temporarily use a different value for iskeyword.

autocmd Filetype php nnoremap <silent> * :setlocal iskeyword+=\$<cr>*:setlocal iskeyword-=\$<cr>
autocmd Filetype php nnoremap <silent> g* :setlocal iskeyword+=\$<cr>g*:setlocal iskeyword-=\$<cr>

It works but it's a bit ugly and not very modular. Is there a way to factor out these definitions into separate commands or functions that cleanly override iskeyword and restore the old definition and handle potential errors correctly?

1

In a filetype plugin, for example ~/.vim/after/ftplugin/php.vim, you could try this code:

nno <buffer> <nowait> <silent> *  :<c-u>exe <sid>wrap_star(1)<cr>
nno <buffer> <nowait> <silent> g* :<c-u>exe <sid>wrap_star(0)<cr>

fu! s:wrap_star(whole) abort
    let isk_save = &l:isk
    setl isk+=$
    try
        exe 'norm! '.v:count1.(a:whole ? '' : 'g').'*'
    catch
        return 'echoerr '.string(v:exception)
    finally
        let &l:isk = isk_save
    endtry
    return ''
endfu

As your original code did, the s:wrap_star() function should temporarily add the $ character to the buffer-local value of the 'iskeyword' option. The command responsible for moving the cursor is:

exe 'norm! '.v:count1.(a:whole ? '' : 'g').'*'

It's wrapped inside a try conditional to handle a possible error. No matter what happens, that is even if an error occurs, the finally clause should remove the $ character from 'iskeyword', before Vim finishes to process the function.

You can check this by adding a non-existing command, such as :abcd, inside the try conditional:

nno <buffer> <nowait> <silent> *  :<c-u>exe <sid>wrap_star(1)<cr>
nno <buffer> <nowait> <silent> g* :<c-u>exe <sid>wrap_star(0)<cr>

fu! s:wrap_star(whole) abort
    let isk_save = &l:isk
    setl isk+=$
    try
        abcd
        exe 'norm! '.v:count1.(a:whole ? '' : 'g').'*'
    catch
        return 'echoerr '.string(v:exception)
    finally
        let &l:isk = isk_save
    endtry
    return ''
endfu

If you hit * on a word, it should raise the error E492:

Vim:E492: Not an editor command:        abcd

After that, if you look at the value of 'isk' (:echo &l:isk), you shouldn't find the $ character.


Edit:

The <c-u> keycode removes all the characters between the cursor position and the beginning of the command-line. You are probably familiar with it, because it's a readline key binding which also works in a bash or zsh shell. When you have typed a wrong command, and you want to rewrite a new one, you can press C-u to kill the text from the cursor position up to the beginning.

It's not an obligation to use it, but it's a good habit. Watch this:

nno cd :echo 'hello'<cr>

Hit cd: it displays hello. Now, hit 2cd: it raises the error:

E481: No range allowed

This is because, the 2 key that you pressed, combined with the first colon in the right-hand-side of your mapping, caused the following automatic translation:

2:    →    :.,.+1

So, :echo received the range .,.+1 which means from the current line, down to the next one. But :echo doesn't accept a range, so it's an error.

In your case, it's the same thing, :exe doesn't accept a range. So, if you hit a number, you don't want :exe to be, wrongly, prefixed by a range.

If you press a number to repeat the motion, the function will still have access to it through the built-in variable v:count1.

See :h c^u for more info.

| improve this answer | |
  • what does the <c-u> before execute in the commands do? – Gregory Nisbet Oct 5 '17 at 5:20
  • @GregoryNisbet I edited the answer. If something is wrong or unclear, let me know, and I'll try to fix it. – user852573 Oct 5 '17 at 5:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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