diff --git a/autoload/fern/mapping.vim b/autoload/fern/mapping.vim index 1d236cac..ed26c9dd 100644 --- a/autoload/fern/mapping.vim +++ b/autoload/fern/mapping.vim @@ -34,6 +34,7 @@ endfunction call s:Config.config(expand(':p'), { \ 'mappings': [ + \ 'diff', \ 'drawer', \ 'filter', \ 'mark', diff --git a/autoload/fern/mapping/diff.vim b/autoload/fern/mapping/diff.vim new file mode 100644 index 00000000..811799a6 --- /dev/null +++ b/autoload/fern/mapping/diff.vim @@ -0,0 +1,144 @@ +let s:Promise = vital#fern#import('Async.Promise') +let s:timer_diffupdate = 0 + +function! fern#mapping#diff#init(disable_default_mappings) abort + nnoremap (fern-action-diff:select) :call call('diff', 'select', v:false) + nnoremap (fern-action-diff:split) :call call('diff', 'split', v:false) + nnoremap (fern-action-diff:vsplit) :call call('diff', 'vsplit', v:false) + nnoremap (fern-action-diff:tabedit) :call call('diff', 'tabedit', v:false) + nnoremap (fern-action-diff:above) :call call('diff', 'leftabove split', v:false) + nnoremap (fern-action-diff:left) :call call('diff', 'leftabove vsplit', v:false) + nnoremap (fern-action-diff:below) :call call('diff', 'rightbelow split', v:false) + nnoremap (fern-action-diff:right) :call call('diff', 'rightbelow vsplit', v:false) + nnoremap (fern-action-diff:top) :call call('diff', 'topleft split', v:false) + nnoremap (fern-action-diff:leftest) :call call('diff', 'topleft vsplit', v:false) + nnoremap (fern-action-diff:bottom) :call call('diff', 'botright split', v:false) + nnoremap (fern-action-diff:rightest) :call call('diff', 'botright vsplit', v:false) + nnoremap (fern-action-diff:edit-or-error) :call call('diff', 'edit', v:false) + nnoremap (fern-action-diff:edit-or-split) :call call('diff', 'edit/split', v:false) + nnoremap (fern-action-diff:edit-or-vsplit) :call call('diff', 'edit/vsplit', v:false) + nnoremap (fern-action-diff:edit-or-tabedit) :call call('diff', 'edit/tabedit', v:false) + + nnoremap (fern-action-diff:select:vert) :call call('diff', 'select', v:true) + nnoremap (fern-action-diff:split:vert) :call call('diff', 'split', v:true) + nnoremap (fern-action-diff:vsplit:vert) :call call('diff', 'vsplit', v:true) + nnoremap (fern-action-diff:tabedit:vert) :call call('diff', 'tabedit', v:true) + nnoremap (fern-action-diff:above:vert) :call call('diff', 'leftabove split', v:true) + nnoremap (fern-action-diff:left:vert) :call call('diff', 'leftabove vsplit', v:true) + nnoremap (fern-action-diff:below:vert) :call call('diff', 'rightbelow split', v:true) + nnoremap (fern-action-diff:right:vert) :call call('diff', 'rightbelow vsplit', v:true) + nnoremap (fern-action-diff:top:vert) :call call('diff', 'topleft split', v:true) + nnoremap (fern-action-diff:leftest:vert) :call call('diff', 'topleft vsplit', v:true) + nnoremap (fern-action-diff:bottom:vert) :call call('diff', 'botright split', v:true) + nnoremap (fern-action-diff:rightest:vert) :call call('diff', 'botright vsplit', v:true) + nnoremap (fern-action-diff:edit-or-error:vert) :call call('diff', 'edit', v:true) + nnoremap (fern-action-diff:edit-or-split:vert) :call call('diff', 'edit/split', v:true) + nnoremap (fern-action-diff:edit-or-vsplit:vert) :call call('diff', 'edit/vsplit', v:true) + nnoremap (fern-action-diff:edit-or-tabedit:vert) :call call('diff', 'edit/tabedit', v:true) + + " Smart map + nmap + \ (fern-action-diff:side) + \ fern#smart#drawer( + \ "\(fern-action-diff:left)", + \ "\(fern-action-diff:right)", + \ ) + nmap + \ (fern-action-diff:side:vert) + \ fern#smart#drawer( + \ "\(fern-action-diff:left:vert)", + \ "\(fern-action-diff:right:vert)", + \ ) + + " Alias map + nmap (fern-action-diff:edit) (fern-action-diff:edit-or-error) + nmap (fern-action-diff:edit:vert) (fern-action-diff:edit-or-error:vert) + nmap (fern-action-diff) (fern-action-diff:edit) + nmap (fern-action-diff:vert) (fern-action-diff:edit:vert) +endfunction + +function! s:call(name, ...) abort + return call( + \ 'fern#mapping#call', + \ [funcref(printf('s:map_%s', a:name))] + a:000, + \) +endfunction + +function! s:map_diff(helper, opener, vert) abort + let nodes = a:helper.sync.get_selected_nodes() + let nodes = filter(copy(nodes), { -> v:val.bufname isnot# v:null }) + if empty(nodes) + return s:Promise.reject('no node found which has bufname') + elseif len(nodes) < 2 + return s:Promise.reject('at least two nodes are required to perform diff') + endif + try + let is_drawer = a:helper.sync.is_drawer() + let first = nodes[0] + let nodes = nodes[1:] + call fern#internal#buffer#open(first.bufname, { + \ 'opener': a:opener, + \ 'locator': is_drawer, + \ 'keepalt': !is_drawer && g:fern#keepalt_on_edit, + \ 'keepjumps': !is_drawer && g:fern#keepjumps_on_edit, + \}) + call s:diffthis() + let winid = win_getid() + for node in nodes + noautocmd call win_gotoid(winid) + call fern#internal#buffer#open(node.bufname, { + \ 'opener': a:vert ? 'vsplit' : 'split', + \ 'locator': is_drawer, + \ 'keepalt': !is_drawer && g:fern#keepalt_on_edit, + \ 'keepjumps': !is_drawer && g:fern#keepjumps_on_edit, + \}) + call s:diffthis() + endfor + call s:diffupdate() + normal! zm + " Fix (#47) + let winid_fern = win_getid() + noautocmd call win_gotoid(winid) + noautocmd call win_gotoid(winid_fern) + return a:helper.async.update_marks([]) + \.then({ -> a:helper.async.remark() }) + catch + return s:Promise.reject(v:exception) + endtry +endfunction + +function! s:diffthis() abort + diffthis + augroup fern_mapping_diff_internal + autocmd! * + autocmd BufReadPost + \ if &diff && &foldmethod !=# 'diff' | + \ setlocal foldmethod=diff | + \ endif + augroup END +endfunction + +function! s:diffupdate() abort + " NOTE: + " 'diffupdate' does not work just after a buffer has opened + " so use timer to delay the command. + silent! call timer_stop(s:timer_diffupdate) + let s:timer_diffupdate = timer_start(100, function('s:diffupdate_internal', [bufnr('%')])) +endfunction + +function! s:diffupdate_internal(bufnr, ...) abort + let winid = bufwinid(a:bufnr) + if winid == -1 + return + endif + let winid_saved = win_getid() + try + if winid != winid_saved + call win_gotoid(winid) + endif + diffupdate + syncbind + finally + call win_gotoid(winid_saved) + endtry +endfunction diff --git a/autoload/fern/scheme/file/mapping.vim b/autoload/fern/scheme/file/mapping.vim index b517ad4e..d06d3e8e 100644 --- a/autoload/fern/scheme/file/mapping.vim +++ b/autoload/fern/scheme/file/mapping.vim @@ -241,6 +241,7 @@ endfunction let g:fern#scheme#file#mapping#mappings = get(g:, 'fern#scheme#file#mapping#mappings', [ \ 'cd', \ 'clipboard', + \ 'ex', \ 'grep', \ 'rename', \ 'system', diff --git a/autoload/fern/scheme/file/mapping/ex.vim b/autoload/fern/scheme/file/mapping/ex.vim new file mode 100644 index 00000000..77580b76 --- /dev/null +++ b/autoload/fern/scheme/file/mapping/ex.vim @@ -0,0 +1,30 @@ +let s:Promise = vital#fern#import('Async.Promise') + +function! fern#scheme#file#mapping#ex#init(disable_default_mappings) abort + nnoremap (fern-action-ex) :call call('ex') +endfunction + +function! s:call(name, ...) abort + return call( + \ 'fern#mapping#call', + \ [funcref(printf('s:map_%s', a:name))] + a:000, + \) +endfunction + +function! s:map_ex(helper) abort + let nodes = a:helper.sync.get_selected_nodes() + let nodes = filter(copy(nodes), { -> v:val._path isnot# v:null }) + if empty(nodes) + return + endif + call feedkeys("\", 'in') + let expr = join(map(copy(nodes), { _, v -> fnamemodify(v._path, ':~:.') }), ' ') + let expr = input(':', ' ' . expr, 'command') + if empty(expr) + return + endif + if a:helper.sync.is_drawer() + call fern#internal#locator#focus(winnr('#')) + endif + execute expr +endfunction diff --git a/doc/fern.txt b/doc/fern.txt index 004a1633..4cad9a04 100644 --- a/doc/fern.txt +++ b/doc/fern.txt @@ -919,7 +919,7 @@ GLOBAL *fern-mapping-global* The command will be applied on an "anchor" window when invoked from a drawer style fern (|fern-glossary-anchor|.) -*(fern-action-edit-or-error)* +*(fern-action-open:edit-or-error)* Open a cursor node or marked nodes with |edit| command or fallback to print an error. Note that when 'hidden' has set or 'bufhidden' is "hide", the |edit| @@ -980,6 +980,95 @@ GLOBAL *fern-mapping-global* \ (fern-action-open) \ (fern-action-open:select) < +*(fern-action-diff:select)* +*(fern-action-diff:select:vert)* + Open a first marked node through "window selector" + (|fern-glossary-window-selector|.) then open remains with |split| or + |vsplit| (:vert) to compare content through |diff| feature. + +*(fern-action-diff:split)* +*(fern-action-diff:split:vert)* +*(fern-action-diff:vsplit)* +*(fern-action-diff:vsplit:vert)* +*(fern-action-diff:tabedit)* +*(fern-action-diff:tabedit:vert)* + Open a first marked node with a corresponding command then open remains + with |split| or |vsplit| (:vert) to compare contents through |diff| + feature. + The command will be applied on an "anchor" window when invoked from a + drawer style fern (|fern-glossary-anchor|.) + +*(fern-action-diff:edit-or-error)* +*(fern-action-diff:edit-or-error:vert)* + Open a first marked node with |edit| command or fallback to print an + error then open remains with |split| or |vsplit| (:vert) to compare + contents through |diff| feature. + Note that when 'hidden' has set or 'bufhidden' is "hide", the |edit| + command will never fails. + +*(fern-action-diff:edit-or-split)* +*(fern-action-diff:edit-or-split:vert)* +*(fern-action-diff:edit-or-vsplit)* +*(fern-action-diff:edit-or-vsplit:vert)* +*(fern-action-diff:edit-or-tabedit)* +*(fern-action-diff:edit-or-tabedit:vert)* + Open a first marked node with |edit| command or fallback to a + corresponding command then . + Note that when 'hidden' has set or 'bufhidden' is "hide", the |edit| + command will never fails. + +*(fern-action-diff:above)* +*(fern-action-diff:above:vert)* +*(fern-action-diff:left)* +*(fern-action-diff:left:vert)* +*(fern-action-diff:below)* +*(fern-action-diff:below:vert)* +*(fern-action-diff:right)* +*(fern-action-diff:right:vert)* + Open a first marked node on a corresponding direction from an "anchor" + window then open remains with |split| or |vsplit| (:vert) to compare + contents through |diff| feature. + The command will be applied on the anchor window when invoked from a + drawer style fern (|fern-glossary-anchor|.) + +*(fern-action-diff:top)* +*(fern-action-diff:top:vert)* +*(fern-action-diff:leftest)* +*(fern-action-diff:leftest:vert)* +*(fern-action-diff:bottom)* +*(fern-action-diff:bottom:vert)* +*(fern-action-diff:rightest)* +*(fern-action-diff:rightest:vert)* + Open a first marked node on a edge of a corresponding direction from + an "anchor" window then open remains with |split| or |vsplit| (:vert) + to compare contents through |diff| feature. + The command will be applied on the anchor window when invoked from a + drawer style fern (|fern-glossary-anchor|.) + +*(fern-action-diff:side)* +*(fern-action-diff:side:vert)* + Open a first marked node on the right side of the current window then + open remains with |split| or |vsplit| (:vert) to compare contents + through |diff| feature. + The behavior is slightly different between a drawer style fern + window and a split style fern window due to the presence of "anchor". + +*(fern-action-diff:edit)* + An alias to "diff:edit-or-error" action. Users can overwrite this + mapping to change the default behavior of "diff:edit" action like: +> + nmap + \ (fern-action-diff:edit) + \ (fern-action-diff:edit-or-tabedit) +< +*(fern-action-diff)* + An alias to "diff:edit" action. Users can overwrite this mapping to + change the default behavior of "diff" action like: +> + nmap + \ (fern-action-diff) + \ (fern-action-diff:select) +< *(fern-action-cancel)* Cancel tree rendering. @@ -1013,6 +1102,10 @@ FILE *fern-mapping-file* The following mappings/actions are only available on file:// scheme. +*(fern-action-ex)* + Open a prompt to execute an Ex command with a path of cursor node or + paths of marked nodes. + *(fern-action-new-path)* Open a prompt to ask a path and create a file/directory of the input path from the path of a cursor node.