0

I tried to make a function

function! WrapSelect (front, back)
    "puts characters around the selected text.
    execute '''<, ''>s/\%V\(\_.*\)\%V\(\_.\)/'.a:front.'\1\2'.a:back.'/'
    nohl
endfunction
vnoremap <C-l> :call WrapSelect('[',']')<cr>

The reason there's a second group is to make it behave like yank: I want to put the brackets around the selected text, including the character under the cursor, the way yank copies selected text.

Example input (where | denotes highlighted section):

a|nd then some words
and some other wor|ds

Expected output

a[nd then some words
and some other wor]ds

Actual output: an error message:

 Error detected while processing function WrapSelect:
 line   15:
 E19: Mark has invalid line number
 Press ENTER or type command to continue

and the text becomes

 a[nd then some words
 and some other wor][ds
 ]

With more lines in the file unselected the output becomes more garbage:

a[[nd then some words
and some other wor][ds
third line
]
fifth line
]sixth line

Reading about substitute says that it does the search on each line in the selected range, and I don't see a way to turn that off and just do it on the whole block once.

I also saw here that there's a visual mode command I could possibly use, and you can make visual commands work with functions, so I tried:

function! WrapSelect (front, back)
    "puts characters around the selected text.
    execute "normal gv" . 'c'.a:front.'^R"'.a:back
endfunction
vnoremap <C-l> :call WrapSelect('[',']')<cr>

where ^R is the literal C-r from Ctrl+v Ctrl+r (it didn't paste into SE)

This produces different wrong output:

a[[nd then some words]
and some other wor]ds

This one seemed more promising from reading the help docs (:h v_c, :h i_CTRL-R), but still did not do what I expected.

I know plugins exist that can do cooler things than I can, but I would like to understand how to do this from my vimrc if possible.

1 Answer 1

1

I can think of a number of ways to do this but I'll just focus on what you tried.

First, there is a fundamental problem with the mappings. If you have visually selected text when the command line is opened with : the selection's line range, '<,'>, will be automatically inserted. You won't notice this when it's used in a mapping but it's there and it will interfere with the function's logic. Solution:

vnoremap <C-l> :<C-U>call WrapSelect('[',']')<cr>

Upon opening the command line, before inserting anything we clear any existing text with Ctrl-U by way of the equivalent keycode <C-U>.

On to the function code. Using your second approach I tweaked a couple things and it worked fine:

execute 'norm! gvc' . a:front. "\<C-R>\""  . a:back

I'm using norm! to ensure there are no user mappings interfering with what we want to do. And I'm using <C-R> instead of Ctrl-V+Ctrl-R. If you look at the help for :execute you'll find this...

        ":execute" is also a nice way to avoid having to type
        control characters in a Vim script for a ":normal"
        command: 
    :execute "normal ixxx\<Esc>"

The backslash before the keycode is required per expression strings where it also says "double quotes [must be] used" (which also means we need to escape the double quote signifying the " register).

Besides, keycodes are more readable and less error prone than Ctrl-V sequences.

6
  • Did this work for you in a function on multiple lines? It does not work for me on vim 7.4 in that setup. Aug 14, 2018 at 17:08
  • @jeremysprofile What does "does not work" mean? I tested it using your examples. I doubt I'm doing anything involving a post 7.4 change though I'll try to find an installation of that version and check it there. I recommend you rule out any local changes by testing with plugins disabled (vim --noplugin) and without your personal config (vim -u NONE).
    – B Layer
    Aug 14, 2018 at 17:35
  • I have no plugins installed. your command works from the command line, but does not seem to work from a test vimrc file that only contains the function and the hotkey. When it fails, it creates too many brackets similar to my existing bind. I will do more testing. Aug 14, 2018 at 17:41
  • @jeremysprofile Try adding <C-U> to the beginning of your mapping right after the :. Pretty sure that's your main problem now that I think about it.
    – B Layer
    Aug 14, 2018 at 17:58
  • 1
    Does it work for you with nnoremap instead of vnoremap? (I'm not in front of Vim right now.) Maybe with <ESC> before the :? Since you are using gv might as well deselect visual selection in all cases.
    – B Layer
    Aug 14, 2018 at 18:09

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.