Skip to main content
11 of 15
deleted 11 characters in body
user9433424
  • 6.2k
  • 2
  • 21
  • 30

You could use the maparg() function.

To test if the user mapped something to <C-c> in normal mode, you would write:

if !empty(maparg('<C-c>', 'n'))

If the user mapped something, to store the {rhs} in a variable, you would write:

let rhs_save = maparg('<C-c>', 'n')

If you want more information about the mapping, like:

  • is it silent (<silent> argument)?
  • is it local to the current buffer (<buffer> argument)?
  • is the {rhs} the evaluation of an expression (<expr> argument)?
  • does it remap the {rhs} (nnoremap vs nmap)?
  • if the user has another mapping which begins with <C-c>, does Vim wait for more characters to be typed (<nowait> argument)?
  • ...

Then, you could give a third and a fourth argument: 0 and 1.
0 because you're looking for a mapping and not an abbreviation, and 1 because you want a dictionary with a maximum of information and not just the {rhs} value:

let map_save = maparg('<C-c>', 'n', 0, 1)

Assuming the user didn't use any special argument in his mapping, and that it doesn't remap the {rhs}, to restore it, you could simply write:

let rhs_save = maparg('<C-c>', 'n')

" do some stuff which changes the mapping

exe 'nnoremap <C-c> ' . rhs_save

Or to be sure and restore all the possible arguments:

let map_save = maparg('<C-c>', 'n', 0, 1)

" do some stuff which changes the mapping

exe (map_save.noremap ? 'nnoremap' : 'nmap') .
     \ (map_save.buffer ? ' <buffer> ' : '') .
     \ (map_save.expr ? ' <expr> ' : '') .
     \ (map_save.nowait ? ' <nowait> ' : '') .
     \ (map_save.silent ? ' <silent> ' : '') .
     \ ' <C-c> ' .
     \ map_save.rhs

Edit: Sorry, I've just realised it wouldn't work as expected if the user calls a script-local function in the {rhs} of the mapping.

Suppose that the user has the following mapping inside his vimrc:

nnoremap <C-c> :<C-U>call <SID>FuncA()<CR>

function! s:FuncA()
    echo 'hello world!'
endfunction

When he hits <C-c>, it displays the message hello world!.

And in your plugin, you save a dictionary with all the information, then temporarily change his mapping like this:

let map_save = maparg('<C-c>', 'n', 0, 1)
nnoremap <C-c> :<C-U>call <SID>FuncB()<CR>

function! s:FuncB()
    echo 'bye all!'
endfunction

Now, it will display bye all!. Your plugin does some work, and when it's over, it tries to restore the mapping with the previous command.

It will probably fail with a message looking like this:

E117: Unknown function: <SNR>61_FuncA

61 is just the identifier of the script in which your mapping command would be executed. It could be any other number. If your plugin is the 42th file sourced on the user's system, it will be 42.

Inside a script, when a mapping command is executed, Vim automatically translates the notation <SID> into the special key code <SNR>, followed by a number which is unique for the script, and an underscore. It has to do this, because when the user will hit <C-c>, the mapping will be executed outside of the script, and thus it won't know in which script FuncA() is defined.

The problem is that the original mapping was sourced in a different script than your plugin, so here the automatic translation is wrong. It uses the identifier of your script, while it should use the identifier of the user's vimrc.

But you could do the translation manually. The dictionary map_save contains a key called 'sid' whose value is the correct identifier.
So, to make the previous restoration command more robust, you could replace map_save.rhs with:

substitute(map_save.rhs, '<SID>', '<SNR>' . map_save.sid . '_', 'g')

If the {rhs} of the original mapping contained <SID>, it should be properly translated. Otherwise, nothing should be changed.

And if you want to shorten the code a little, you could replace the 4 lines which take care of the special arguments with:

join(map(['buffer', 'expr', 'nowait', 'silent'], 'map_save[v:val] ? "<" . v:val . ">": ""'))

The map() function should convert each item from the list ['buffer', 'expr', 'nowait', 'silent'] into the corresponding mapping argument but only if its key inside map_save is non-zero. Andjoin() should join all the items into a string.

So, a more robust way of saving and restoring the mapping could be:

let map_save = maparg('<C-c>', 'n', 0, 1)

" do some stuff which changes the mapping

exe (map_save.noremap ? 'nnoremap' : 'nmap') .
    \ join(map(['buffer', 'expr', 'nowait', 'silent'], 'map_save[v:val] ? "<" . v:val . ">": ""')) .
    \ map_save.lhs . ' ' .
    \ substitute(map_save.rhs, '<SID>', '<SNR>' . map_save.sid . '_', 'g')
user9433424
  • 6.2k
  • 2
  • 21
  • 30