I have the following solution in my .vimrc that allows me to wrap text with certain delimiters that works

function! Wrap(action, delim)
    let l:delims = {'(': ')', '[': ']', '{': '}', '"': '"', "'": "'"}
    if a:action == "w"
        return "c" . a:delim . "\<C-r>" . '"' . get(delims, a:delim) . "\<C-c>"
    elseif a:action == "d"
        return "vi" . a:delim . "c" . "\<BS>" . "\<Del>" . "\<Esc>" . "p" 

xnoremap <expr> w( Wrap("w", "(")
nnoremap <expr> wd( Wrap("d", "(")

xnoremap <expr> w[ Wrap("w", "[")
nnoremap <expr> wd[ Wrap("d", "[")

xnoremap <expr> w{ Wrap("w", "{")
nnoremap <expr> wd{ Wrap("d", "{")

xnoremap <expr> w" Wrap("w", '"')
nnoremap <expr> wd" Wrap("d", '"')

xnoremap <expr> w' Wrap("w", "'")
nnoremap <expr> wd' Wrap("d", "'")

I would, however, like to change this so that I can instead have one remapping for wrapping visually selected text and only one other remapping for removing surrounding delimiters. For example:

xnoremap <expr> w (pass arguments and call Wrap())
nnoremap <expr> wd (pass arguments and call Wrap())

I would then only need to press w ( on visually selected text to wrap it with (), or wd ( to remove the surround parenthesis from where the cursor is.

I can't seem to find the best way of doing this. I have tried with user-commands, but without success. Is there a way to do this?

I know that some plugins will solve this, but would like to try without plugins.

  • If you're ok with an extra <CR> keystoke, you can use input() in Wrap to get the argument
    – Hoblovski
    Commented Apr 14, 2023 at 6:30
  • Thanks @Hoblovski, that works!
    – hilbor
    Commented Apr 14, 2023 at 6:49

1 Answer 1

  1. Those are mappings, not "remappings".

  2. You can use :help getcharstr() to, well… get one character from the user:

    xnoremap <expr> <key> Wrap("w", getcharstr())
    nnoremap <expr> <key> Wrap("d", getcharstr())

A few comments, though,

  • Make sure you experiment with the various possible values of [expr] in getcharstr([expr]).

  • Mapping wd<char> in normal mode will make the following harder than necessary:

    • w to move to next word,
    • w to move to next word, then d( to delete from the cursor to the beginning of the sentence,
    • w to move to next word, then d{ to delete from the cursor to the beginning of the paragraph,
    • w to move to next word, then d'<char> to delete from the cursor to the given mark.

    If I were you I would reconsider my normal mode mapping. Surround, the plugin you are trying to replicate, uses mappings that don't conflict with regular commands. That is a best practice that is not reserved to plugin developers:

    • Surround's visual mode S would be the equivalent of your visual mode w,
    • and Surround's normal mode ds would be the equivalent of your normal mode wd.

    See this gist for mapping ideas.

  • 1
    FWIW in case getcharstr() is unavailable e.g. on vim <=8.1, you can use nr2char(getchar()).
    – Hoblovski
    Commented Apr 14, 2023 at 11:18
  • Thanks for the insights @romainl. I have also changed some of the mappings to not clash with other commands.
    – hilbor
    Commented Apr 14, 2023 at 12:03

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.