1

Let's say I have a function like this:

function! Foo()
  echom 'foo'
endfunction

And a map like this:

nmap gf :<C-U>call Foo()<CR>0v$

When I type gf I see foo echoed and the current line is highlighted. So far, so good.

I want to be able to execute the rhs of the map without using the lhs. Since the rhs is processed as if typed, I tried this:

let rhs = maparg('gf', 'n')
execute 'normal!' rhs

But it doesn't appear to do anything. I also tried:

call feedkeys(rhs, 'xt')

But that didn't work either.

(My motivation is wanting to override a mapping by setting a new rhs which calls a function which can fall back to the original rhs when need be.)

How can I invoke the original rhs?

Thank you!

1
  • From :h maparg() "The returned String has special characters translated like in the output...". That is, it returns <C-U> as five chars, etc.
    – Matt
    Oct 22 '20 at 18:13
0

(My motivation is wanting to override a mapping by setting a new rhs which calls a function which can fall back to the original rhs when need be.)

First, save all the information about the original mapping via maparg(). To do so, you need to pass the optional boolean arguments {abbr} and {dict}:

let s:gf_mapsave = maparg('gf', 'n', v:false, v:true)

Then, in your new mapping – which overrides the original one – do whatever you want. And when you need to invoke the original mapping, use mapset() to temporarily restore it:

call mapset('n', v:false, s:gf_mapsave)

Feed the lhs into the typeahead buffer:

call feedkeys('gf', 'xt')

And restore your overriding mapping:

nno gf :call Override()<cr>

As an example:

fu Foo()
    echom 'foo'
endfu

nno gf :<c-u>call Foo()<cr>
let s:gf_mapsave = maparg('gf', 'n', v:false, v:true)

nno gf :call Override()<cr>
fu Override()
    try
        echom 'working'
        not_working
    " fall back
    catch
        call mapset('n', v:false, s:gf_mapsave)
        call feedkeys('gf', 'xt')
        nno gf :call Override()<cr>
    endtry
endfu

This requires the patch 8.2.0807.

For more info, see:

1
  • Thank you! I didn't know about mapset() and it makes things much easier. It still surprises me that there isn't a way to trigger a mapping's rhs without having to go through the lhs... Oct 23 '20 at 13:08
0

In a plugin, I have a lh#bracket#enrich_imap() function that I use to execute some action if a condition is found true, and trigger the previous mapping/default expected behaviour otherwise.

For instance, I use it in the following way:

call lh#brackets#enrich_imap('<cr>',
        \ {'condition': 'getline(".")[col(".")-2:col(".")-1]=="{}"',
        \   'action': 'lh#brackets#_add_newline_between_brackets()'},
        \ 0
        \ )

Internally, I use maparg() to fetch the previous mapping if any. Then, I take care with some old portable functions to correctly handle calls to script-local functions, and to build the appropriate mapping.

The main functions are

" from lh-vim-lib
function! lh#mapping#_build_rhs(mapping_definition) abort
  call lh#assert#value(a:mapping_definition)
        \.has_key('rhs')
  " Inject the right SNR instead of "<sid>"
  let rhs = substitute(a:mapping_definition.rhs, '\c<SID>', "\<SNR>".get(a:mapping_definition, 'sid', 'SID_EXPECTED').'_', 'g')
  return rhs
endfunction

function! lh#mapping#_switch(trigger, cases) abort
  return lh#mapping#_switch_int(a:trigger, a:cases)
endfunction

function! lh#mapping#_switch_int(trigger, cases) abort
  for c in a:cases
    if eval(c.condition)
      return eval(c.action)
    endif
  endfor
  return lh#mapping#reinterpret_escaped_char(eval(a:trigger))
endfunction

function! lh#mapping#reinterpret_escaped_char(seq) abort
  let seq = escape(a:seq, '"\')
  exe 'return "' .
        \   substitute( seq, '\\\\<\(.\{-}\)\\\\>', '"."\\<\1>"."', 'g' ) .  '"'
endfunction

-------

" from lh-brackets
function! lh#brackets#enrich_imap(trigger, case, isLocal, ...) abort
  " - Some keys, like '<bs>', cannot be used to code the default.
  " - Double "string(" because those chars are correctly interpreted with
  " lh#mapping#reinterpret_escaped_char(eval()), which requires nested strings...
  if a:0 == 0
    let previous = maparg(a:trigger, 'i', 0, 1)
    if !empty(previous) && previous.expr
      " If not an expression, I do know yet how to forward a non expr mapping
      " from an expr mapping definition
      let default = lh#mapping#_build_rhs(previous)
    else
      let default = string(a:trigger)
    endif
  else
    let default = string(a:1)
  endif
  let sCase='lh#mapping#_switch('.string(default).', '.string([a:case]).')'
  call s:DefineImap(a:trigger, sCase, a:isLocal)
endfunction

function! s:DefineImap(trigger, inserter, isLocal) abort
  if exists('*IMAP') && a:trigger !~? '<bs>\|<cr>\|<up>\|<down>\|<left>\|<right>'
    if a:isLocal
      call IMAP(a:trigger,  "\<c-r>=".a:inserter."\<cr>", &ft)
    else
      call IMAP(a:trigger,  "\<c-r>=".a:inserter."\<cr>", '')
    endif
  else
    call s:DefineMap('inore', a:trigger, (a:inserter), a:isLocal, 1)
  endif
endfunction

function! s:DefineMap(mode, trigger, action, isLocal, isExpr) abort
  " The actual definition does much more as mappings from the plugin can be activated or deactivated
  let crt_mapping = {}
  let crt_mapping.lhs     = escape(a:trigger, '|') " need to escape bar
  let crt_mapping.mode    = a:mode
  let crt_mapping.rhs     = a:action
  let crt_mapping.buffer  = a:isLocal ? '<buffer> ' : ''
  let crt_mapping.expr    = a:isExpr  ? '<expr> '   : ''
  call s:Map(crt_mapping)
endfunction

function! s:Map(m) abort
  " Nowadays there exists a vimscript function that does the same; we no longer need :exe
  let cmd = a:m.mode.'map <silent> ' . a:m.expr . a:m.buffer . a:m.lhs .' '.a:m.rhs
  call s:Verbose(cmd)
  exe cmd
endfunction
3
  • Thank you – that's some impressive escaping! The fact that it takes so much code shows, I suppose, that it's not easy to invoke the rhs of a map without using the lhs, which still surprises me. Oct 23 '20 at 13:12
  • The escaping part is some 20ish years old code that I use to define complex mappings with expressions like {}<left> written as {}\<left\> to avoid troubles with :exe and some other things I've forgotten. It's likely to be useless in your current use case. These functions as used in other situations where the escaping is required. Oct 23 '20 at 16:46
  • Regarding the biggest difficulty of using the rhs, i'd say it's about correctly handling the SNR part. Oct 23 '20 at 16:49

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.