diff --git a/autoload/fern/action.vim b/autoload/fern/action.vim new file mode 100644 index 00000000..b3cea2d4 --- /dev/null +++ b/autoload/fern/action.vim @@ -0,0 +1,13 @@ +let s:Action = vital#fern#import('App.Action') + +function! fern#action#_init() abort + call s:Action.init() +endfunction + +function! fern#action#call(...) abort + call call(s:Action.call, a:000, s:Action) +endfunction + +function! fern#action#list(...) abort + return call(s:Action.list, a:000, s:Action) +endfunction diff --git a/autoload/fern/internal/action.vim b/autoload/fern/internal/action.vim deleted file mode 100644 index cc16c1c2..00000000 --- a/autoload/fern/internal/action.vim +++ /dev/null @@ -1,188 +0,0 @@ -function! fern#internal#action#init() abort - nnoremap (fern-action-choice) :call map_choice() - nnoremap (fern-action-repeat) :call map_repeat() - nnoremap (fern-action-help) :call map_help(0) - nnoremap (fern-action-help:all) :call map_help(1) - - " NOTE: - " Action is core feature of fern so do NOT refer g:fern#disable_default_mappings - if !hasmapto('(fern-action-choice)', 'n') - nmap a (fern-action-choice) - endif - if !hasmapto('(fern-action-repeat)', 'n') - nmap . (fern-action-repeat) - endif - if !hasmapto('(fern-action-help)', 'n') - nmap ? (fern-action-help) - endif - - let prefix = 'fern-action-' - let b:fern_action = { - \ 'actions': s:build_actions(prefix), - \ 'previous': '', - \} -endfunction - -function! fern#internal#action#call(name, ...) abort - let options = extend({ - \ 'capture': 0, - \ 'verbose': 0, - \}, a:0 ? a:1 : {}, - \) - if !exists('b:fern_action') - throw 'the buffer has not been initialized for actions' - endif - if index(b:fern_action.actions, a:name) is# -1 - throw printf('no action %s found in the buffer', a:name) - endif - let b:fern_action.previous = a:name - let Fn = funcref('s:call', [a:name]) - if options.verbose - let Fn = funcref('s:verbose', [Fn]) - endif - if options.capture - let Fn = funcref('s:capture', [Fn]) - endif - call Fn() -endfunction - -function! s:map_choice() abort - if !exists('b:fern_action') - throw 'the buffer has not been initialized for actions' - endif - call inputsave() - try - let fn = get(function('s:complete_choice'), 'name') - let expr = input('action: ', '', printf('customlist,%s', fn)) - finally - call inputrestore() - endtry - let r = s:parse_expr(expr) - let ns = copy(b:fern_action.actions) - let r.name = get(filter(ns, { -> v:val =~# '^' . r.name }), 0) - if empty(r.name) - return - endif - call fern#internal#action#call(r.name, { - \ 'capture': r.capture, - \ 'verbose': r.verbose, - \}) -endfunction - -function! s:map_repeat() abort - if !exists('b:fern_action') - throw 'the buffer has not been initialized for actions' - endif - if empty(b:fern_action.previous) - return - endif - call fern#internal#action#call(b:fern_action.previous) -endfunction - -function! s:map_help(all) abort - let Sort = { a, b -> s:compare(a[1], b[1]) } - let rs = split(execute('nmap'), '\n') - call map(rs, { _, v -> v[3:] }) - call map(rs, { _, v -> matchlist(v, '^\([^ ]\+\)\s*\*\?@\?\(.*\)$')[1:2] }) - - " To action mapping - let rs1 = map(copy(rs), { _, v -> v + [matchstr(v[1], '^(fern-action-\zs.*\ze)$')] }) - call filter(rs1, { _, v -> !empty(v[2]) }) - call filter(rs1, { _, v -> v[0] !~# '^' || v[0] =~# '^(fern-action-' }) - call map(rs1, { _, v -> [v[0], v[2], v[1]] }) - - " From action mapping - let rs2 = map(copy(rs), { _, v -> v + [matchstr(v[0], '^(fern-action-\zs.*\ze)$')] }) - call filter(rs2, { _, v -> !empty(v[2]) }) - call map(rs2, { _, v -> ['', v[2], v[0]] }) - - let rs = uniq(sort(rs1 + rs2, Sort), Sort) - if !a:all - call filter(rs, { -> v:val[1] !~# ':' || !empty(v:val[0]) }) - endif - let len0 = max(map(copy(rs), { -> len(v:val[0]) })) - let len1 = max(map(copy(rs), { -> len(v:val[1]) })) - let len2 = max(map(copy(rs), { -> len(v:val[2]) })) - call map(rs, { _, v -> [ - \ printf(printf('%%-%dS', len0), v[0]), - \ printf(printf('%%-%dS', len1), v[1]), - \ printf(printf('%%-%dS', len2), v[2]), - \ ] - \}) - - call map(rs, { -> join(v:val, ' ') }) - if !a:all - echohl Title - echo "NOTE: Some actions are concealed. Use 'help:all' action to see all actions." - echohl None - endif - echo join(rs, "\n") -endfunction - -function! s:parse_expr(expr) abort - if empty(a:expr) - return {'name' : '', 'capture': 0, 'verbose': 0} - endif - let terms = split(a:expr) - let name = remove(terms, -1) - let Has = { ns, n -> len(filter(copy(ns), { -> v:val ==# n })) } - return { - \ 'name': name, - \ 'capture': Has(terms, 'capture'), - \ 'verbose': Has(terms, 'verbose'), - \} -endfunction - -function! s:build_actions(prefix) abort - let n = len(a:prefix) - let ms = split(execute(printf('nmap (%s', a:prefix)), '\n') - call map(ms, { _, v -> split(v)[1] }) - call map(ms, { _, v -> matchstr(v, '^(\zs.*\ze)$') }) - call filter(ms, { _, v -> !empty(v) }) - call map(ms, { _, expr -> expr[n :] }) - return sort(ms) -endfunction - -function! s:complete_choice(arglead, cmdline, cursorpos) abort - if !exists('b:fern_action') - return [] - endif - let names = copy(b:fern_action.actions) - let names += ['capture', 'verbose'] - if empty(a:arglead) - call filter(names, { -> v:val !~# ':' }) - endif - return filter(names, { -> v:val =~# '^' . a:arglead }) -endfunction - -function! s:compare(i1, i2) abort - return a:i1 == a:i2 ? 0 : a:i1 > a:i2 ? 1 : -1 -endfunction - -function! s:call(name) abort - execute printf("normal \(fern-action-%s)", a:name) -endfunction - -function! s:capture(fn) abort - let output = execute('call a:fn()') - let rs = split(output, '\r\?\n') - execute printf('botright %dnew', len(rs)) - call setline(1, rs) - setlocal buftype=nofile bufhidden=wipe - setlocal noswapfile nobuflisted - setlocal nomodifiable nomodified - setlocal nolist signcolumn=no - setlocal nonumber norelativenumber - setlocal cursorline - nnoremap q :q -endfunction - -function! s:verbose(fn) abort - let verbose_saved = &verbose - try - set verbose - call a:fn() - finally - let &verbose = verbose_saved - endtry -endfunction diff --git a/autoload/fern/internal/locator.vim b/autoload/fern/internal/locator.vim index c1b2373d..18900be2 100644 --- a/autoload/fern/internal/locator.vim +++ b/autoload/fern/internal/locator.vim @@ -1,52 +1,25 @@ -let s:conditions = [ - \ { wi -> !wi.loclist }, - \ { wi -> !wi.quickfix }, - \ { wi -> !getwinvar(wi.winid, '&winfixwidth', 0) }, - \ { wi -> !getwinvar(wi.winid, '&winfixheight', 0) }, - \ { wi -> !getbufvar(wi.bufnr, '&previewwindow', 0) }, - \] -function! fern#internal#locator#score(winnr) abort +let s:WindowLocator = vital#fern#import('App.WindowLocator') - let winid = win_getid(a:winnr) - let wininfo = getwininfo(winid) - if empty(wininfo) - return 0 - endif - let wi = wininfo[0] - let score = 1 - for Condition in s:conditions - let score += Condition(wi) - endfor - return score +function! fern#internal#locator#list(...) abort + return call(s:WindowLocator.list, a:000, s:WindowLocator) endfunction -function! fern#internal#locator#find(origin) abort - let nwinnr = winnr('$') - if nwinnr == 1 - return 1 - endif - let origin = a:origin == 0 ? winnr() : a:origin - let former = range(origin, winnr('$')) - let latter = reverse(range(1, origin - 1)) - let threshold = g:fern#internal#locator#THRESHOLD - while threshold > 0 - for winnr in (former + latter) - if fern#internal#locator#score(winnr) >= threshold - return winnr - endif - endfor - let threshold -= 1 - endwhile - return 0 +function! fern#internal#locator#focus(...) abort + return call(s:WindowLocator.focus, a:000, s:WindowLocator) endfunction -function! fern#internal#locator#focus(origin) abort - let winnr = fern#internal#locator#find(a:origin) - if winnr == 0 || winnr == winnr() - return 1 - endif - call win_gotoid(win_getid(winnr)) +function! fern#internal#locator#get_condition(...) abort + return call(s:WindowLocator.get_condition, a:000, s:WindowLocator) endfunction -let g:fern#internal#locator#THRESHOLD = len(s:conditions) + 1 -lockvar g:fern#internal#locator#THRESHOLD +function! fern#internal#locator#set_condition(...) abort + return call(s:WindowLocator.set_condition, a:000, s:WindowLocator) +endfunction + +function! fern#internal#locator#get_threshold(...) abort + return call(s:WindowLocator.get_threshold, a:000, s:WindowLocator) +endfunction + +function! fern#internal#locator#set_threshold(...) abort + return call(s:WindowLocator.set_threshold, a:000, s:WindowLocator) +endfunction diff --git a/autoload/fern/internal/viewer.vim b/autoload/fern/internal/viewer.vim index 46280f12..497839c0 100644 --- a/autoload/fern/internal/viewer.vim +++ b/autoload/fern/internal/viewer.vim @@ -101,7 +101,7 @@ function! s:init() abort call helper.fern.renderer.syntax() call fern#hook#emit('viewer:syntax', helper) doautocmd User FernSyntax - call fern#internal#action#init() + call fern#action#_init() let Profile = fern#profile#start('fern#internal#viewer:init') return s:Promise.resolve() diff --git a/autoload/fern/internal/window.vim b/autoload/fern/internal/window.vim index ca136e87..000c5777 100644 --- a/autoload/fern/internal/window.vim +++ b/autoload/fern/internal/window.vim @@ -1,4 +1,5 @@ let s:Config = vital#fern#import('Config') +let s:WindowSelector = vital#fern#import('App.WindowSelector') function! fern#internal#window#find(predicator, ...) abort let n = winnr('$') @@ -20,102 +21,27 @@ function! fern#internal#window#find(predicator, ...) abort endfunction function! fern#internal#window#select() abort - let threshold = g:fern#internal#locator#THRESHOLD - while threshold > 0 - let ws = filter( - \ range(1, winnr('$')), - \ { -> fern#internal#locator#score(v:val) >= threshold }, - \) - if empty(ws) - let threshold -= 1 - else - break - endif - endwhile - if empty(ws) - let ws = range(1, winnr('$')) - endif - return s:select(ws, { + let ws = fern#internal#locator#list() + let ws = empty(ws) ? range(1, winnr('$')) : ws + return s:WindowSelector.select(ws, { \ 'auto_select': g:fern#internal#window#auto_select, + \ 'select_chars': g:fern#internal#window#select_chars, + \ 'statusline_hl': 'FernWindowSelectStatusLine', + \ 'indicator_hl': 'FernWindowSelectIndicator', \}) endfunction -function! s:select(winnrs, ...) abort - let options = extend({ - \ 'auto_select': 0, - \}, a:0 ? a:1 : {}) - if options.auto_select && len(a:winnrs) <= 1 - call win_gotoid(len(a:winnrs) ? win_getid(a:winnrs[0]) : win_getid()) - return 0 - endif - let length = len(a:winnrs) - let store = {} - for winnr in a:winnrs - let store[winnr] = getwinvar(winnr, '&statusline') - endfor - try - let chars = map( - \ range(length + 1), - \ { _, v -> get(g:fern#internal#window#select_chars, v, string(v)) }, - \) - call map(keys(store), { k, v -> setwinvar(v, '&statusline', s:statusline(v, chars[k])) }) - redrawstatus - call s:cnoremap_all(chars) - let n = input('choose window: ') - call s:cunmap_all() - redraw | echo - if n is# v:null - return 1 - endif - let n = index(chars, n) - if n is# -1 - return 1 - endif - call win_gotoid(win_getid(a:winnrs[n])) - finally - call map(keys(store), { _, v -> setwinvar(v, '&statusline', store[v]) }) - redrawstatus - endtry -endfunction - -function! s:statusline(winnr, char) abort - let width = winwidth(a:winnr) - len(a:winnr . '') - 6 - let leading = repeat(' ', width / 2) - return printf( - \ '%%#FernWindowSelectStatusLine#%s%%#FernWindowSelectIndicator# %s %%#FernWindowSelectStatusLine#', - \ leading, - \ a:char, - \) -endfunction - -function! s:cnoremap_all(chars) abort - for nr in range(256) - silent! execute printf("cnoremap \\ \ \", nr) - endfor - for char in a:chars - silent! execute printf("cnoremap \\ %s %s\", char, char) - endfor - silent! cunmap - silent! cunmap -endfunction - -function! s:cunmap_all() abort - for nr in range(256) - silent! execute printf("cunmap \ \", nr) - endfor -endfunction - -function! s:highlight() abort - highlight default link FernWindowSelectStatusLine StatusLineNC - highlight default link FernWindowSelectIndicator DiffText -endfunction - call s:Config.config(expand(':p'), { \ 'auto_select': 1, \ 'select_chars': split('abcdefghijklmnopqrstuvwxyz', '\zs'), \}) -augroup fern_internal_window_internal +function! s:highlight() abort + highlight default link FernWindowSelectStatusLine VitalWindowSelectorStatusLine + highlight default link FernWindowSelectIndicator VitalWindowSelectorIndicator +endfunction + +augroup fern_internal_window autocmd! autocmd ColorScheme * call s:highlight() augroup END diff --git a/autoload/vital/_fern/App/Action.vim b/autoload/vital/_fern/App/Action.vim new file mode 100644 index 00000000..fe362656 --- /dev/null +++ b/autoload/vital/_fern/App/Action.vim @@ -0,0 +1,242 @@ +" ___vital___ +" NOTE: lines between '" ___vital___' is generated by :Vitalize. +" Do not modify the code nor insert new lines before '" ___vital___' +function! s:_SID() abort + return matchstr(expand(''), '\zs\d\+\ze__SID$') +endfunction +execute join(['function! vital#_fern#App#Action#import() abort', printf("return map({'list': '', 'get_prefix': '', 'init': '', 'call': '', 'set_prefix': ''}, \"vital#_fern#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") +delfunction s:_SID +" ___vital___ +let s:prefix = matchstr( + \ fnamemodify(expand(''), ':p:h:h:t'), + \ '^\%(__\zs.*\ze__\|_\zs.*\)$', + \) + +function! s:get_prefix() abort + return s:prefix +endfunction + +function! s:set_prefix(prefix) abort + let s:prefix = a:prefix +endfunction + +function! s:init() abort + execute printf( + \ 'nnoremap (%s-action-choice) :call _map_choice()', + \ s:prefix, + \) + execute printf( + \ 'nnoremap (%s-action-repeat) :call _map_repeat()', + \ s:prefix, + \) + execute printf( + \ 'nnoremap (%s-action-help) :call _map_help(0)', + \ s:prefix, + \) + execute printf( + \ 'nnoremap (%s-action-help:all) :call _map_help(1)', + \ s:prefix, + \) + + if !hasmapto(printf('(%s-action-choice)', s:prefix), 'n') + execute printf( + \ 'nmap a (%s-action-choice)', + \ s:prefix, + \) + endif + if !hasmapto(printf('(%s-action-repeat)', s:prefix), 'n') + execute printf( + \ 'nmap . (%s-action-repeat)', + \ s:prefix, + \) + endif + if !hasmapto(printf('(%s-action-help)', s:prefix), 'n') + execute printf( + \ 'nmap ? (%s-action-help)', + \ s:prefix, + \) + endif + + let b:{s:prefix}_action = { + \ 'actions': s:_build_actions(), + \ 'previous': '', + \} +endfunction + +function! s:call(name, ...) abort + let options = extend({ + \ 'capture': 0, + \ 'verbose': 0, + \}, a:0 ? a:1 : {}, + \) + if !exists(printf('b:%s_action', s:prefix)) + throw 'the buffer has not been initialized for actions' + endif + if index(b:{s:prefix}_action.actions, a:name) is# -1 + throw printf('no action %s found in the buffer', a:name) + endif + let b:{s:prefix}_action.previous = a:name + let Fn = funcref('s:_call', [a:name]) + if options.verbose + let Fn = funcref('s:_verbose', [Fn]) + endif + if options.capture + let Fn = funcref('s:_capture', [Fn]) + endif + call Fn() +endfunction + +function! s:list(...) abort + let conceal = a:0 ? a:1 : v:true + let Sort = { a, b -> s:_compare(a[1], b[1]) } + let rs = split(execute('nmap'), '\n') + call map(rs, { _, v -> v[3:] }) + call map(rs, { _, v -> matchlist(v, '^\([^ ]\+\)\s*\*\?@\?\(.*\)$')[1:2] }) + + " To action mapping + let pattern1 = printf('^(%s-action-\zs.*\ze)$', s:prefix) + let pattern2 = printf('^(%s-action-', s:prefix) + let rs1 = map(copy(rs), { _, v -> v + [matchstr(v[1], pattern1)] }) + call filter(rs1, { _, v -> !empty(v[2]) }) + call filter(rs1, { _, v -> v[0] !~# '^' || v[0] =~# pattern2 }) + call map(rs1, { _, v -> [v[0], v[2], v[1]] }) + + " From action mapping + let rs2 = map(copy(rs), { _, v -> v + [matchstr(v[0], pattern1)] }) + call filter(rs2, { _, v -> !empty(v[2]) }) + call map(rs2, { _, v -> ['', v[2], v[0]] }) + + let rs = uniq(sort(rs1 + rs2, Sort), Sort) + if conceal + call filter(rs, { -> v:val[1] !~# ':' || !empty(v:val[0]) }) + endif + + return rs +endfunction + +function! s:_map_choice() abort + if !exists(printf('b:%s_action', s:prefix)) + throw 'the buffer has not been initialized for actions' + endif + call inputsave() + try + let fn = get(function('s:_complete_choice'), 'name') + let expr = input('action: ', '', printf('customlist,%s', fn)) + finally + call inputrestore() + endtry + let r = s:_parse_expr(expr) + let ns = copy(b:{s:prefix}_action.actions) + let r.name = get(filter(ns, { -> v:val =~# '^' . r.name }), 0) + if empty(r.name) + return + endif + call s:call(r.name, { + \ 'capture': r.capture, + \ 'verbose': r.verbose, + \}) +endfunction + +function! s:_map_repeat() abort + if !exists(printf('b:%s_action', s:prefix)) + throw 'the buffer has not been initialized for actions' + endif + if empty(b:{s:prefix}_action.previous) + return + endif + call s:call(b:{s:prefix}_action.previous) +endfunction + +function! s:_map_help(all) abort + let rs = s:list(!a:all) + + let len0 = max(map(copy(rs), { -> len(v:val[0]) })) + let len1 = max(map(copy(rs), { -> len(v:val[1]) })) + let len2 = max(map(copy(rs), { -> len(v:val[2]) })) + call map(rs, { _, v -> [ + \ printf(printf('%%-%dS', len0), v[0]), + \ printf(printf('%%-%dS', len1), v[1]), + \ printf(printf('%%-%dS', len2), v[2]), + \ ] + \}) + + call map(rs, { -> join(v:val, ' ') }) + if !a:all + echohl Title + echo "NOTE: Some actions are concealed. Use 'help:all' action to see all actions." + echohl None + endif + echo join(rs, "\n") +endfunction + +function! s:_parse_expr(expr) abort + if empty(a:expr) + return {'name' : '', 'capture': 0, 'verbose': 0} + endif + let terms = split(a:expr) + let name = remove(terms, -1) + let Has = { ns, n -> len(filter(copy(ns), { -> v:val ==# n })) } + return { + \ 'name': name, + \ 'capture': Has(terms, 'capture'), + \ 'verbose': Has(terms, 'verbose'), + \} +endfunction + +function! s:_build_actions() abort + let n = len(printf('%s-action-', s:prefix)) + let ms = split(execute(printf('nmap (%s-action-', s:prefix)), '\n') + call map(ms, { _, v -> split(v)[1] }) + call map(ms, { _, v -> matchstr(v, '^(\zs.*\ze)$') }) + call filter(ms, { _, v -> !empty(v) }) + call map(ms, { _, expr -> expr[n :] }) + return sort(ms) +endfunction + +function! s:_complete_choice(arglead, cmdline, cursorpos) abort + if !exists(printf('b:%s_action', s:prefix)) + return [] + endif + let names = copy(b:{s:prefix}_action.actions) + let names += ['capture', 'verbose'] + if empty(a:arglead) + call filter(names, { -> v:val !~# ':' }) + endif + return filter(names, { -> v:val =~# '^' . a:arglead }) +endfunction + +function! s:_compare(i1, i2) abort + return a:i1 == a:i2 ? 0 : a:i1 > a:i2 ? 1 : -1 +endfunction + +function! s:_call(name) abort + execute printf( + \ "normal \(%s-action-%s)", + \ s:prefix, + \ a:name, + \) +endfunction + +function! s:_capture(fn) abort + let output = execute('call a:fn()') + let rs = split(output, '\r\?\n') + execute printf('botright %dnew', len(rs)) + call setline(1, rs) + setlocal buftype=nofile bufhidden=wipe + setlocal noswapfile nobuflisted + setlocal nomodifiable nomodified + setlocal nolist signcolumn=no + setlocal nonumber norelativenumber + setlocal cursorline + nnoremap q :q +endfunction + +function! s:_verbose(fn) abort + let verbose_saved = &verbose + try + set verbose + call a:fn() + finally + let &verbose = verbose_saved + endtry +endfunction diff --git a/autoload/vital/_fern/App/WindowLocator.vim b/autoload/vital/_fern/App/WindowLocator.vim new file mode 100644 index 00000000..79cb98e8 --- /dev/null +++ b/autoload/vital/_fern/App/WindowLocator.vim @@ -0,0 +1,94 @@ +" ___vital___ +" NOTE: lines between '" ___vital___' is generated by :Vitalize. +" Do not modify the code nor insert new lines before '" ___vital___' +function! s:_SID() abort + return matchstr(expand(''), '\zs\d\+\ze__SID$') +endfunction +execute join(['function! vital#_fern#App#WindowLocator#import() abort', printf("return map({'focus': '', 'get_conditions': '', 'list': '', 'find': '', 'score': '', 'set_threshold': '', 'set_conditions': '', 'get_threshold': ''}, \"vital#_fern#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") +delfunction s:_SID +" ___vital___ +let s:threshold = 0 +let s:conditions = [ + \ { wi -> !wi.loclist }, + \ { wi -> !wi.quickfix }, + \ { wi -> !getwinvar(wi.winid, '&winfixwidth', 0) }, + \ { wi -> !getwinvar(wi.winid, '&winfixheight', 0) }, + \ { wi -> !getbufvar(wi.bufnr, '&previewwindow', 0) }, + \] + +function! s:score(winnr) abort + let winid = win_getid(a:winnr) + let wininfo = getwininfo(winid) + if empty(wininfo) + return 0 + endif + let wi = wininfo[0] + let score = 1 + for Condition in s:conditions + let score += Condition(wi) + endfor + return score +endfunction + +function! s:list() abort + let nwinnr = winnr('$') + if nwinnr == 1 + return 1 + endif + let threshold = s:get_threshold() + while threshold > 0 + let ws = filter( + \ range(1, winnr('$')), + \ { -> s:score(v:val) >= threshold } + \) + if !empty(ws) + break + endif + let threshold -= 1 + endwhile + return ws +endfunction + +function! s:find(origin) abort + let nwinnr = winnr('$') + if nwinnr == 1 + return 1 + endif + let origin = a:origin == 0 ? winnr() : a:origin + let former = range(origin, winnr('$')) + let latter = reverse(range(1, origin - 1)) + let threshold = s:get_threshold() + while threshold > 0 + for winnr in (former + latter) + if s:score(winnr) >= threshold + return winnr + endif + endfor + let threshold -= 1 + endwhile + return 0 +endfunction + +function! s:focus(origin) abort + let winnr = s:find(a:origin) + if winnr == 0 || winnr == winnr() + return 1 + endif + call win_gotoid(win_getid(winnr)) +endfunction + +function! s:get_conditions() abort + return copy(s:conditions) +endfunction + +function! s:set_conditions(conditions) abort + let s:conditions = copy(a:conditions) +endfunction + +function! s:get_threshold() abort + return s:threshold is# 0 ? len(s:conditions) + 1 : s:threshold +endfunction + +function! s:set_threshold(threshold) abort + let s:threshold = a:threshold +endfunction diff --git a/autoload/vital/_fern/App/WindowSelector.vim b/autoload/vital/_fern/App/WindowSelector.vim new file mode 100644 index 00000000..8f8fbad3 --- /dev/null +++ b/autoload/vital/_fern/App/WindowSelector.vim @@ -0,0 +1,97 @@ +" ___vital___ +" NOTE: lines between '" ___vital___' is generated by :Vitalize. +" Do not modify the code nor insert new lines before '" ___vital___' +function! s:_SID() abort + return matchstr(expand(''), '\zs\d\+\ze__SID$') +endfunction +execute join(['function! vital#_fern#App#WindowSelector#import() abort', printf("return map({'select': ''}, \"vital#_fern#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") +delfunction s:_SID +" ___vital___ +function! s:select(winnrs, ...) abort + let options = extend({ + \ 'auto_select': 0, + \ 'select_chars': split('abcdefghijklmnopqrstuvwxyz', '\zs'), + \ 'statusline_hl': 'VitalWindowSelectorStatusLine', + \ 'indicator_hl': 'VitalWindowSelectorIndicator', + \}, a:0 ? a:1 : {}) + if options.auto_select && len(a:winnrs) <= 1 + call win_gotoid(len(a:winnrs) ? win_getid(a:winnrs[0]) : win_getid()) + return 0 + endif + let length = len(a:winnrs) + let store = {} + for winnr in a:winnrs + let store[winnr] = getwinvar(winnr, '&statusline') + endfor + try + let scs = options.select_chars + let chars = map( + \ range(length + 1), + \ { _, v -> get(scs, v, string(v)) }, + \) + let S = funcref('s:_statusline', [ + \ options.statusline_hl, + \ options.indicator_hl, + \]) + call map(keys(store), { k, v -> setwinvar(v, '&statusline', S(v, chars[k])) }) + redrawstatus + call s:_cnoremap_all(chars) + let n = input('choose window: ') + call s:_cunmap_all() + redraw | echo + if n is# v:null + return 1 + endif + let n = index(chars, n) + if n is# -1 + return 1 + endif + call win_gotoid(win_getid(a:winnrs[n])) + finally + call map(keys(store), { _, v -> setwinvar(v, '&statusline', store[v]) }) + redrawstatus + endtry +endfunction + +function! s:_statusline(statusline_hl, indicator_hl, winnr, char) abort + let width = winwidth(a:winnr) - len(a:winnr . '') - 6 + let leading = repeat(' ', width / 2) + return printf( + \ '%%#%s#%s%%#%s# %s %%#%s#', + \ a:statusline_hl, + \ leading, + \ a:indicator_hl, + \ a:char, + \ a:statusline_hl, + \) +endfunction + +function! s:_cnoremap_all(chars) abort + for nr in range(256) + silent! execute printf("cnoremap \\ \ \", nr) + endfor + for char in a:chars + silent! execute printf("cnoremap \\ %s %s\", char, char) + endfor + silent! cunmap + silent! cunmap +endfunction + +function! s:_cunmap_all() abort + for nr in range(256) + silent! execute printf("cunmap \ \", nr) + endfor +endfunction + + +function! s:_highlight() abort + highlight default link VitalWindowSelectorStatusLine StatusLineNC + highlight default link VitalWindowSelectorIndicator DiffText +endfunction + +augroup vital_app_window_selector_internal + autocmd! + autocmd ColorScheme * call s:_highlight() +augroup END + +call s:_highlight() diff --git a/autoload/vital/fern.vital b/autoload/vital/fern.vital index abb8166f..8d914592 100644 --- a/autoload/vital/fern.vital +++ b/autoload/vital/fern.vital @@ -1,5 +1,5 @@ fern -ffd4400d5a19716716e8f803dc5084db74033ffc +08087a6270f290e8d1974885f6705131b95691d9 App.Spinner Async.CancellationTokenSource @@ -13,3 +13,6 @@ Lambda System.Filepath Vim.Window.Cursor Prompt +App.Action +App.WindowSelector +App.WindowLocator diff --git a/doc/fern-develop.txt b/doc/fern-develop.txt index 81227fd1..0121cdf8 100644 --- a/doc/fern-develop.txt +++ b/doc/fern-develop.txt @@ -625,6 +625,31 @@ fern#logger#error({object}...) ============================================================================= UTILITY *fern-develop-utility* + *fern#action#call()* +fern#action#call({name}[, {options}]) + Call an action {name} of the current buffer. + The following attributes are available in {options} + + "capture" 1 to enable capture mode which write output messages + into a new empty buffer instead + "verbose" 1 to execute action with 'verbose' (1) mode. + + *fern#action#list()* +fern#action#list([{conceal}]) + Return a |List| of available actions. Each item of the list is tuple + like [{lhs}, {name}, {rhs}] where {lhs} is an actual mapping, {name} + is an action name, and {rhs} is a mapping like: +> + assert_equal(fern#action#list(), [ + \ ['a', 'choice', '(fern-action-choice)'], + \ ['.', 'repeat', '(fern-action-repeat)'], + \ ['?', 'help', '(fern-action-help)'], + \ ['', 'help:all', '(fern-action-help:all)'], + \]) +< + When {conceal} is truthy value, it remove items which contains ":" in + it's name and no actual mapping (like "help:all" in above example.) + *fern#hook#add()* fern#hook#add({name}, {callback}[, {options}]) Add the {callback} to the {name} hook. diff --git a/test/fern/internal/action.vimspec b/test/fern/internal/action.vimspec deleted file mode 100644 index 049d78ff..00000000 --- a/test/fern/internal/action.vimspec +++ /dev/null @@ -1,44 +0,0 @@ -Describe fern#internal#action - After all - %bwipeout! - End - - Before - %bwipeout! - End - - Describe #init() - It defines mappings for actions - call fern#internal#action#init() - let rs = map( - \ split(execute('nmap (fern-action'), '\n'), - \ { -> split(v:val)[1] }, - \) - Assert Equals(sort(rs), sort([ - \ '(fern-action-choice)', - \ '(fern-action-repeat)', - \ '(fern-action-help)', - \ '(fern-action-help:all)', - \])) - End - End - - Describe #call() - It invoke a corresponding mapping of a given name - function! s:map_test() abort - let b:called = 1 - endfunction - - nnoremap (fern-action-test) :call map_test() - - call fern#internal#action#init() - call fern#internal#action#call('test') - Assert Equals(b:called, 1) - End - - It throws exception when no corresponding mapping exists - call fern#internal#action#init() - Throws /no action test found in the buffer/ fern#internal#action#call('test') - End - End -End