Skip to content
51 changes: 24 additions & 27 deletions autoload/fern/internal/command/fern.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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 =~# '^[^:]\+://'
Expand All @@ -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 =~# '\<drawer\>'
call fern#internal#drawer#open(fri, {
Expand All @@ -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
Expand All @@ -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
3 changes: 2 additions & 1 deletion autoload/fern/internal/complete.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
81 changes: 74 additions & 7 deletions autoload/fern/internal/viewer.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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([<f-args>])',
\ get(funcref('s:reveal_complete'), 'name'),
\)

setlocal buftype=nofile bufhidden=unload
setlocal noswapfile nobuflisted nomodifiable
setlocal signcolumn=yes
Expand Down Expand Up @@ -82,16 +97,12 @@ function! s:init() abort
doautocmd <nomodeline> 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
Expand All @@ -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
Expand All @@ -129,11 +192,12 @@ function! s:BufReadCmd() abort
call helper.fern.renderer.syntax()
call fern#hook#emit('viewer:syntax', helper)
doautocmd <nomodeline> 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) })
Expand All @@ -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) })
31 changes: 16 additions & 15 deletions autoload/fern/mapping/node.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
17 changes: 12 additions & 5 deletions autoload/fern/scheme/file/complete.vim
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -10,9 +11,15 @@ endfunction
function! fern#scheme#file#complete#reveal(arglead, cmdline, cursorpos) abort
let base = '/' . fern#fri#parse(matchstr(a:cmdline, '\<file:///\S*')).path
let path = matchstr(a:arglead, '^-reveal=\zs.*')
let path = fern#internal#filepath#to_slash(path)
let path = fern#internal#path#absolute(path, base)
let rs = getcompletion(fern#internal#filepath#from_slash(path), 'dir')
if path ==# ''
let path = base
let suffix = '/'
else
let path = fern#internal#filepath#to_slash(path)
let path = fern#internal#path#absolute(path, base)
let suffix = a:arglead =~# '/$' ? '/' : ''
endif
let rs = getcompletion(fern#internal#filepath#from_slash(path) . suffix, 'file')
call map(rs, { -> 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) })
Expand All @@ -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': '',
\})
Expand Down
8 changes: 8 additions & 0 deletions doc/fern.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.

-----------------------------------------------------------------------------
Expand Down