1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-24 02:00:05 +08:00
SpaceVim/bundle/gina.vim/autoload/vital/__gina__/Action.vim

574 lines
17 KiB
VimL

let s:t_funcref = type(function('tr'))
let s:t_list = type([])
let s:PREFIX = '_vital_action_binder_'
let s:UNIQUE = sha256(expand('<sfile>:p'))
let s:MODIFIERS = [
\ 'aboveleft',
\ 'belowright',
\ 'botright',
\ 'browse',
\ 'confirm',
\ 'hide',
\ 'keepalt',
\ 'keepjumps',
\ 'keepmarks',
\ 'keeppatterns',
\ 'lockmarks',
\ 'noswapfile',
\ 'silent',
\ 'tab',
\ 'topleft',
\ 'verbose',
\ 'vertical',
\]
function! s:_vital_loaded(V) abort
let s:Revelator = a:V.import('App.Revelator')
endfunction
function! s:_vital_depends() abort
return ['App.Revelator']
endfunction
function! s:_vital_created(module) abort
let a:module.name = 'action'
let a:module.mark_sign_text = '*'
endfunction
function! s:get() abort
return get(b:, s:PREFIX . s:UNIQUE, v:null)
endfunction
function! s:attach(candidates, ...) abort dict
let binder = copy(s:binder)
call extend(binder, {
\ 'name': substitute(self.name, '\W', '-', 'g'),
\ '_candidates': a:candidates,
\ 'actions': {},
\ 'aliases': {},
\ 'markable': 0,
\})
call extend(binder, get(a:000, 0, {}))
" Lock methods
lockvar binder._get_candidates
lockvar binder._get_marked_candidates
lockvar binder.define
lockvar binder.alias
lockvar binder.action
lockvar binder.call
" Define builtin actions
call binder.define('builtin:echo', function('s:_action_echo'), {
\ 'hidden': 1,
\ 'description': 'Echo candidates',
\ 'clear_marks': 0,
\})
call binder.define('builtin:help', function('s:_action_help'), {
\ 'description': 'Show a help of actions',
\ 'mapping_mode': 'n',
\ 'repeatable': 0,
\ 'use_marks': 0,
\ 'clear_marks': 0,
\})
call binder.define('builtin:help:all', function('s:_action_help'), {
\ 'description': 'Show a help of actions including hidden actions',
\ 'mapping_mode': 'n',
\ 'options': { 'all': 1 },
\ 'repeatable': 0,
\ 'use_marks': 0,
\ 'clear_marks': 0,
\})
call binder.define('builtin:choice', function('s:_action_choice'), {
\ 'description': 'Select an action to perform',
\ 'mapping_mode': 'inv',
\ 'repeatable': 0,
\ 'clear_marks': 0,
\})
call binder.define('builtin:repeat', function('s:_action_repeat'), {
\ 'description': 'Repeat a previous repeatable action',
\ 'mapping_mode': 'inv',
\ 'repeatable': 0,
\})
call binder.alias('echo', 'builtin:echo')
call binder.alias('help', 'builtin:help')
call binder.alias('help:all', 'builtin:help:all')
execute printf('nmap <buffer> ? <Plug>(%s-builtin-help)', binder.name)
execute printf('nmap <buffer> a <Plug>(%s-builtin-choice)', binder.name)
execute printf('vmap <buffer> a <Plug>(%s-builtin-choice)', binder.name)
execute printf('imap <buffer> a <Plug>(%s-builtin-choice)', binder.name)
execute printf('nmap <buffer> . <Plug>(%s-builtin-repeat)', binder.name)
execute printf('vmap <buffer> . <Plug>(%s-builtin-repeat)', binder.name)
execute printf('imap <buffer> . <Plug>(%s-builtin-repeat)', binder.name)
if binder.markable
execute printf(
\ 'sign define %s linehl=%s texthl=%s text=%s',
\ 'VitalActionMarkSelectedSign',
\ 'VitalActionMarkSelectedLine',
\ 'VitalActionMarkSelected',
\ self.mark_sign_text,
\)
call binder.define('builtin:mark', function('s:_action_mark'), {
\ 'description': 'Mark/Unmark selected candidates',
\ 'mapping_mode': 'nv',
\ 'requirements': ['__lnum'],
\ 'use_marks': 0,
\ 'clear_marks': 0,
\})
call binder.define('builtin:mark:set', function('s:_action_mark_set'), {
\ 'hidden': 1,
\ 'description': 'Mark selected candidates',
\ 'mapping_mode': 'nv',
\ 'requirements': ['__lnum'],
\ 'use_marks': 0,
\ 'clear_marks': 0,
\})
call binder.define('builtin:mark:unset', function('s:_action_mark_unset'), {
\ 'hidden': 1,
\ 'description': 'Unmark selected candidates',
\ 'mapping_mode': 'nv',
\ 'requirements': ['__lnum'],
\ 'use_marks': 0,
\ 'clear_marks': 0,
\})
call binder.define('builtin:mark:unall', function('s:_action_mark_unall'), {
\ 'hidden': 1,
\ 'description': 'Unmark all candidates',
\ 'mapping_mode': 'n',
\ 'use_marks': 0,
\ 'clear_marks': 0,
\})
call binder.alias('mark', 'builtin:mark')
call binder.alias('mark:unall', 'builtin:mark:unall')
execute printf('nmap <buffer> mm <Plug>(%s-builtin-mark)', binder.name)
execute printf('vmap <buffer> mm <Plug>(%s-builtin-mark)', binder.name)
execute printf('nmap <buffer> m+ <Plug>(%s-builtin-mark-set)', binder.name)
execute printf('vmap <buffer> m+ <Plug>(%s-builtin-mark-set)', binder.name)
execute printf('nmap <buffer> m- <Plug>(%s-builtin-mark-unset)', binder.name)
execute printf('vmap <buffer> m- <Plug>(%s-builtin-mark-unset)', binder.name)
execute printf('nmap <buffer> m* <Plug>(%s-builtin-mark-unall)', binder.name)
execute printf('nmap <buffer> <C-j> <Plug>(%s-builtin-mark)j', binder.name)
execute printf('nmap <buffer> <C-k> k<Plug>(%s-builtin-mark)', binder.name)
endif
let b:{s:PREFIX . s:UNIQUE} = binder
return binder
endfunction
" Instance -------------------------------------------------------------------
let s:binder = {}
function! s:binder._get_candidates(...) abort
let fline = get(a:000, 0, line('.'))
let lline = get(a:000, 1, fline)
if type(self._candidates) == s:t_funcref
let candidates = self._candidates(fline, lline)
else
let candidates = self._candidates[fline-1 : lline-1]
endif
return map(
\ deepcopy(candidates),
\ 'extend(v:val, {''__lnum'': fline + v:key})'
\)
endfunction
function! s:binder._get_marked_candidates() abort
if !self.markable
throw 'vital: Action: The action binder is not markable'
endif
let signs = s:_get_signs()
let lnums = map(signs, 'v:val.line')
let candidates = []
call map(lnums, 'extend(candidates, self._get_candidates(v:val, v:val))')
return candidates
endfunction
function! s:binder.define(name, callback, ...) abort
let action = extend({
\ 'callback': a:callback,
\ 'name': a:name,
\ 'description': '',
\ 'mapping': '',
\ 'mapping_mode': '',
\ 'requirements': [],
\ 'options': {},
\ 'hidden': 0,
\ 'repeatable': 1,
\ 'use_marks': 1,
\ 'clear_marks': 1,
\}, get(a:000, 0, {}),
\)
if empty(action.mapping)
let action.mapping = printf(
\ '<Plug>(%s-%s)',
\ substitute(self.name, '\W', '-', 'g'),
\ substitute(action.name, '\W', '-', 'g'),
\)
endif
for mode in split(action.mapping_mode, '\zs')
execute printf(
\ '%snoremap <buffer><silent> %s %s:%scall <SID>_call_for_mapping(''%s'')<CR>',
\ mode,
\ action.mapping,
\ mode =~# '[i]' ? '<Esc>' : '',
\ mode =~# '[ni]' ? '<C-u>' : '',
\ a:name,
\)
endfor
let self.actions[action.name] = action
endfunction
function! s:binder.alias(alias, expr, ...) abort
let alias = extend({
\ 'name': matchstr(a:expr, '\S\+$'),
\ 'expr': a:expr,
\ 'alias': a:alias,
\}, get(a:000, 0, {}),
\)
let self.aliases[a:alias] = alias
endfunction
function! s:binder.action(expr) abort
let mods = matchstr(a:expr, '^\%(.* \)\?\ze\S\+$')
let name = matchstr(a:expr, '\S\+$')
if has_key(self.aliases, name)
let alias = self.aliases[name]
return self.action(mods . alias.expr)
elseif has_key(self.actions, name)
return [mods, self.actions[name]]
endif
" If only one action/alias could be determine, use it.
let candidates = filter(
\ keys(self.aliases) + keys(self.actions),
\ 'v:val =~# ''^'' . name'
\)
" Shorter to Longer
call sort(candidates, { a, b -> len(a) - len(b) })
if empty(candidates)
throw s:Revelator.warning(printf(
\ 'No corresponding action has found for "%s"',
\ a:expr
\))
else
" Use the first match
return self.action(mods . candidates[0])
endif
endfunction
function! s:binder.call(expr, ...) abort range
let [mods, action] = self.action(a:expr)
if a:0 == 1 && type(a:1) == s:t_list
let candidates = a:1
elseif self.markable && action.use_marks
let candidates = self._get_marked_candidates()
let candidates = empty(candidates)
\ ? call(self._get_candidates, a:000, self)
\ : candidates
else
let candidates = call(self._get_candidates, a:000, self)
endif
if !empty(action.requirements)
call filter(
\ candidates,
\ 's:_is_satisfied(v:val, action.requirements)',
\)
endif
let options = extend({
\ 'mods': mods,
\}, action.options
\)
if self.markable && action.clear_marks
call self.call('builtin:mark:unall')
endif
call call(action.callback, [candidates, options], self)
return [mods, action]
endfunction
" Actions --------------------------------------------------------------------
function! s:_action_echo(candidates, options) abort
for candidate in a:candidates
echo string(candidate)
endfor
endfunction
function! s:_action_help(candidates, options) abort dict
let mappings = s:_find_mappings(self)
let actions = values(self.actions)
if !get(a:options, 'all')
call filter(actions, '!v:val.hidden')
endif
let rows = []
let longest1 = 0
let longest2 = 0
let longest3 = 0
for action in actions
let mapping = get(mappings, action.mapping, {})
let lhs = !empty(action.mapping) && !empty(mapping) ? mapping.lhs : ''
let alias = s:_find_alias(self, action)
let identifier = empty(alias)
\ ? action.name
\ : printf('%s [%s]', action.name, alias)
let hidden = action.hidden ? '*' : ' '
let description = action.description
let mapping = printf('%s [%s]', action.mapping, action.mapping_mode)
call add(rows, [
\ lhs,
\ identifier,
\ hidden,
\ description,
\ mapping,
\])
let longest1 = len(lhs) > longest1 ? len(lhs) : longest1
let longest2 = len(identifier) > longest2 ? len(identifier) : longest2
let longest3 = len(description) > longest3 ? len(description) : longest3
endfor
let content = []
let pattern = printf(
\ '%%-%ds %%-%ds %%s %%-%ds %%s',
\ longest1,
\ longest2,
\ longest3,
\)
for params in sort(rows, 's:_compare')
call add(content, call('printf', [pattern] + params))
endfor
redraw | echo join(content, "\n")
endfunction
function! s:_action_choice(candidates, options) abort dict
let s:_binder = self
call inputsave()
try
echohl Question
redraw | echo
let fname = s:_get_function_name(function('s:_complete_action_aliases'))
let aname = input(
\ 'action: ', '',
\ printf('customlist,%s', fname),
\)
redraw | echo
finally
echohl None
call inputrestore()
endtry
if empty(aname)
return
endif
let [mods, action] = self.call(aname, a:candidates)
if action.repeatable
let self._previous_action = [mods, action]
endif
endfunction
function! s:_action_repeat(candidates, options) abort dict
let [mods, action] = get(self, '_previous_action', ['', {}])
if empty(action)
return
endif
return self.call(join([mods, action.name]), a:candidates)
endfunction
function! s:_action_mark(candidates, options) abort dict
let set_candidates = []
let unset_candidates = []
let signmap = s:_get_signmap()
for candidate in a:candidates
if has_key(signmap, string(candidate.__lnum))
call add(unset_candidates, candidate)
else
call add(set_candidates, candidate)
endif
endfor
call self.call('builtin:mark:set', set_candidates)
call self.call('builtin:mark:unset', unset_candidates)
endfunction
function! s:_action_mark_set(candidates, options) abort dict
let options = extend({}, a:options)
let lnums = map(a:candidates, 'v:val.__lnum')
let bufnr = bufnr('%')
for lnum in lnums
execute printf(
\ 'sign place %d line=%d name=%s buffer=%d',
\ lnum, lnum,
\ 'VitalActionMarkSelectedSign',
\ bufnr,
\)
endfor
endfunction
function! s:_action_mark_unset(candidates, options) abort dict
let options = extend({}, a:options)
let lnums = map(a:candidates, 'v:val.__lnum')
let bufnr = bufnr('%')
for lnum in lnums
execute printf(
\ 'sign unplace %d buffer=%d',
\ lnum, bufnr,
\)
endfor
endfunction
function! s:_action_mark_unall(candidates, options) abort dict
let options = extend({}, a:options)
let bufnr = bufnr('%')
execute printf('sign unplace * buffer=%d', bufnr)
endfunction
" Privates -------------------------------------------------------------------
function! s:_is_satisfied(candidate, requirements) abort
for requirement in a:requirements
if !has_key(a:candidate, requirement)
return 0
endif
endfor
return 1
endfunction
function! s:_compare(i1, i2) abort
return a:i1[1] == a:i2[1] ? 0 : a:i1[1] > a:i2[1] ? 1 : -1
endfunction
function! s:_compare_length(w1, w2) abort
let l1 = len(a:w1)
let l2 = len(a:w2)
return l1 == l2 ? 0 : l1 > l2 ? 1 : -1
endfunction
function! s:_find_mappings(binder) abort
let content = s:_execute('map')
let rhss = filter(
\ map(values(a:binder.actions), 'v:val.mapping'),
\ '!empty(v:val)'
\)
let rhsp = printf('\%%(%s\)', join(map(rhss, 'escape(v:val, ''\'')'), '\|'))
let rows = filter(split(content, '\r\?\n'), 'v:val =~# ''@.*'' . rhsp')
let pattern = '\(...\)\(\S\+\)'
let mappings = {}
for row in rows
let [mode, lhs] = matchlist(row, pattern)[1 : 2]
let rhs = matchstr(row, rhsp)
let mappings[rhs] = {
\ 'mode': mode,
\ 'lhs': lhs,
\ 'rhs': rhs,
\}
endfor
return mappings
endfunction
function! s:_find_alias(binder, action) abort
let aliases = filter(
\ values(a:binder.aliases),
\ 'v:val.name ==# a:action.name',
\)
" Remove aliases with mods
call filter(aliases, 'v:val.name ==# v:val.expr')
" Prefer shorter name
let candidates = sort(
\ map(aliases, 'v:val.alias'),
\ function('s:_compare_length')
\)
return get(candidates, 0, '')
endfunction
function! s:_complete_action_aliases(arglead, cmdline, cursorpos) abort
let terms = split(a:arglead, ' ', 1)
" Build modifier candidates
let modifiers = terms[:-2]
let candidates = map(
\ filter(copy(s:MODIFIERS), 'index(modifiers, v:val) == -1'),
\ 'v:val . '' '''
\)
" Build action/alias candidates
let arglead = terms[-1]
let binder = s:_binder
let actions = values(binder.actions)
if empty(arglead)
call filter(actions, '!v:val.hidden')
endif
call extend(candidates, sort(extend(
\ map(actions, 'v:val.name'),
\ keys(binder.aliases),
\)), 0)
call filter(uniq(candidates), 'v:val =~# ''^'' . arglead')
call map(candidates, 'join(modifiers + [v:val])')
return candidates
endfunction
function! s:_call_for_mapping(name) abort range
let binder = s:get()
return s:Revelator.call(binder.call, [a:name, a:firstline, a:lastline], binder)
endfunction
function! s:_get_signs() abort
let content = split(s:_execute('sign place buffer=' . bufnr('%')), '\r\?\n')
let signs = map(
\ filter(content, 'v:val =~# ''^\s\{4}'''),
\ 's:_parse_sign(v:val)',
\)
return filter(signs, 'v:val.name ==# ''VitalActionMarkSelectedSign''')
endfunction
function! s:_get_signmap() abort
let signmap = {}
for sign in s:_get_signs()
let signmap[sign.line] = sign
endfor
return signmap
endfunction
function! s:_parse_sign(sign) abort
let terms = split(a:sign)
let sign = {}
for term in terms
let m = split(term, '=')
let sign[m[0]] = m[1]
endfor
return sign
endfunction
" Compatibility --------------------------------------------------------------
if has('patch-7.4.1842')
function! s:_get_function_name(fn) abort
return get(a:fn, 'name')
endfunction
else
function! s:_get_function_name(fn) abort
return matchstr(string(a:fn), 'function(''\zs.*\ze''')
endfunction
endif
if exists('*execute')
let s:_execute = function('execute')
else
function! s:_execute(command) abort
try
redir => content
silent execute a:command
finally
redir END
endtry
return content
endfunction
endif
" Highlight ------------------------------------------------------------------
function! s:_define_mark_highlights() abort
highlight default link VitalActionMarkSelectedLine Search
highlight default link VitalActionMarkSelected Search
endfunction
augroup vital_action_internal
autocmd! *
autocmd ColorScheme * call s:_define_mark_highlights()
augroup END
call s:_define_mark_highlights()