EDIT @Octaviour's solution is way smarter, you should use it :-) Nonetheless I'll leave this answer here because I worked on it and because it contains some interesting part about the usage of <SID>
which can still be interesting to read
I believe that actually your problem is more complex than you could think:
What you want is not a second layout: if you want ca
to behave like cs
, it means that you want to remap ca
to call the function that cs
usually calls.
Here it is tricky because the cs
mapping is done by the vim surround script and this plugin doesn't provide a mapping override mechanism. Thus you'll need to use several tricks.
First let's check surround.vim code, especially the part where the mapping is done:
nmap cs <Plug>Csurround
nnoremap <silent> <Plug>Csurround :<C-U>call <SID>changesurround()<CR>
The first line shows that cs
is mapped to an expression which is local to the plugin: <Plug>Csurround
.
The second line show what <Plug>Csurround
(and thus cs
) is mapped to :<C-U>call <SID>changesurround()<CR>
. It means that it is mapped to call the plugin local function changesurround()
.
This means that if we want to map ca
to changesurround()
we will need to get the <SID>
of the surround.vim script.
See :h <SID>
for more details about the <SID>
variables.
To get the <SID>
of a script we can use the following function (largely inspired by :h scriptnames-dictionary
):
function! GetScriptSID(scriptName)
" Get the output of ":scriptnames" in the scriptnames_output variable.
let scriptnames_output = ''
redir => scriptnames_output
silent scriptnames
redir END
" Split the output into lines and parse each line
" Looking for vimsurround
let scripts = {}
for line in split(scriptnames_output, "\n")
" Only do non-blank lines.
if line =~ '\S'
" Get the first number in the line.
let nr = matchstr(line, '\d\+')
" Get the file name
let name = split(substitute(line, '.\+:\s*', '', ''), '\')[-1]
if name == a:scriptName
return nr
endif
endif
endfor
unlet scriptnames_output
endfunction
That we should call with call GetScritpSID('surround.vim')
. On my system it returns 31
because this plugin was the 31st script to be sourced at vim startup.
Now you should be able to enter the following command in the command line:
nnoremap <silent> ca :<C-U>call <SNR>31_changesurround()<CR>
Replace 31 by the result of your call to GetScriptSID
And you'll have ca
behaving like cs
That's nice but we are only half way through what we want to do. The next step is to put this mapping in your vimrc. We are have 2 problems:
You could put the command with the hard coded SID in your vimrc like this:
nnoremap <silent> ca :<C-U>call <SNR>31_changesurround()<CR>
But if you install/remove a script the SID might change and your mapping won't work anymore.
So you could use execute
to get the SID dynamically: you need to add the GetScriptSID()
function to your vimrc along with this line:
execute 'nnoremap <silent> ca :<C-U>call <SNR>' . GetScriptSID('surround.vim') . '_changesurround()<CR>'
But here when you'll use the mapping Vim will yell at you that 0_changesurround()
doesn't exists.
This is because the vimrc is sourced before the other scripts so your execution of scriptname
in your vimrc will return 0 scripts.
To solve this problem you'll have to do the following:
function! RemapVimSurround()
let surroundSID = GetScriptSID('surround.vim')
execute 'nnoremap <silent> ca :<C-U>call <SNR>' . surroundSID . '_changesurround()<CR>'
unmap cs
endfunction
autocmd VimEnter * call RemapVimSurround()
The autocommand will execute the function RemapVimSurround()
once all the scripts are sourced. This way the scriptname
execution in the function will return the correct SID and your mapping will be valid. Also we unmap cs
which was mapped by the plugin.
Finally you also wanted to have s
behave like a
in normal mode so you should be able to use:
nnoremap s a
To sum up you should add the following to your vimrc:
function! GetScriptSID(scriptName)
" Get the output of ":scriptnames" in the scriptnames_output variable.
let scriptnames_output = ''
redir => scriptnames_output
silent scriptnames
redir END
" Split the output into lines and parse each line
" Looking for vimsurround
let scripts = {}
for line in split(scriptnames_output, "\n")
" Only do non-blank lines.
if line =~ '\S'
" Get the first number in the line.
let nr = matchstr(line, '\d\+')
" Get the file name
let name = split(substitute(line, '.\+:\s*', '', ''), '\')[-1]
if name == a:scriptName
return nr
endif
endif
endfor
unlet scriptnames_output
endfunction
function! RemapVimSurround()
let surroundSID = GetScriptSID('surround.vim')
execute 'nnoremap <silent> ca :<C-U>call <SNR>' . surroundSID . '_changesurround()<CR>'
unmap cs
nnoremap s a
endfunction
autocmd VimEnter * call RemapVimSurround()
f1
doesn't dof!
(and I couldn't figure out what to do about it).s
to append anda
to substitute? It seems like more trouble than it's worth to me.:lmap 1 !
and:set iminsert=1
to be able to use the mapping withf
as well.lmap
before, but not:iminsert
. However, the bigger problem it creates is that if you use this, then it breaks macros. For example, if I record a macro in which I pressf
and1
(which translates tof!
, when I repeat the macro, it tries to dof1
. So unless you have a solution for that too, using:lmap
is more troublesome. (Maybe I should ask a real question for this)