diff --git a/autoload/fern/internal/command/fern.vim b/autoload/fern/internal/command/fern.vim index 8debc07b..d2e6e70f 100644 --- a/autoload/fern/internal/command/fern.vim +++ b/autoload/fern/internal/command/fern.vim @@ -46,9 +46,6 @@ function! fern#internal#command#fern#command(mods, fargs) abort let opener = s:drawer_opener endif - " Resolve reveal - let reveal = empty(reveal) ? '' : expand(reveal) - let expr = expand(a:fargs[0]) let path = fern#fri#format( \ expr =~# '^[^:]\+://' @@ -67,16 +64,18 @@ function! fern#internal#command#fern#command(mods, fargs) abort \ 'width': width, \ 'keep': keep, \}) - let fri.fragment = empty(reveal) ? '' : fern#internal#filepath#to_slash(reveal) - - " Normalize fragment if expr does not start from {scheme}:// - if expr !~# '^[^:]\+://' - call s:norm_fragment(fri) - endif - call fern#logger#debug('expr:', expr) call fern#logger#debug('fri:', fri) + " A promise which will be resolved once the viewer become ready + let waiter = fern#hook#promise('viewer:ready') + + " Register callback to reveal node + let reveal = s:normalize_reveal(fri, reveal) + if reveal !=# '' + let waiter = waiter.then({ h -> fern#internal#viewer#reveal(h, reveal) }) + endif + let winid_saved = win_getid() if fri.authority =~# '\' call fern#internal#drawer#open(fri, { @@ -92,18 +91,16 @@ function! fern#internal#command#fern#command(mods, fargs) abort \ 'stay': stay ? win_getid() : 0, \}) endif + if stay call win_gotoid(winid_saved) endif if wait - let [_, err] = s:Promise.wait( - \ fern#hook#promise('viewer:ready'), - \ { - \ 'interval': 100, - \ 'timeout': 5000, - \ }, - \) + let [_, err] = s:Promise.wait(waiter, { + \ 'interval': 100, + \ 'timeout': 5000, + \}) if err isnot# v:null throw printf('[fern] Failed to wait: %s', err) endif @@ -128,15 +125,15 @@ function! fern#internal#command#fern#complete(arglead, cmdline, cursorpos) abort return fern#internal#complete#url(a:arglead, a:cmdline, a:cursorpos) endfunction -function! s:norm_fragment(fri) abort - if empty(a:fri.fragment) - return +function! s:normalize_reveal(fri, reveal) abort + let reveal = expand(a:reveal) + if !fern#internal#filepath#is_absolute(reveal) + return reveal endif - " fragment is one of the following - " 1) An absolute path of fs (/ in Unix, \ in Windows) - " 2) A relative path of fs (/ in Unix, \ in Windows) - " 3) A relative path of URI (/ in all platform) - let root = fern#fri#to#path(fern#fri#parse(a:fri.path)) - let reveal = fern#internal#filepath#to_slash(a:fri.fragment) - let a:fri.fragment = fern#internal#path#relative(reveal, root) + " reveal points a real filesystem + let fri = fern#fri#parse(a:fri.path) + let root = '/' . fri.path + let reveal = fern#internal#filepath#to_slash(reveal) + let reveal = fern#internal#path#relative(reveal, root) + return reveal endfunction diff --git a/autoload/fern/internal/complete.vim b/autoload/fern/internal/complete.vim index a3aa1713..89528081 100644 --- a/autoload/fern/internal/complete.vim +++ b/autoload/fern/internal/complete.vim @@ -57,7 +57,8 @@ endfunction function! fern#internal#complete#reveal(arglead, cmdline, cursorpos) abort let scheme = matchstr(a:cmdline, '\<[^ :]\+\ze://') if empty(scheme) - let rs = getcompletion(matchstr(a:arglead, '^-reveal=\zs.*'), 'dir') + let rs = getcompletion(matchstr(a:arglead, '^-reveal=\zs.*'), 'file') + call map(rs, { _, v -> matchstr(v, '.\{-}\ze[/\\]\?$') }) return map(rs, { _, v -> printf('-reveal=%s', escape(v, ' ')) }) endif let rs = fern#internal#scheme#complete_reveal(scheme, a:arglead, a:cmdline, a:cursorpos) diff --git a/autoload/fern/internal/viewer.vim b/autoload/fern/internal/viewer.vim index 5db36d60..4e44911b 100644 --- a/autoload/fern/internal/viewer.vim +++ b/autoload/fern/internal/viewer.vim @@ -14,6 +14,16 @@ function! fern#internal#viewer#init() abort \.catch({ e -> s:Lambda.pass(e, s:notify(bufnr, e)) }) endfunction +function! fern#internal#viewer#reveal(helper, path) abort + let path = fern#internal#filepath#to_slash(a:path) + let reveal = split(path, '/') + let previous = a:helper.sync.get_cursor_node() + return s:Promise.resolve() + \.then({ -> a:helper.async.reveal_node(reveal) }) + \.then({ -> a:helper.async.redraw() }) + \.then({ -> a:helper.sync.focus_node(reveal) }) +endfunction + function! s:open(bufname, options, resolve, reject) abort if fern#internal#buffer#open(a:bufname, a:options) call a:reject('Cancelled') @@ -26,6 +36,11 @@ function! s:open(bufname, options, resolve, reject) abort endfunction function! s:init() abort + execute printf( + \ 'command! -buffer -bar -nargs=* -complete=customlist,%s FernReveal call s:reveal([])', + \ get(funcref('s:reveal_complete'), 'name'), + \) + setlocal buftype=nofile bufhidden=unload setlocal noswapfile nobuflisted nomodifiable setlocal signcolumn=yes @@ -82,16 +97,12 @@ function! s:init() abort doautocmd User FernSyntax call fern#internal#action#init() - let reveal = split(fri.fragment, '/') let Profile = fern#profile#start('fern#internal#viewer:init') return s:Promise.resolve() \.then({ -> helper.async.expand_node(root.__key) }) \.finally({ -> Profile('expand') }) - \.then({ -> helper.async.reveal_node(reveal) }) - \.finally({ -> Profile('reveal') }) \.then({ -> helper.async.redraw() }) \.finally({ -> Profile('redraw') }) - \.then({ -> helper.sync.focus_node(reveal) }) \.finally({ -> Profile() }) \.then({ -> fern#hook#emit('viewer:ready', helper) }) catch @@ -111,6 +122,58 @@ function! s:notify(bufnr, error) abort endif endfunction +function! s:cache_content(helper) abort + let bufnr = a:helper.bufnr + let content = getbufline(bufnr, 1, '$') + call setbufvar(bufnr, 'fern_viewer_cache_content', content) +endfunction + +function! s:reveal(fargs) abort + try + let wait = fern#internal#args#pop(a:fargs, 'wait', v:false) + if len(a:fargs) isnot# 1 + \ || type(wait) isnot# v:t_bool + throw 'Usage: FernReveal {reveal} [-wait]' + endif + + " Does all options are handled? + call fern#internal#args#throw_if_dirty(a:fargs) + + let expr = expand(a:fargs[0]) + let helper = fern#helper#new() + let promise = fern#internal#viewer#reveal(helper, expr) + + if wait + let [_, err] = s:Promise.wait( + \ promise, + \ { + \ 'interval': 100, + \ 'timeout': 5000, + \ }, + \) + if err isnot# v:null + throw printf('[fern] Failed to wait: %s', err) + endif + endif + catch + echohl ErrorMsg + echomsg v:exception + echohl None + call fern#logger#debug(v:exception) + call fern#logger#debug(v:throwpoint) + endtry +endfunction + +function! s:reveal_complete(arglead, cmdline, cursorpos) abort + let helper = fern#helper#new() + let fri = fern#fri#parse(bufname('%')) + let scheme = helper.fern.scheme + let cmdline = fri.path + let arglead = printf('-reveal=%s', a:arglead) + let rs = fern#internal#complete#reveal(arglead, cmdline, a:cursorpos) + return map(rs, { -> matchstr(v:val, '-reveal=\zs.*') }) +endfunction + function! s:WinEnter() abort if len(win_findbuf(bufnr('%'))) < 2 return @@ -129,11 +192,12 @@ function! s:BufReadCmd() abort call helper.fern.renderer.syntax() call fern#hook#emit('viewer:syntax', helper) doautocmd User FernSyntax + setlocal modifiable + call setline(1, get(b:, 'fern_viewer_cache_content', [])) + setlocal nomodifiable + call helper.sync.set_cursor(get(b:, 'fern_cursor', getcurpos()[1:2])) let root = helper.sync.get_root_node() - let cursor = get(b:, 'fern_cursor', getcurpos()[1:2]) call s:Promise.resolve() - \.then({ -> helper.async.redraw() }) - \.then({ -> helper.sync.set_cursor(cursor) }) \.then({ -> helper.async.reload_node(root.__key) }) \.then({ -> helper.async.redraw() }) \.then({ -> fern#hook#emit('viewer:ready', helper) }) @@ -153,6 +217,9 @@ augroup fern-internal-viewer-internal autocmd User FernHighlight : augroup END +" Cache content to accelerate rendering +call fern#hook#add('viewer:redraw', { h -> s:cache_content(h) }) + " Deprecated: call fern#hook#add('viewer:highlight', { h -> fern#hook#emit('renderer:highlight', h) }) call fern#hook#add('viewer:syntax', { h -> fern#hook#emit('renderer:syntax', h) }) diff --git a/autoload/fern/mapping/node.vim b/autoload/fern/mapping/node.vim index a2332fbd..ffbd01ad 100644 --- a/autoload/fern/mapping/node.vim +++ b/autoload/fern/mapping/node.vim @@ -83,24 +83,15 @@ function! s:map_collapse(helper) abort endfunction function! s:map_reveal(helper) abort - let node = a:helper.sync.get_cursor_node() - let path = node is# v:null - \ ? '' - \ : join(node.__key, '/') . '/' - let path = input('Reveal: ', path) + let path = input( + \ 'Reveal: ', + \ '', + \ printf('customlist,%s', get(funcref('s:reveal_complete'), 'name')), + \) if empty(path) return s:Promise.reject('Cancelled') endif - let key = split(path, '/') - let root = a:helper.sync.get_root_node() - let previous = a:helper.sync.get_cursor_node() - return a:helper.async.reveal_node(key) - \.then({ -> a:helper.async.redraw() }) - \.then({ -> a:helper.sync.focus_node( - \ key, - \ { 'previous': previous }, - \ ) - \}) + return fern#internal#viewer#reveal(a:helper, path) endfunction function! s:map_enter(helper) abort @@ -114,3 +105,13 @@ endfunction function! s:map_leave(helper) abort return a:helper.async.leave_tree() endfunction + +function! s:reveal_complete(arglead, cmdline, cursorpos) abort + let helper = fern#helper#new() + let fri = fern#fri#parse(bufname('%')) + let scheme = helper.fern.scheme + let cmdline = fri.path + let arglead = printf('-reveal=%s', a:arglead) + let rs = fern#internal#complete#reveal(arglead, cmdline, a:cursorpos) + return map(rs, { -> matchstr(v:val, '-reveal=\zs.*') }) +endfunction diff --git a/autoload/fern/scheme/file/complete.vim b/autoload/fern/scheme/file/complete.vim index 03035b5b..9facdf7a 100644 --- a/autoload/fern/scheme/file/complete.vim +++ b/autoload/fern/scheme/file/complete.vim @@ -1,7 +1,8 @@ function! fern#scheme#file#complete#url(arglead, cmdline, cursorpos) abort let path = '/' . fern#fri#parse(a:arglead).path let path = fern#internal#filepath#to_slash(path) - let rs = getcompletion(fern#internal#filepath#from_slash(path), 'dir') + let suffix = a:arglead =~# '/$' ? '/' : '' + let rs = getcompletion(fern#internal#filepath#from_slash(path) . suffix, 'dir') call map(rs, { -> fern#internal#filepath#to_slash(v:val) }) call map(rs, { -> s:to_fri(v:val) }) return rs @@ -10,9 +11,15 @@ endfunction function! fern#scheme#file#complete#reveal(arglead, cmdline, cursorpos) abort let base = '/' . fern#fri#parse(matchstr(a:cmdline, '\ fern#internal#filepath#to_slash(v:val) }) call map(rs, { -> fern#internal#path#relative(v:val, base) }) call map(rs, { -> printf('-reveal=%s', v:val) }) @@ -23,7 +30,7 @@ function! s:to_fri(path) abort return fern#fri#format({ \ 'scheme': 'file', \ 'authority': '', - \ 'path': a:path, + \ 'path': a:path[1:], \ 'query': {}, \ 'fragment': '', \}) diff --git a/doc/fern.txt b/doc/fern.txt index f1647203..9d3d6a7a 100644 --- a/doc/fern.txt +++ b/doc/fern.txt @@ -492,6 +492,14 @@ COMMAND *fern-command* DEPRECATED: Use |:FernDo| with ":" like ":FernDo :" instead. Focus a next fern viewer. If "-drawer" option is specified, it focus only a project drawer style fern. + Note that the command can be followed by a '|' and another command. + + *:FernReveal* +:FernReveal {reveal} [-wait] BUFFER LOCAL + Reveal {reveal} on a current fern viewer. Note that this command + exists only in a fern viewer buffer. + If "-wait" option is specified, the command wait synchronously until + the node specified has revealed. Note that the command can be followed by a '|' and another command. -----------------------------------------------------------------------------