" vim:foldmethod=marker:fen:
scriptencoding utf-8
" Load Once {{{
if exists('g:loaded_savemap') && g:loaded_savemap
finish
endif
let g:loaded_savemap = 1
" }}}
" Saving 'cpoptions' {{{
let s:save_cpo = &cpo
set cpo&vim
" }}}
let g:savemap#version = str2nr(printf('%02d%02d%03d', 0, 2, 4))
" Interface {{{
function! savemap#load() "{{{
" dummy function to load this script
endfunction "}}}
function! savemap#save_map(...) "{{{
return call('s:save_map', [0] + a:000)
endfunction "}}}
function! savemap#save_abbr(...) "{{{
return call('s:save_map', [1] + a:000)
endfunction "}}}
function! savemap#supported_version() "{{{
return v:version > 703 || v:version == 703 && has('patch32')
endfunction "}}}
" }}}
" Implementation {{{
function! s:SID() "{{{
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$')
endfunction "}}}
let s:SID_PREFIX = s:SID()
delfunc s:SID
function! s:local_func(name) "{{{
return function('<SNR>' . s:SID_PREFIX . '_' . a:name)
endfunction "}}}
function! s:save_map(is_abbr, arg, ...) "{{{
if !savemap#supported_version()
return {}
endif
let map_dict = s:MapDict_new(a:is_abbr)
if type(a:arg) == type({})
\ && filter(copy(a:000), 'type(v:val) == type({})') ==# a:000
" {options}
for options in [a:arg] + a:000
for mode in s:split_maparg_modes(get(options, 'mode', 'nvo'))
for lhs in s:get_all_lhs(mode, a:is_abbr)
let map_info =
\ s:make_map_info(mode, lhs, a:is_abbr)
if s:match_map_info_string(
\ map_info, 'lhs', options, 'lhs')
\ && s:match_map_info_regexp(
\ map_info, 'lhs', options, 'lhs-regexp')
\ && s:match_map_info_string(
\ map_info, 'rhs', options, 'rhs')
\ && s:match_map_info_regexp(
\ map_info, 'rhs', options, 'rhs-regexp')
\ && s:match_map_info_bool(
\ map_info, 'silent', options, 'silent')
\ && s:match_map_info_bool(
\ map_info, 'noremap', options, 'noremap')
\ && s:match_map_info_bool(
\ map_info, 'expr', options, 'expr')
\ && s:match_map_info_bool(
\ map_info, 'buffer', options, 'buffer')
if has_key(options, 'buffer')
" Remove unmatched mapping.
let map_info[options.buffer ? 'normal' : 'buffer'] = {}
" Assert !empty(map_info[options.buffer ? 'buffer' : 'normal'])
endif
call map_dict.add_map_info(map_info)
endif
endfor
endfor
endfor
elseif type(a:arg) == type("")
\ && a:0 == 1
\ && type(a:1) == type("")
" {mode}, {lhs}
let [mode, lhs] = [a:arg, a:1]
call map_dict.add_map_info(
\ s:make_map_info(mode, lhs, a:is_abbr)
\)
elseif type(a:arg) == type("")
\ && a:0 == 0
" {mode}
let mode = a:arg
for lhs in s:get_all_lhs(mode, a:is_abbr)
call map_dict.add_map_info(
\ s:make_map_info(mode, lhs, a:is_abbr)
\)
endfor
else
echoerr 'invalid argument.'
return {}
endif
return map_dict
endfunction "}}}
function! s:MapDict_new(is_abbr) "{{{
let obj = {}
let obj.__is_abbr = a:is_abbr
let obj.__map_info = []
let obj.restore = s:local_func('MapDict_restore')
let obj.add_map_info = s:local_func('MapDict_add_map_info')
let obj.has_abbr = s:local_func('MapDict_has_abbr')
let obj.get_map_info = s:local_func('MapDict_get_map_info')
return obj
endfunction "}}}
function! s:MapDict_restore() dict "{{{
for d in self.__map_info
call s:restore_map_info(d.normal, self.__is_abbr)
call s:restore_map_info(d.buffer, self.__is_abbr)
endfor
endfunction "}}}
function! s:MapDict_add_map_info(map_info) dict "{{{
call add(self.__map_info, a:map_info)
endfunction "}}}
function! s:MapDict_has_abbr() dict "{{{
return self.__is_abbr
endfunction "}}}
function! s:MapDict_get_map_info() dict "{{{
return self.__map_info
endfunction "}}}
function! s:get_all_lhs(mode, is_abbr) "{{{
redir => output
silent execute a:mode . (a:is_abbr ? 'abbr' : 'map')
redir END
let r = []
let uniq = {}
for l in split(output, '\n')
let m = matchstr(l, '^.\s\+\zs\S\+')
if m != '' && !has_key(uniq, m)
call add(r, m)
let uniq[m] = 1
endif
endfor
return r
endfunction "}}}
function! s:make_map_info(mode, lhs, is_abbr) "{{{
let r = {
\ 'buffer': {},
\ 'normal': {},
\}
let info = s:maparg(a:lhs, a:mode, a:is_abbr, 1)
if empty(info)
" No such a mapping for a:lhs
elseif info.buffer
" <buffer>
let r.buffer = info
" Also save a non-<buffer> mapping if it exists.
call s:do_unmap_silently(a:mode, a:lhs, a:is_abbr, 1)
let r.normal = s:maparg(a:lhs, a:mode, a:is_abbr, 1)
call s:restore_map_info(r.buffer, a:is_abbr)
else
" non-<buffer>
let r.normal = info
endif
return r
endfunction "}}}
function! s:do_unmap_silently(mode, lhs, is_abbr, is_buffer) "{{{
if a:mode == '' || a:lhs == ''
return
endif
" Even if no such a mapping for a:lhs,
" this does not raise an error.
silent! execute
\ a:mode . (a:is_abbr ? 'unabbr' : 'unmap')
\ (a:is_buffer ? '<buffer>' : '')
\ a:lhs
endfunction "}}}
function! s:restore_map_info(map_info, is_abbr) "{{{
if empty(a:map_info)
return
endif
if a:map_info.lhs ==# '' || a:map_info.rhs ==# ''
echohl WarningMsg
echomsg 'invalid arguments: either lhs or rhs is empty'
echohl None
return
endif
for mode in s:split_maparg_modes(a:map_info.mode)
execute
\ mode . (a:map_info.noremap ? 'nore' : '')
\ . (a:is_abbr ? 'abbr' : 'map')
\ s:convert_maparg_options(a:map_info)
\ a:map_info.lhs
\ a:map_info.rhs
endfor
endfunction "}}}
function! s:match_map_info_regexp(map_info, map_info_name, options, option_name) "{{{
return s:match_map_info_compare(
\ a:map_info, a:map_info_name,
\ a:options, a:option_name,
\ function('s:compare_map_info_regexp'))
endfunction "}}}
function! s:compare_map_info_regexp(map_info, option) "{{{
return a:map_info =~# a:option
endfunction "}}}
function! s:match_map_info_string(map_info, map_info_name, options, option_name) "{{{
return s:match_map_info_compare(
\ a:map_info, a:map_info_name,
\ a:options, a:option_name,
\ function('s:compare_map_info_string'))
endfunction "}}}
function! s:compare_map_info_string(map_info, option) "{{{
return a:map_info ==# a:option
endfunction "}}}
function! s:match_map_info_bool(map_info, map_info_name, options, option_name) "{{{
return s:match_map_info_compare(
\ a:map_info, a:map_info_name,
\ a:options, a:option_name,
\ function('s:compare_map_info_bool'))
endfunction "}}}
function! s:compare_map_info_bool(map_info, option) "{{{
return !!a:map_info == !!a:option
endfunction "}}}
function! s:match_map_info_compare(map_info, map_info_name, options, option_name, compare) "{{{
" When a:options.buffer was given and 1,
" check only <buffer> mapping.
" When a:options.buffer was given and 0,
" check only non-<buffer> mapping.
" When a:options.buffer was not given,
" check both <buffer and non-<buffer> mappings.
if !has_key(a:options, a:option_name)
return 1
endif
if a:map_info_name ==# 'buffer'
return !empty(a:map_info[a:options.buffer ? 'buffer' : 'normal'])
else
let match_buffer =
\ (!has_key(a:options, 'buffer') || a:options.buffer)
\ && has_key(a:map_info.buffer, a:map_info_name)
\ && a:compare(
\ a:map_info.buffer[a:map_info_name],
\ a:options[a:option_name])
let match_normal =
\ (!has_key(a:options, 'buffer') || !a:options.buffer)
\ && has_key(a:map_info.normal, a:map_info_name)
\ && a:compare(
\ a:map_info.normal[a:map_info_name],
\ a:options[a:option_name])
let BOTH = 0
let BUFFER = 1
let NORMAL = 2
let check =
\ !has_key(a:options, 'buffer') ? BOTH
\ : a:options.buffer ? BUFFER
\ : NORMAL
return check ==# BOTH ? match_buffer || match_normal
\ : check ==# BUFFER ? match_buffer
\ : match_normal
endif
endfunction "}}}
function! s:maparg(...) "{{{
let info = call('maparg', a:000)
if has_key(info, 'lhs')
let info.lhs = substitute(info.lhs, '|', '<Bar>', 'g')
endif
return info
endfunction "}}}
function! s:convert_maparg_options(maparg) "{{{
return join(map(
\ ['silent', 'expr', 'buffer'],
\ 'a:maparg[v:val] ? "<" . v:val . ">" : ""'
\), '')
endfunction "}}}
function! s:split_maparg_modes(modes) "{{{
let h = {}
for _ in split(a:modes, '\zs')
if _ ==# ' '
let h['n'] = 1
let h['v'] = 1
let h['o'] = 1
elseif _ ==# '!'
let h['i'] = 1
let h['c'] = 1
else
let h[_] = 1
endif
endfor
return keys(h)
endfunction "}}}
" }}}
" Restore 'cpoptions' {{{
let &cpo = s:save_cpo
" }}}