From af773b6bffd5849d0a8911e194d652d1a6ab55e5 Mon Sep 17 00:00:00 2001 From: Wang Shidong Date: Sat, 31 Oct 2020 15:58:52 +0800 Subject: [PATCH] Update defx.nvim (#3803) --- bundle/README.md | 3 + bundle/defx.nvim/.github/ISSUE_TEMPLATE.md | 4 +- bundle/defx.nvim/.github/workflows/lint.yml | 17 ++ bundle/defx.nvim/.travis.yml | 14 - bundle/defx.nvim/Makefile | 19 +- bundle/defx.nvim/README.md | 25 +- bundle/defx.nvim/autoload/defx.vim | 27 +- bundle/defx.nvim/autoload/defx/exrename.vim | 71 +++-- bundle/defx.nvim/autoload/defx/init.vim | 20 +- bundle/defx.nvim/autoload/defx/util.vim | 150 ++++++++- bundle/defx.nvim/doc/defx.txt | 87 +++++- .../defx.nvim/rplugin/python3/defx/action.py | 3 + .../rplugin/python3/defx/base/column.py | 20 +- .../rplugin/python3/defx/base/kind.py | 40 ++- .../rplugin/python3/defx/column/filename.py | 85 ++---- .../rplugin/python3/defx/column/icon.py | 45 +-- .../rplugin/python3/defx/column/mark.py | 37 +-- .../rplugin/python3/defx/column/size.py | 20 +- .../rplugin/python3/defx/column/time.py | 20 +- .../rplugin/python3/defx/column/type.py | 31 +- .../defx.nvim/rplugin/python3/defx/context.py | 14 +- bundle/defx.nvim/rplugin/python3/defx/defx.py | 68 +++-- .../rplugin/python3/defx/kind/file.py | 176 +++++++++-- .../rplugin/python3/defx/preview_image.py | 17 ++ .../rplugin/python3/defx/preview_image.sh | 4 + .../defx.nvim/rplugin/python3/defx/rplugin.py | 18 +- .../defx/source/{file.py => file/__init__.py} | 17 +- .../rplugin/python3/defx/source/file/list.py | 71 +++++ bundle/defx.nvim/rplugin/python3/defx/util.py | 43 ++- bundle/defx.nvim/rplugin/python3/defx/view.py | 288 ++++++++++++++---- .../python3/denite/source/defx/history.py | 7 +- config/plugins/defx.vim | 2 + 32 files changed, 1099 insertions(+), 364 deletions(-) create mode 100644 bundle/defx.nvim/.github/workflows/lint.yml delete mode 100644 bundle/defx.nvim/.travis.yml create mode 100644 bundle/defx.nvim/rplugin/python3/defx/preview_image.py create mode 100644 bundle/defx.nvim/rplugin/python3/defx/preview_image.sh rename bundle/defx.nvim/rplugin/python3/defx/source/{file.py => file/__init__.py} (79%) create mode 100644 bundle/defx.nvim/rplugin/python3/defx/source/file/list.py diff --git a/bundle/README.md b/bundle/README.md index 0ee7c5ea8..1551bde7d 100644 --- a/bundle/README.md +++ b/bundle/README.md @@ -1,5 +1,8 @@ ## Forked repos +- [defx.nvim](https://github.com/Shougo/defx.nvim/commit/df5e6ea6734dc002919ea41786668069fa0b497d) + + ### checkers layer - neomake diff --git a/bundle/defx.nvim/.github/ISSUE_TEMPLATE.md b/bundle/defx.nvim/.github/ISSUE_TEMPLATE.md index f75aff7be..86a13ce8f 100644 --- a/bundle/defx.nvim/.github/ISSUE_TEMPLATE.md +++ b/bundle/defx.nvim/.github/ISSUE_TEMPLATE.md @@ -8,7 +8,7 @@ ## Environment Information - * defx version(SHA1): + * plugin version(SHA1): * OS: @@ -20,7 +20,7 @@ ```vim " Your minimal init.vim/vimrc -set runtimepath+=~/path/to/defx.nvim/ +set runtimepath+=~/path/to/plugin/ ``` diff --git a/bundle/defx.nvim/.github/workflows/lint.yml b/bundle/defx.nvim/.github/workflows/lint.yml new file mode 100644 index 000000000..99b553e92 --- /dev/null +++ b/bundle/defx.nvim/.github/workflows/lint.yml @@ -0,0 +1,17 @@ +name: Lint and test + +on: [push, pull_request] + +jobs: + lint: + name: Lint and test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install + run: | + sudo apt install python3-setuptools python3-wheel + + - name: Lint + run: make --keep-going install-user test lint diff --git a/bundle/defx.nvim/.travis.yml b/bundle/defx.nvim/.travis.yml deleted file mode 100644 index eac636a70..000000000 --- a/bundle/defx.nvim/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -dist: xenial - -language: python - -python: - - 3.6 - - 3.7 - -install: - - eval "$(curl -Ss https://raw.githubusercontent.com/neovim/bot-ci/master/scripts/travis-setup.sh) nightly-x64" - - make install - -script: - - make --keep-going test lint diff --git a/bundle/defx.nvim/Makefile b/bundle/defx.nvim/Makefile index 5773e96a1..7161dffeb 100644 --- a/bundle/defx.nvim/Makefile +++ b/bundle/defx.nvim/Makefile @@ -1,14 +1,10 @@ -PATH := ./vim-themis/bin:$(PATH) -export THEMIS_VIM := nvim -export THEMIS_ARGS := -e -s --headless -export THEMIS_HOME := ./vim-themis +PATH := $(HOME)/.local/bin:$(PATH) +install: + pip3 install --upgrade -r test/requirements.txt -install: vim-themis - pip install --upgrade -r test/requirements.txt - -install-user: vim-themis - pip install --user --upgrade -r test/requirements.txt +install-user: + pip3 install --user --upgrade -r test/requirements.txt lint: vint --version @@ -20,12 +16,7 @@ lint: mypy --ignore-missing-imports --follow-imports=skip --strict rplugin/python3/defx test: - # themis --version - # themis test/autoload/* pytest --version pytest -vim-themis: - git clone https://github.com/thinca/vim-themis vim-themis - .PHONY: install lint test diff --git a/bundle/defx.nvim/README.md b/bundle/defx.nvim/README.md index 147666a5c..970538982 100644 --- a/bundle/defx.nvim/README.md +++ b/bundle/defx.nvim/README.md @@ -37,7 +37,7 @@ It replaces the deprecated vimfiler plugin. ## Installation -**Note:** defx requires Neovim 0.3.0+ or Vim8.1+ with Python3.6.1+. See +**Note:** defx requires Neovim 0.4.0+ or Vim8.2+ with Python3.6.1+. See [requirements](#requirements) if you aren't sure whether you have this. For vim-plug @@ -70,10 +70,12 @@ For manual installation(not recommended) ## Requirements -defx requires Python3.6.1+ and Neovim(0.3.0+) or Vim8.1+ with if\_python3. If +defx requires Python3.6.1+ and Neovim(0.4.0+) or Vim8.2+ with if\_python3. If `:echo has("python3")` returns `1`, then you have python 3 support; otherwise, see below. +Note: The latest Neovim is recommended, because it is faster. + You can enable Python3 interface with pip: pip3 install --user pynvim @@ -92,26 +94,25 @@ If Defx was installed prior to Python support being added to Neovim, ## Configuration Examples -```vim -" Todo -``` - +Please see `:help defx-examples`. ## Screenshots -![multi root feature](https://user-images.githubusercontent.com/41495/45696476-ac9d0a80-bb9e-11e8-9ee2-120ac7d0f045.png) +![Multi root feature](https://user-images.githubusercontent.com/41495/45696476-ac9d0a80-bb9e-11e8-9ee2-120ac7d0f045.png) ![Defx -split=vertical](https://user-images.githubusercontent.com/2835826/45823772-7190f900-bcbc-11e8-9727-3dda3ce4c07c.png) ![Defx -new](https://user-images.githubusercontent.com/3047695/45927914-7f07e680-bf3b-11e8-9b36-755e1eec2a8f.png) ![Defx + neovim-qt](https://user-images.githubusercontent.com/1314340/48659914-0b4a0c00-ea9c-11e8-9953-2f2d5ca7f24a.png) -![custom icon](https://user-images.githubusercontent.com/10108377/59982828-ac93d480-9620-11e9-8c10-51909cfeaf94.png) -![custom icon2](https://user-images.githubusercontent.com/3021667/55260000-95ba2d80-523d-11e9-877c-756a080a9a28.png) -![custom icon3](https://user-images.githubusercontent.com/10397021/57774111-3f04a680-774c-11e9-852a-53c394f672ef.png) -![custom icon4](https://user-images.githubusercontent.com/12205650/58801907-d9346d80-85d9-11e9-8a2d-de4635aa1eba.png) -![custom icon5](https://user-images.githubusercontent.com/11615211/82411894-381e1b80-9aa5-11ea-9552-fd9847fe25e3.png) +![Custom icon](https://user-images.githubusercontent.com/10108377/59982828-ac93d480-9620-11e9-8c10-51909cfeaf94.png) +![Custom icon2](https://user-images.githubusercontent.com/3021667/55260000-95ba2d80-523d-11e9-877c-756a080a9a28.png) +![Custom icon3](https://user-images.githubusercontent.com/10397021/57774111-3f04a680-774c-11e9-852a-53c394f672ef.png) +![Custom icon4](https://user-images.githubusercontent.com/12205650/58801907-d9346d80-85d9-11e9-8a2d-de4635aa1eba.png) +![Custom icon5](https://user-images.githubusercontent.com/11615211/82411894-381e1b80-9aa5-11ea-9552-fd9847fe25e3.png) ![Defx on kitty](https://user-images.githubusercontent.com/8403993/51080184-d29e6b80-16b5-11e9-802b-7c2f56705e2e.png) ![Defx in SpaceVim](https://user-images.githubusercontent.com/13142418/54086225-85233f80-4382-11e9-8091-7f387319b90a.png) ![Variable column](https://user-images.githubusercontent.com/19503791/56090130-58f26580-5ed0-11e9-8b66-e684cb11b0d1.png) ![Denite action call](https://user-images.githubusercontent.com/41671631/56280845-a6bfd580-613d-11e9-857a-d81f2633eeab.png) ![Defx floating window](https://user-images.githubusercontent.com/24732170/59892964-1c823f00-9416-11e9-8369-2e21910e168c.png) ![Horizon colorscheme](https://user-images.githubusercontent.com/324519/63241202-a4fb4100-c207-11e9-9060-c3c04608ea7b.png) +![Image preview](https://user-images.githubusercontent.com/41671631/85951370-5d9c2000-b995-11ea-8a3d-2c304d21cc4c.gif) +![Defx + vim-quickui](https://user-images.githubusercontent.com/32936898/92196371-bd390f00-eea1-11ea-957e-5dcde77afd3e.png) diff --git a/bundle/defx.nvim/autoload/defx.vim b/bundle/defx.nvim/autoload/defx.vim index 4f121ecb6..0992d448b 100644 --- a/bundle/defx.nvim/autoload/defx.vim +++ b/bundle/defx.nvim/autoload/defx.vim @@ -9,11 +9,32 @@ function! defx#initialize() abort endfunction function! defx#start(paths, user_context) abort + let prev_winid = win_getid() + call defx#initialize() let context = defx#init#_context(a:user_context) - let paths = a:paths - let paths = map(paths, "fnamemodify(v:val, ':p')") - call defx#util#rpcrequest('_defx_start', [paths, context], v:false) + let paths = map(a:paths, "[v:val[0], fnamemodify(v:val[1], ':p')]") + + call defx#util#rpcrequest('_defx_start', + \ [paths, context], v:false) + + if context['search'] !=# '' + call defx#call_action('search', [context['search']]) + endif + + if !context['focus'] + " Restore the window + call win_gotoid(prev_winid) + endif +endfunction +function! defx#start_candidates(candidates, user_context) abort + call defx#initialize() + let context = defx#init#_context(a:user_context) + let listfile = tempname() + call writefile(a:candidates, listfile) + let paths = [['file/list', listfile]] + call defx#util#rpcrequest('_defx_start', + \ [paths, context], v:false) if context['search'] !=# '' call defx#call_action('search', [context['search']]) endif diff --git a/bundle/defx.nvim/autoload/defx/exrename.vim b/bundle/defx.nvim/autoload/defx/exrename.vim index 1ab73c8a5..9f283255c 100644 --- a/bundle/defx.nvim/autoload/defx/exrename.vim +++ b/bundle/defx.nvim/autoload/defx/exrename.vim @@ -54,7 +54,7 @@ function! defx#exrename#create_buffer(candidates, ...) abort silent! syntax clear defxExrenameOriginal " validate candidates and register - let unique_filenames = [] + let unique_filenames = {} let b:exrename.candidates = [] let b:exrename.filenames = [] let cnt = 1 @@ -67,13 +67,15 @@ function! defx#exrename#create_buffer(candidates, ...) abort if !filewritable(candidate.action__path) \ && !isdirectory(candidate.action__path) redraw - echo candidate.action__path 'does not exist. Skip.' + call defx#util#print_error( + \ candidate.action__path . ' does not exist. Skip.') continue endif " make sure that the 'action__path' is unique - if index(unique_filenames, candidate.action__path) != -1 + if has_key(unique_filenames, candidate.action__path) redraw - echo candidate.action__path 'is duplicated. Skip.' + call defx#util#print_error( + \ candidate.action__path . ' is duplicated. Skip.') continue endif " create filename @@ -90,11 +92,14 @@ function! defx#exrename#create_buffer(candidates, ...) abort \ '/'.printf('^\%%%dl%s$', cnt, \ escape(s:escape_pattern(filename), '/')).'/' " register - call add(unique_filenames, candidate.action__path) + let unique_filenames[candidate.action__path] = 1 call add(b:exrename.candidates, candidate) call add(b:exrename.filenames, filename) let cnt += 1 endfor + + let b:exrename.unique_filenames = unique_filenames + " write filenames let [undolevels, &undolevels] = [&undolevels, -1] try @@ -115,7 +120,7 @@ endfunction function! s:do_rename() abort if line('$') != len(b:exrename.filenames) - echohl Error | echo 'Invalid rename buffer!' | echohl None + call defx#util#print_error('Invalid rename buffer!') return endif @@ -129,22 +134,44 @@ function! s:do_rename() abort echo printf('(%'.len(max).'d/%d): %s -> %s', \ linenr, max, filename, getline(linenr)) - if filename !=# getline(linenr) - let old_file = b:exrename.candidates[linenr - 1].action__path - let new_file = expand(getline(linenr)) - if new_file !~# '^\%(\a\a\+:\)\|^\%(\a:\|/\)' - let new_file = b:exrename.cwd . new_file - endif - - if rename(old_file, new_file) - " Rename error - continue - endif - - " update b:exrename - let b:exrename.filenames[linenr - 1] = getline(linenr) - let b:exrename.candidates[linenr - 1].action__path = new_file + if filename ==# getline(linenr) + let linenr += 1 + continue endif + + let old_file = b:exrename.candidates[linenr - 1].action__path + let new_file = expand(getline(linenr)) + if !s:is_absolute(new_file) + " Convert to absolute path + let new_file = b:exrename.cwd . new_file + endif + + if filereadable(new_file) || isdirectory(new_file) + " new_file is already exists. + redraw + call defx#util#print_error( + \ new_file . ' is already exists. Skip.') + + let linenr += 1 + continue + endif + + if rename(old_file, new_file) + " Rename error + redraw + call defx#util#print_error( + \ new_file . ' is rename error. Skip.') + + let linenr += 1 + continue + endif + + call defx#util#buffer_rename(bufnr(old_file), new_file) + + " update b:exrename + let b:exrename.filenames[linenr - 1] = getline(linenr) + let b:exrename.candidates[linenr - 1].action__path = new_file + let linenr += 1 endwhile @@ -178,7 +205,7 @@ function! s:check_lines() abort endif if line('$') != len(b:exrename.filenames) - echohl Error | echo 'Invalid rename buffer!' | echohl None + call defx#util#print_error('Invalid rename buffer!') return endif endfunction diff --git a/bundle/defx.nvim/autoload/defx/init.vim b/bundle/defx.nvim/autoload/defx/init.vim index 1696fb48e..05e823a21 100644 --- a/bundle/defx.nvim/autoload/defx/init.vim +++ b/bundle/defx.nvim/autoload/defx/init.vim @@ -16,6 +16,7 @@ function! defx#init#_initialize() abort augroup END let g:defx#_histories = [] + let g:defx#_previewed_buffers = {} endfunction function! defx#init#_channel() abort if !has('python3') @@ -23,12 +24,12 @@ function! defx#init#_channel() abort \ 'defx requires Python3 support("+python3").') return v:true endif - if has('nvim') && !has('nvim-0.3.0') - call defx#util#print_error('defx requires nvim 0.3.0+.') + if has('nvim') && !has('nvim-0.4.0') + call defx#util#print_error('defx requires nvim 0.4.0+.') return v:true endif - if !has('nvim') && v:version < 801 - call defx#util#print_error('defx requires Vim 8.1+.') + if !has('nvim') && !defx#util#has_textprop() + call defx#util#print_error('defx requires Vim 8.2+ with textprop.') return v:true endif @@ -95,25 +96,32 @@ function! defx#init#_user_options() abort \ 'auto_cd': v:false, \ 'auto_recursive_level': 0, \ 'buffer_name': 'default', + \ 'close': v:false, \ 'columns': 'mark:indent:icon:filename:type', \ 'direction': '', + \ 'filtered_files': '', + \ 'floating_preview': v:false, + \ 'focus': v:true, \ 'ignored_files': '.*', \ 'listed': v:false, \ 'new': v:false, + \ 'preview_height': &previewheight, + \ 'preview_width': 40, \ 'profile': v:false, \ 'resume': v:false, - \ 'root_marker': '[in]: ', + \ 'root_marker': '[in] ', \ 'search': '', \ 'session_file': '', \ 'show_ignored_files': v:false, - \ 'split': 'no', \ 'sort': 'filename', + \ 'split': 'no', \ 'toggle': v:false, \ 'wincol': &columns / 4, \ 'winheight': 30, \ 'winrelative': 'editor', \ 'winrow': &lines / 3, \ 'winwidth': 90, + \ 'vertical_preview': v:false, \ } endfunction function! s:internal_options() abort diff --git a/bundle/defx.nvim/autoload/defx/util.vim b/bundle/defx.nvim/autoload/defx/util.vim index d2735b839..aefb74cd4 100644 --- a/bundle/defx.nvim/autoload/defx/util.vim +++ b/bundle/defx.nvim/autoload/defx/util.vim @@ -40,6 +40,9 @@ endfunction function! defx#util#has_yarp() abort return !has('nvim') || get(g:, 'defx#enable_yarp', 0) endfunction +function! defx#util#has_textprop() abort + return v:version >= 802 && exists('*prop_add') +endfunction function! defx#util#execute_path(command, path) abort try @@ -127,8 +130,16 @@ function! s:parse_options(cmdline) abort \ s:eval_cmdline(a:cmdline) : a:cmdline for s in split(cmdline, s:re_unquoted_match('\%(\\\@= 0 let options[name] = value else - call add(args, arg) + call add(args, [source_name, source_arg]) endif endfor @@ -323,3 +334,134 @@ function! s:strwidthpart_reverse(str, width) abort let vcol = strwidth(str) - a:width return matchstr(str, '\%>' . (vcol < 0 ? 0 : vcol) . 'v.*') endfunction + +function! defx#util#buffer_rename(bufnr, new_filename) abort + if a:bufnr < 0 || !bufloaded(a:bufnr) + return + endif + + let hidden = &hidden + + set hidden + let bufnr_save = bufnr('%') + noautocmd silent! execute 'buffer' a:bufnr + silent execute (&l:buftype ==# '' ? 'saveas!' : 'file') + \ fnameescape(a:new_filename) + if &l:buftype ==# '' + " Remove old buffer. + silent! bdelete! # + endif + + noautocmd silent execute 'buffer' bufnr_save + let &hidden = hidden +endfunction + +function! defx#util#buffer_delete(bufnr) abort + if a:bufnr < 0 + return + endif + + let winid = get(win_findbuf(a:bufnr), 0, -1) + if winid > 0 + let winid_save = win_getid() + call win_gotoid(winid) + + noautocmd silent enew + execute 'silent! bdelete!' a:bufnr + + call win_gotoid(winid_save) + else + execute 'silent! bdelete!' a:bufnr + endif +endfunction + +function! defx#util#_get_preview_window() abort + " Note: For popup preview feature + if exists('*popup_findpreview') && popup_findpreview() > 0 + return 1 + endif + + return len(filter(range(1, winnr('$')), + \ "getwinvar(v:val, '&previewwindow') ==# 1")) +endfunction + +function! defx#util#preview_file(context, filename) abort + let preview_width = str2nr(a:context.preview_width) + let preview_height = str2nr(a:context.preview_height) + let pos = win_screenpos(win_getid()) + let win_width = winwidth(0) + let win_height = winheight(0) + + if a:context.vertical_preview + call defx#util#execute_path( + \ 'silent rightbelow vertical pedit!', a:filename) + wincmd P + + if a:context.floating_preview && exists('*nvim_win_set_config') + if a:context['split'] ==# 'floating' + let win_row = str2nr(a:context['winrow']) + let win_col = str2nr(a:context['wincol']) + else + let win_row = pos[0] - 1 + let win_col = pos[1] - 1 + endif + let win_col += win_width + if (win_col + preview_width) > &columns + let win_col -= preview_width + endif + + call nvim_win_set_config(win_getid(), { + \ 'relative': 'editor', + \ 'row': win_row, + \ 'col': win_col, + \ 'width': preview_width, + \ 'height': preview_height, + \ }) + else + execute 'vert resize ' . preview_width + endif + else + call defx#util#execute_path('silent aboveleft pedit!', a:filename) + + wincmd P + + if a:context.floating_preview && exists('*nvim_win_set_config') + let win_row = pos[0] - 1 + let win_col = pos[1] + 1 + if win_row <= preview_height + let win_row += win_height + 1 + let anchor = 'NW' + else + let anchor = 'SW' + endif + + call nvim_win_set_config(0, { + \ 'relative': 'editor', + \ 'anchor': anchor, + \ 'row': win_row, + \ 'col': win_col, + \ 'width': preview_width, + \ 'height': preview_height, + \ }) + else + execute 'resize ' . preview_height + endif + endif + + if exists('#User#defx-preview') + doautocmd User defx-preview + endif +endfunction + +function! defx#util#call_atomic(calls) abort + let results = [] + for [name, args] in a:calls + try + call add(results, call(name, args)) + catch + call defx#util#print_error(v:exception) + return [results, v:exception] + endtry + endfor + return [results, v:null] +endfunction diff --git a/bundle/defx.nvim/doc/defx.txt b/bundle/defx.nvim/doc/defx.txt index 46aa1076f..2aead20bc 100644 --- a/bundle/defx.nvim/doc/defx.txt +++ b/bundle/defx.nvim/doc/defx.txt @@ -1,6 +1,6 @@ *defx.txt* Dark powered file explorer for neovim/Vim8. -Version: 1.0 +Version: 2.0 Author: Shougo License: MIT license @@ -29,7 +29,9 @@ INTRODUCTION *defx-introduction* ============================================================================== INSTALL *defx-install* -Note: defx requires Neovim 0.3.0+ or Vim8.1+ with Python3.6.1+. +Note: defx requires Neovim 0.4.0+ or Vim8.2+ with Python3.6.1+. + +Note: The latest Neovim is recommended, because it is faster. Please install nvim-yarp plugin for Vim8. https://github.com/roxma/nvim-yarp @@ -233,6 +235,18 @@ cd *defx-action-cd* Action args: 0. new current directory path +change_filtered_files *defx-action-change_filtered_files* + Change |defx-option-filtered-files| dynamically. + + Action args: + 0. filtered files pattern + +change_ignored_files *defx-action-change_ignored_files* + Change |defx-option-ignored-files| dynamically. + + Action args: + 0. ignored files pattern + change_vim_cwd *change_vim_cwd* Change current working directory to the current directory. Note: It changes global current directory if the window has @@ -343,6 +357,14 @@ paste *defx-action-paste* Note: It is used after |defx-action-copy| or |defx-action-move|. +preview *defx-action-preview* + Preview the file. Close the preview window if it is already + exists. + Note: "ueberzug" and "bash" commands are needed to preview + image files. + https://pypi.org/project/ueberzug/ + Note: The image preview is for X11 only. + print *defx-action-print* Print the filename. @@ -418,6 +440,9 @@ toggle_select_visual yank_path *defx-action-yank_path* Yank the all candidates path. + Action args: + 0. |fnamemodify()| modifier(The default is "") + ------------------------------------------------------------------------------ OPTIONS *defx-options* @@ -425,7 +450,7 @@ OPTIONS *defx-options* -no-{option-name} Disable {option-name} flag. Note: If you use both {option-name} and -no-{option-name} in - the same denite buffer, it is undefined. + the same defx buffer, it is undefined. *defx-option-auto-cd* -auto-cd @@ -446,6 +471,11 @@ OPTIONS *defx-options* Specify defx buffer name. Default: "default" + *defx-option-close* +-close + Close defx buffer window. + Default: false + *defx-option-columns* -columns={columns1:columns2,...} Specify defx columns. @@ -458,6 +488,29 @@ OPTIONS *defx-options* You can use "topleft" or "botright". Default: "" + *defx-option-focus* +-focus + Focus on the defx buffer after opening a defx buffer. + + Default: true + + *defx-option-filtered-files* +-filtered-files={pattern} + Specify the filtered files pattern. + The pattern is comma separated. + Default: "" + + *defx-option-floating-preview* +-floating-preview + Open the preview window in floating window when + |defx-option-vertical-preview|. + Note: To use it, you need to use neovim + 0.4.0+(|nvim_open_win()|). + Note: If you need the feature in Vim8, you should use + 'previewpopup' instead. + + Default: false + *defx-option-ignored-files* -ignored-files={pattern} Specify the ignored files pattern. @@ -476,6 +529,19 @@ OPTIONS *defx-options* Default: false + *defx-option-preview-height* +-preview-height={preview-height} + Specify the preview window height. + + Default: 'previewheight' + + *defx-option-preview-width* +-preview-width={preview-width} + Specify the preview width when + |defx-option-vertical-preview|. + + Default: 40 + *defx-option-profile* -profile Enable profile feature. @@ -494,7 +560,7 @@ OPTIONS *defx-options* -root-marker={marker} Root marker. - Default: "[in]: " + Default: "[in] " *defx-option-search* -search={path} @@ -542,6 +608,11 @@ OPTIONS *defx-options* *defx-option-toggle* -toggle Close defx buffer window if this defx window exists. + Default: false + + *defx-option-vertical-preview* +-vertical-preview + Open the preview window vertically. Default: false *defx-option-wincol* @@ -704,7 +775,7 @@ EXAMPLES *defx-examples* nnoremap E \ defx#do_action('open', 'vsplit') nnoremap P - \ defx#do_action('open', 'pedit') + \ defx#do_action('preview') nnoremap o \ defx#do_action('open_tree', 'toggle') nnoremap K @@ -805,6 +876,12 @@ Q: I want to open file by double click. A: > nnoremap <2-LeftMouse> defx#do_action('open') + +Q: I want to separate defx state by tabs. + +A: > + Defx -buffer-name=`'defx' . tabpagenr()` + ============================================================================== COMPATIBILITY *defx-compatibility* diff --git a/bundle/defx.nvim/rplugin/python3/defx/action.py b/bundle/defx.nvim/rplugin/python3/defx/action.py index 8e27e3d4c..0d057bbca 100644 --- a/bundle/defx.nvim/rplugin/python3/defx/action.py +++ b/bundle/defx.nvim/rplugin/python3/defx/action.py @@ -31,6 +31,9 @@ def do_action(view: View, defx: Defx, """ Do "action_name" action. """ + if not defx._source: + return True + actions: typing.Dict[str, ActionTable] = defx._source.kind.get_actions() if action_name not in actions: diff --git a/bundle/defx.nvim/rplugin/python3/defx/base/column.py b/bundle/defx.nvim/rplugin/python3/defx/base/column.py index 1cd1b9cc6..5a8193645 100644 --- a/bundle/defx.nvim/rplugin/python3/defx/base/column.py +++ b/bundle/defx.nvim/rplugin/python3/defx/base/column.py @@ -9,10 +9,12 @@ import typing from abc import abstractmethod from defx.context import Context -from defx.util import Nvim +from defx.util import Nvim, Candidate from defx.util import error from defx.view import View +Highlights = typing.List[typing.Tuple[str, int, int]] + class Base: @@ -20,12 +22,14 @@ class Base: self.vim: Nvim = vim self.name: str = 'base' self.syntax_name: str = '' + self.highlight_name: str = '' self.start: int = -1 self.end: int = -1 self.vars: typing.Dict[str, typing.Any] = {} self.is_start_variable: bool = False self.is_stop_variable: bool = False self.is_within_variable: bool = False + self.has_get_with_highlights: bool = False def on_init(self, view: View, context: Context) -> None: pass @@ -33,14 +37,18 @@ class Base: def on_redraw(self, view: View, context: Context) -> None: pass - def get(self, context: Context, - candidate: typing.Dict[str, typing.Any]) -> str: + def get(self, context: Context, candidate: Candidate) -> str: return '' def get_with_variable_text( - self, context: Context, variable_text: str, - candidate: typing.Dict[str, typing.Any]) -> str: - return '' + self, context: Context, variable_text: str, candidate: Candidate + ) -> typing.Tuple[str, Highlights]: + return ('', []) + + def get_with_highlights( + self, context: Context, candidate: Candidate + ) -> typing.Tuple[str, Highlights]: + return ('', []) @abstractmethod def length(self, context: Context) -> int: diff --git a/bundle/defx.nvim/rplugin/python3/defx/base/kind.py b/bundle/defx.nvim/rplugin/python3/defx/base/kind.py index 7f3ea92eb..8c9772d0e 100644 --- a/bundle/defx.nvim/rplugin/python3/defx/base/kind.py +++ b/bundle/defx.nvim/rplugin/python3/defx/base/kind.py @@ -88,6 +88,24 @@ def _call(view: View, defx: Defx, context: Context) -> None: view._vim.call(function, dict_context) +@action(name='change_filtered_files', attr=ActionAttr.REDRAW) +def _change_filtered_files(view: View, defx: Defx, context: Context) -> None: + filtered_files = context.args[0] if context.args else view._vim.call( + 'defx#util#input', + f'{".".join(defx._filtered_files)} -> ', + '.'.join(defx._filtered_files)) + defx._filtered_files = filtered_files.split(',') + + +@action(name='change_ignored_files', attr=ActionAttr.REDRAW) +def _change_ignored_files(view: View, defx: Defx, context: Context) -> None: + ignored_files = context.args[0] if context.args else view._vim.call( + 'defx#util#input', + f'{".".join(defx._ignored_files)} -> ', + '.'.join(defx._ignored_files)) + defx._ignored_files = ignored_files.split(',') + + @action(name='clear_select_all', attr=ActionAttr.MARK | ActionAttr.NO_TAGETS) def _clear_select_all(view: View, defx: Defx, context: Context) -> None: for candidate in [x for x in view._candidates @@ -217,7 +235,7 @@ def _resize(view: View, defx: Defx, context: Context) -> None: return view._context = view._context._replace(winwidth=int(context.args[0])) - view._resize_window() + view._init_window() view.redraw(True) @@ -243,19 +261,7 @@ def _search(view: View, defx: Defx, context: Context) -> None: return search_path = context.args[0] - path = Path(search_path) - parents: typing.List[Path] = [] - while view.get_candidate_pos( - path, defx._index) < 0 and path.parent != path: - path = path.parent - parents.append(path) - - for parent in reversed(parents): - view.open_tree(parent, defx._index, False, 0) - - view.update_candidates() - view.redraw() - view.search_file(Path(search_path), defx._index) + view.search_recursive(Path(search_path), defx._index) @action(name='toggle_columns', attr=ActionAttr.REDRAW) @@ -324,7 +330,11 @@ def _toggle_sort(view: View, defx: Defx, context: Context) -> None: @action(name='yank_path') def _yank_path(view: View, defx: Defx, context: Context) -> None: - yank = '\n'.join([str(x['action__path']) for x in context.targets]) + mods = context.args[0] if context.args else '' + paths = [str(x['action__path']) for x in context.targets] + if mods: + paths = [view._vim.call('fnamemodify', x, mods) for x in paths] + yank = '\n'.join(paths) view._vim.call('setreg', '"', yank) if (view._vim.call('has', 'clipboard') or view._vim.call('has', 'xterm_clipboard')): diff --git a/bundle/defx.nvim/rplugin/python3/defx/column/filename.py b/bundle/defx.nvim/rplugin/python3/defx/column/filename.py index 59d3738c0..ed4d319c4 100644 --- a/bundle/defx.nvim/rplugin/python3/defx/column/filename.py +++ b/bundle/defx.nvim/rplugin/python3/defx/column/filename.py @@ -4,9 +4,9 @@ # License: MIT license # ============================================================================ -from defx.base.column import Base +from defx.base.column import Base, Highlights from defx.context import Context -from defx.util import Nvim +from defx.util import Nvim, Candidate, len_bytes, strwidth from defx.view import View import typing @@ -24,12 +24,12 @@ class Column(Base): 'root_marker_highlight': 'Constant', } self.is_stop_variable = True + self.has_get_with_highlights = True self._current_length = 0 self._syntaxes = [ 'directory', 'directory_marker', - 'hidden', 'root', 'root_marker', ] @@ -41,15 +41,30 @@ class Column(Base): self._context = context def get_with_variable_text( - self, context: Context, variable_text: str, - candidate: typing.Dict[str, typing.Any]) -> str: - marker = (self._directory_marker - if candidate['is_directory'] and not candidate['is_root'] - else self._file_marker) - return self._truncate(variable_text + marker + candidate['word']) + self, context: Context, variable_text: str, candidate: Candidate + ) -> typing.Tuple[str, Highlights]: + text = variable_text + highlights = [] + + if candidate['is_directory']: + if candidate['is_root']: + root_len = len_bytes(candidate['root_marker']) + highlights = [ + (f'{self.highlight_name}_root_marker', + self.start, root_len), + (f'{self.highlight_name}_root', + self.start + root_len, + len_bytes(candidate['word']) - root_len), + ] + else: + highlights = [(f'{self.highlight_name}_directory', + self.start, len_bytes(candidate['word']))] + + text += candidate['word'] + return (self._truncate(text), highlights) def length(self, context: Context) -> int: - max_fnamewidth = max([self._strwidth(x['word']) + max_fnamewidth = max([strwidth(self.vim, x['word']) for x in context.targets]) max_fnamewidth += context.variable_length max_fnamewidth += len(self._file_marker) @@ -67,63 +82,21 @@ class Column(Base): def highlight_commands(self) -> typing.List[str]: commands: typing.List[str] = [] - directory_marker = self.vim.call( - 'escape', self._directory_marker, r'~/\.^$[]*') - commands.append( - r'syntax match {0}_{1} /{2}/ conceal contained ' - 'containedin={0}_directory'.format( - self.syntax_name, 'directory_marker', directory_marker)) - commands.append( - r'syntax match {0}_{1} /{2}\%(.{3}[{4}/]\)\+/ ' - 'contained containedin={0}'.format( - self.syntax_name, 'directory', directory_marker, r'\{-}', - '\\' if self.vim.call('defx#util#is_windows') else '')) - - file_marker = self.vim.call( - 'escape', self._file_marker, r'~/\.^$[]*') - commands.append( - r'syntax match {0}_{1} /{2}/ conceal contained ' - 'containedin={0}_file'.format( - self.syntax_name, 'file_marker', file_marker)) - commands.append( - r'syntax match {0}_{1} /{2}.{3}/ ' - 'contained containedin={0}'.format( - self.syntax_name, 'file', file_marker, r'\{-}')) - - root_marker = self.vim.call( - 'escape', self._context.root_marker, r'~/\.^$[]*') - commands.append( - r'syntax match {0}_{1} /{2}/ contained ' - 'containedin={0}_root'.format( - self.syntax_name, 'root_marker', root_marker)) - commands.append( - r'syntax match {0}_{1} /{2}.*/ contained ' - 'containedin={0}'.format( - self.syntax_name, 'root', root_marker)) - commands.append( 'highlight default link {}_{} {}'.format( - self.syntax_name, 'directory', 'PreProc')) + self.highlight_name, 'directory', 'PreProc')) commands.append( 'highlight default link {}_{} {}'.format( - self.syntax_name, 'hidden', 'Comment')) - commands.append( - 'highlight default link {}_{} {}'.format( - self.syntax_name, 'root_marker', + self.highlight_name, 'root_marker', self.vars['root_marker_highlight'])) commands.append( 'highlight default link {}_{} {}'.format( - self.syntax_name, 'root', 'Identifier')) + self.highlight_name, 'root', 'Identifier')) return commands - def _strwidth(self, word: str) -> int: - return (int(self.vim.call('strwidth', word)) - if len(word) != len(bytes(word, 'utf-8', - 'surrogatepass')) else len(word)) - def _truncate(self, word: str) -> str: - width = self._strwidth(word) + width = strwidth(self.vim, word) max_length = self._current_length if (width > max_length or len(word) != len(bytes(word, 'utf-8', 'surrogatepass'))): diff --git a/bundle/defx.nvim/rplugin/python3/defx/column/icon.py b/bundle/defx.nvim/rplugin/python3/defx/column/icon.py index d72952e84..0c7af04d3 100644 --- a/bundle/defx.nvim/rplugin/python3/defx/column/icon.py +++ b/bundle/defx.nvim/rplugin/python3/defx/column/icon.py @@ -5,9 +5,9 @@ # License: MIT license # ============================================================================ -from defx.base.column import Base +from defx.base.column import Base, Highlights from defx.context import Context -from defx.util import Nvim +from defx.util import Nvim, Candidate, len_bytes import typing @@ -24,23 +24,36 @@ class Column(Base): 'opened_icon': '-', 'root_icon': ' ', } + self.has_get_with_highlights = True + self._syntaxes = [ 'directory_icon', 'opened_icon', 'root_icon', ] + self._highlights = { + 'directory': 'Special', + 'opened': 'Special', + 'root': 'Identifier', + } - def get(self, context: Context, - candidate: typing.Dict[str, typing.Any]) -> str: - icon: str = ' ' + def get_with_highlights( + self, context: Context, candidate: Candidate + ) -> typing.Tuple[str, Highlights]: if candidate['is_opened_tree']: - icon = self.vars['opened_icon'] + return (self.vars['opened_icon'], + [(f'{self.highlight_name}_opened_icon', + self.start, len_bytes(self.vars['opened_icon']))]) elif candidate['is_root']: - icon = self.vars['root_icon'] + return (self.vars['root_icon'], + [(f'{self.highlight_name}_root_icon', + self.start, len_bytes(self.vars['root_icon']))]) elif candidate['is_directory']: - icon = self.vars['directory_icon'] + return (self.vars['directory_icon'], + [(f'{self.highlight_name}_directory_icon', + self.start, len_bytes(self.vars['directory_icon']))]) - return icon + return (' ', []) def length(self, context: Context) -> int: return typing.cast(int, self.vars['length']) @@ -50,19 +63,9 @@ class Column(Base): def highlight_commands(self) -> typing.List[str]: commands: typing.List[str] = [] - for icon, highlight in { - 'directory': 'Special', - 'opened': 'Special', - 'root': 'Identifier', - }.items(): - commands.append( - ('syntax match {0}_{1}_icon /[{2}]{3}/ ' + - 'contained containedin={0}').format( - self.syntax_name, icon, self.vars[icon + '_icon'], - ' ' if self.is_within_variable else '' - )) + for icon, highlight in self._highlights.items(): commands.append( 'highlight default link {}_{}_icon {}'.format( - self.syntax_name, icon, highlight)) + self.highlight_name, icon, highlight)) return commands diff --git a/bundle/defx.nvim/rplugin/python3/defx/column/mark.py b/bundle/defx.nvim/rplugin/python3/defx/column/mark.py index 7d9f6c868..b057d91a8 100644 --- a/bundle/defx.nvim/rplugin/python3/defx/column/mark.py +++ b/bundle/defx.nvim/rplugin/python3/defx/column/mark.py @@ -4,9 +4,9 @@ # License: MIT license # ============================================================================ -from defx.base.column import Base +from defx.base.column import Base, Highlights from defx.context import Context -from defx.util import Nvim +from defx.util import Nvim, Candidate, len_bytes import os import typing @@ -29,15 +29,25 @@ class Column(Base): 'readonly', 'selected', ] + self.has_get_with_highlights = True - def get(self, context: Context, - candidate: typing.Dict[str, typing.Any]) -> str: - icon: str = ' ' * self.vars['length'] + self._icons = { + 'readonly': 'Comment', + 'selected': 'Statement', + } + + def get_with_highlights( + self, context: Context, candidate: Candidate + ) -> typing.Tuple[str, Highlights]: if candidate['is_selected']: - icon = self.vars['selected_icon'] + return (str(self.vars['selected_icon']), + [(f'{self.highlight_name}_selected', + self.start, len_bytes(self.vars['selected_icon']))]) elif not os.access(str(candidate['action__path']), os.W_OK): - icon = self.vars['readonly_icon'] - return icon + return (str(self.vars['readonly_icon']), + [(f'{self.highlight_name}_readonly', + self.start, len_bytes(self.vars['readonly_icon']))]) + return (' ' * self.vars['length'], []) def length(self, context: Context) -> int: return typing.cast(int, self.vars['length']) @@ -47,15 +57,8 @@ class Column(Base): def highlight_commands(self) -> typing.List[str]: commands: typing.List[str] = [] - for icon, highlight in { - 'readonly': 'Comment', - 'selected': 'Statement', - }.items(): - commands.append( - ('syntax match {0}_{1} /[{2}]/ ' + - 'contained containedin={0}').format( - self.syntax_name, icon, self.vars[icon + '_icon'])) + for icon, highlight in self._icons.items(): commands.append( 'highlight default link {}_{} {}'.format( - self.syntax_name, icon, highlight)) + self.highlight_name, icon, highlight)) return commands diff --git a/bundle/defx.nvim/rplugin/python3/defx/column/size.py b/bundle/defx.nvim/rplugin/python3/defx/column/size.py index ae67bc948..3733fecad 100644 --- a/bundle/defx.nvim/rplugin/python3/defx/column/size.py +++ b/bundle/defx.nvim/rplugin/python3/defx/column/size.py @@ -4,9 +4,9 @@ # License: MIT license # ============================================================================ -from defx.base.column import Base +from defx.base.column import Base, Highlights from defx.context import Context -from defx.util import Nvim, readable +from defx.util import Nvim, readable, Candidate import typing @@ -17,14 +17,18 @@ class Column(Base): super().__init__(vim) self.name = 'size' + self.has_get_with_highlights = True + self._length = 9 - def get(self, context: Context, - candidate: typing.Dict[str, typing.Any]) -> str: + def get_with_highlights( + self, context: Context, candidate: Candidate + ) -> typing.Tuple[str, Highlights]: path = candidate['action__path'] if not readable(path) or path.is_dir(): - return ' ' * 9 + return (' ' * self._length, []) size = self._get_size(path.stat().st_size) - return '{:>6s}{:>3s}'.format(size[0], size[1]) + text = '{:>6s}{:>3s}'.format(size[0], size[1]) + return (text, [(self.highlight_name, self.start, self._length)]) def _get_size(self, size: float) -> typing.Tuple[str, str]: multiple = 1024 @@ -38,10 +42,10 @@ class Column(Base): return ('INF', '') def length(self, context: Context) -> int: - return 9 + return self._length def highlight_commands(self) -> typing.List[str]: commands: typing.List[str] = [] commands.append( - f'highlight default link {self.syntax_name} Constant') + f'highlight default link {self.highlight_name} Constant') return commands diff --git a/bundle/defx.nvim/rplugin/python3/defx/column/time.py b/bundle/defx.nvim/rplugin/python3/defx/column/time.py index 9a6f60b55..7eea4bfe4 100644 --- a/bundle/defx.nvim/rplugin/python3/defx/column/time.py +++ b/bundle/defx.nvim/rplugin/python3/defx/column/time.py @@ -4,9 +4,9 @@ # License: MIT license # ============================================================================ -from defx.base.column import Base +from defx.base.column import Base, Highlights from defx.context import Context -from defx.util import Nvim, readable +from defx.util import Nvim, readable, Candidate from defx.view import View import time @@ -19,22 +19,26 @@ class Column(Base): super().__init__(vim) self.name = 'time' - self._length = 0 self.vars = { 'format': '%y.%m.%d %H:%M', } + self.has_get_with_highlights = True + + self._length = 0 def on_init(self, view: View, context: Context) -> None: self._length = self.vim.call('strwidth', time.strftime(self.vars['format'])) - def get(self, context: Context, - candidate: typing.Dict[str, typing.Any]) -> str: + def get_with_highlights( + self, context: Context, candidate: Candidate + ) -> typing.Tuple[str, Highlights]: path = candidate['action__path'] if not readable(path): - return str(' ' * self._length) - return time.strftime(self.vars['format'], + return (str(' ' * self._length), []) + text = time.strftime(self.vars['format'], time.localtime(path.stat().st_mtime)) + return (text, [(self.highlight_name, self.start, self._length)]) def length(self, context: Context) -> int: return self._length @@ -42,5 +46,5 @@ class Column(Base): def highlight_commands(self) -> typing.List[str]: commands: typing.List[str] = [] commands.append( - f'highlight default link {self.syntax_name} Identifier') + f'highlight default link {self.highlight_name} Identifier') return commands diff --git a/bundle/defx.nvim/rplugin/python3/defx/column/type.py b/bundle/defx.nvim/rplugin/python3/defx/column/type.py index 66d2b9f41..99f79d847 100644 --- a/bundle/defx.nvim/rplugin/python3/defx/column/type.py +++ b/bundle/defx.nvim/rplugin/python3/defx/column/type.py @@ -4,12 +4,11 @@ # License: MIT license # ============================================================================ -from defx.base.column import Base +from defx.base.column import Base, Highlights from defx.context import Context -from defx.util import Nvim +from defx.util import Nvim, Candidate, len_bytes from defx.view import View -import re import typing @@ -21,7 +20,7 @@ class Column(Base): self.name = 'type' types = [ { - 'name': 'text', 'globs': ['*.txt'], + 'name': 'text', 'globs': ['*.txt', '*.md', 'README'], 'icon': '[T]', 'highlight': 'Constant' }, { @@ -40,19 +39,27 @@ class Column(Base): self.vars = { 'types': types, } + self.has_get_with_highlights = True + self._length: int = 0 def on_init(self, view: View, context: Context) -> None: self._length = max([self.vim.call('strwidth', x['icon']) for x in self.vars['types']]) - def get(self, context: Context, - candidate: typing.Dict[str, typing.Any]) -> str: + def get_with_highlights( + self, context: Context, candidate: Candidate + ) -> typing.Tuple[str, Highlights]: for t in self.vars['types']: for glob in t['globs']: - if candidate['action__path'].match(glob): - return str(t['icon']) - return ' ' * self._length + if not candidate['action__path'].match(glob): + continue + return (str(t['icon']), [ + (f"{self.highlight_name}_{t['name']}", + self.start, len_bytes(t['icon'])) + ]) + + return (' ' * self._length, []) def length(self, context: Context) -> int: return self._length @@ -64,11 +71,7 @@ class Column(Base): def highlight_commands(self) -> typing.List[str]: commands: typing.List[str] = [] for t in self.vars['types']: - commands.append( - ('syntax match {0}_{1} /{2}/ ' + - 'contained containedin={0}').format( - self.syntax_name, t['name'], re.escape(t['icon']))) commands.append( 'highlight default link {}_{} {}'.format( - self.syntax_name, t['name'], t['highlight'])) + self.highlight_name, t['name'], t['highlight'])) return commands diff --git a/bundle/defx.nvim/rplugin/python3/defx/context.py b/bundle/defx.nvim/rplugin/python3/defx/context.py index a2ff6d06d..8d2cd63ad 100644 --- a/bundle/defx.nvim/rplugin/python3/defx/context.py +++ b/bundle/defx.nvim/rplugin/python3/defx/context.py @@ -12,31 +12,39 @@ class Context(typing.NamedTuple): auto_cd: bool = False auto_recursive_level: int = 0 buffer_name: str = 'default' + close: bool = False columns: str = '' cursor: int = 0 direction: str = '' drives: typing.List[str] = [] + filtered_files: str = '' + focus: bool = False + floating_preview: bool = False ignored_files: str = '' listed: bool = False new: bool = False prev_bufnr: int = 0 prev_last_bufnr: int = 0 prev_winid: int = 0 + preview_height: int = 0 + preview_width: int = 0 profile: bool = False resume: bool = False root_marker: str = '' search: str = '' session_file: str = '' - sort: str = '' show_ignored_files: bool = False + sort: str = '' split: str = 'no' - toggle: bool = False targets: typing.List[typing.Dict[str, typing.Any]] = [] + toggle: bool = False variable_length: int = 0 - visual_start: int = 0 visual_end: int = 0 + visual_start: int = 0 + with_highlights: bool = True wincol: int = 0 winheight: int = 0 winrelative: str = 'editor' winrow: int = 0 winwidth: int = 0 + vertical_preview: bool = False diff --git a/bundle/defx.nvim/rplugin/python3/defx/defx.py b/bundle/defx.nvim/rplugin/python3/defx/defx.py index db7a25f81..cc8828bec 100644 --- a/bundle/defx.nvim/rplugin/python3/defx/defx.py +++ b/bundle/defx.nvim/rplugin/python3/defx/defx.py @@ -6,7 +6,9 @@ import typing -from defx.source.file import Source as File +from defx.base.source import Base as Source +from defx.source.file.list import Source as SourceList +from defx.source.file import Source as SourceFile from defx.context import Context from defx.sort import sort from defx.util import Nvim @@ -20,20 +22,25 @@ Candidate = typing.Dict[str, typing.Any] class Defx(object): def __init__(self, vim: Nvim, context: Context, - cwd: str, index: int) -> None: + source_name: str, cwd: str, index: int) -> None: self._vim = vim self._context = context self._cwd = self._vim.call('getcwd') self.cd(cwd) - self._source: File = File(self._vim) + + self._source: Source = (SourceList(self._vim) + if source_name == 'file/list' + else SourceFile(self._vim)) self._index = index self._enabled_ignored_files = not context.show_ignored_files + self._filtered_files = context.filtered_files.split(',') self._ignored_files = context.ignored_files.split(',') self._cursor_history: typing.Dict[str, Path] = {} self._sort_method: str = self._context.sort self._mtime: int = -1 self._opened_candidates: typing.Set[str] = set() self._selected_candidates: typing.Set[str] = set() + self._nested_candidates: typing.Set[str] = set() self._init_source() @@ -49,19 +56,24 @@ class Defx(object): def cd(self, path: str) -> None: self._cwd = str(Path(self._cwd).joinpath(path)) - if self._context.auto_cd: + if self._context.auto_cd and Path(path).is_dir(): cd(self._vim, path) def get_root_candidate(self) -> Candidate: """ Returns root candidate """ + if not self._source: + return {} + root = self._source.get_root_candidate(self._context, Path(self._cwd)) root['is_root'] = True root['is_opened_tree'] = False root['is_selected'] = False root['level'] = 0 - root['word'] = self._context.root_marker + root['word'] + root['root_marker'] = self._context.root_marker + ( + self._source.name + ':' if self._source.name != 'file' else '') + root['word'] = root['root_marker'] + root['word'] return root @@ -71,21 +83,26 @@ class Defx(object): gathered_candidates = self.gather_candidates_recursive( path, base_level, max_level) - if self._opened_candidates: - candidates = [] - for candidate in gathered_candidates: - candidates.append(candidate) - candidate['level'] = base_level - candidate_path = str(candidate['action__path']) + if not self._opened_candidates and not self._nested_candidates: + return gathered_candidates - if (candidate_path in self._opened_candidates and - not candidate['is_opened_tree']): - candidate['is_opened_tree'] = True - candidates += self.tree_candidates( - candidate_path, base_level + 1, max_level) - else: - candidates = gathered_candidates + candidates = [] + for candidate in gathered_candidates: + candidates.append(candidate) + candidate['level'] = base_level + candidate_path = str(candidate['action__path']) + if not candidate['is_directory']: + continue + if (candidate_path not in self._opened_candidates and + candidate_path not in self._nested_candidates): + continue + + children = self.tree_candidates( + candidate_path, base_level + 1, max_level) + + candidate['is_opened_tree'] = True + candidates += children return candidates def gather_candidates_recursive( @@ -110,9 +127,24 @@ class Defx(object): """ Returns file candidates """ + if not self._source: + return [] + candidates = self._source.gather_candidates( self._context, Path(path)) + if self._filtered_files != ['']: + new_candidates = [] + for candidate in candidates: + matched = False + for glob in self._filtered_files: + if candidate['action__path'].match(glob): + matched = True + break + if matched or candidate['is_directory']: + new_candidates.append(candidate) + candidates = new_candidates + if self._enabled_ignored_files: for glob in self._ignored_files: candidates = [x for x in candidates diff --git a/bundle/defx.nvim/rplugin/python3/defx/kind/file.py b/bundle/defx.nvim/rplugin/python3/defx/kind/file.py index 0ccd9499b..851864a6a 100644 --- a/bundle/defx.nvim/rplugin/python3/defx/kind/file.py +++ b/bundle/defx.nvim/rplugin/python3/defx/kind/file.py @@ -4,9 +4,10 @@ # License: MIT license # ============================================================================ +from pathlib import Path import copy import importlib -from pathlib import Path +import mimetypes import shutil import time import typing @@ -17,7 +18,7 @@ from defx.base.kind import Base from defx.clipboard import ClipboardAction from defx.context import Context from defx.defx import Defx -from defx.util import cd, cwd_input, confirm, error +from defx.util import cd, cwd_input, confirm, error, Candidate from defx.util import readable, Nvim from defx.view import View @@ -50,6 +51,9 @@ class Kind(Base): def check_overwrite(view: View, dest: Path, src: Path) -> Path: + if not src.exists() or not dest.exists(): + return Path('') + s_stat = src.stat() s_mtime = s_stat.st_mtime view.print_msg(f' src: {src} {s_stat.st_size} bytes') @@ -68,8 +72,10 @@ def check_overwrite(view: View, dest: Path, src: Path) -> Path: elif choice == 2: ret = Path('') elif choice == 3: - ret = Path(view._vim.call('input', f'{src} -> ', str(dest), - ('dir' if src.is_dir() else 'file'))) + ret = Path(view._vim.call( + 'defx#util#input', + f'{src} -> ', str(dest), + ('dir' if src.is_dir() else 'file'))) elif choice == 4 and d_mtime < s_mtime: ret = src elif choice == 5: @@ -82,14 +88,22 @@ def _cd(view: View, defx: Defx, context: Context) -> None: """ Change the current directory. """ - path = Path(context.args[0]) if context.args else Path.home() + source_name = defx._source.name + if context.args: + if len(context.args) > 1: + source_name = context.args[0] + path = Path(context.args[1]) + else: + path = Path(context.args[0]) + else: + path = Path.home() path = Path(defx._cwd).joinpath(path).resolve() - if not readable(path) or not path.is_dir(): - error(view._vim, f'{path} is not readable directory') + if not readable(path) or (source_name == 'file' and not path.is_dir()): + error(view._vim, f'{path} is invalid.') return prev_cwd = defx._cwd - view.cd(defx, str(path), context.cursor) + view.cd(defx, source_name, str(path), context.cursor) if context.args and context.args[0] == '..': view.search_file(Path(prev_cwd), defx._index) @@ -132,14 +146,14 @@ def _drop(view: View, defx: Defx, context: Context) -> None: """ Open like :drop. """ - cwd = view._vim.call('getcwd') + cwd = view._vim.call('getcwd', -1) command = context.args[0] if context.args else 'edit' for target in context.targets: path = target['action__path'] if path.is_dir(): - view.cd(defx, str(path), context.cursor) + view.cd(defx, defx._source.name, str(path), context.cursor) continue bufnr = view._vim.call('bufnr', f'^{path}$') @@ -153,12 +167,18 @@ def _drop(view: View, defx: Defx, context: Context) -> None: view._vim.call('win_gotoid', context.prev_winid) else: view._vim.command('wincmd w') - try: - path = path.relative_to(cwd) - except ValueError: - pass + + if not view._vim.call('haslocaldir'): + try: + path = path.relative_to(cwd) + except ValueError: + pass + view._vim.call('defx#util#execute_path', command, str(path)) + view.restore_previous_buffer() + view.close_preview() + @action(name='execute_command', attr=ActionAttr.NO_TAGETS) def _execute_command(view: View, defx: Defx, context: Context) -> None: @@ -231,7 +251,7 @@ def _new_directory(view: View, defx: Defx, context: Context) -> None: filename.mkdir(parents=True) view.redraw(True) - view.search_file(filename, defx._index) + view.search_recursive(filename, defx._index) @action(name='new_file') @@ -269,7 +289,7 @@ def _new_file(view: View, defx: Defx, context: Context) -> None: filename.touch() view.redraw(True) - view.search_file(filename, defx._index) + view.search_recursive(filename, defx._index) @action(name='new_multiple_files') @@ -312,7 +332,7 @@ def _new_multiple_files(view: View, defx: Defx, context: Context) -> None: filename.touch() view.redraw(True) - view.search_file(filename, defx._index) + view.search_recursive(filename, defx._index) @action(name='open') @@ -320,21 +340,32 @@ def _open(view: View, defx: Defx, context: Context) -> None: """ Open the file. """ - cwd = view._vim.call('getcwd') + cwd = view._vim.call('getcwd', -1) command = context.args[0] if context.args else 'edit' + previewed_buffers = view._vim.vars['defx#_previewed_buffers'] for target in context.targets: path = target['action__path'] if path.is_dir(): - view.cd(defx, str(path), context.cursor) + view.cd(defx, defx._source.name, str(path), context.cursor) continue - try: - path = path.relative_to(cwd) - except ValueError: - pass + if not view._vim.call('haslocaldir'): + try: + path = path.relative_to(cwd) + except ValueError: + pass + view._vim.call('defx#util#execute_path', command, str(path)) + bufnr = str(view._vim.call('bufnr', str(path))) + if bufnr in previewed_buffers: + previewed_buffers.pop(bufnr) + view._vim.vars['defx#_previewed_buffers'] = previewed_buffers + + view.restore_previous_buffer() + view.close_preview() + @action(name='open_directory') def _open_directory(view: View, defx: Defx, context: Context) -> None: @@ -348,7 +379,7 @@ def _open_directory(view: View, defx: Defx, context: Context) -> None: path = target['action__path'] if path.is_dir(): - view.cd(defx, str(path), context.cursor) + view.cd(defx, 'file', str(path), context.cursor) @action(name='paste', attr=ActionAttr.NO_TAGETS) @@ -373,7 +404,7 @@ def _paste(view: View, defx: Defx, context: Context) -> None: continue dest = overwrite - if path == dest: + if not path.exists() or path == dest: continue view.print_msg( @@ -384,13 +415,89 @@ def _paste(view: View, defx: Defx, context: Context) -> None: else: shutil.copy2(str(path), dest) elif action == ClipboardAction.MOVE: + if dest.exists(): + # Must remove dest before + if dest.is_dir(): + shutil.rmtree(str(dest)) + else: + dest.unlink() shutil.move(str(path), cwd) view._vim.command('redraw') + if action == ClipboardAction.MOVE: + # Clear clipboard after move + view._clipboard.candidates = [] view._vim.command('echo') view.redraw(True) if dest: - view.search_file(dest, defx._index) + view.search_recursive(dest, defx._index) + + +@action(name='preview') +def _preview(view: View, defx: Defx, context: Context) -> None: + candidate = view.get_cursor_candidate(context.cursor) + if not candidate or candidate['action__path'].is_dir(): + return + + filepath = str(candidate['action__path']) + guess_type = mimetypes.guess_type(filepath)[0] + if (guess_type and guess_type.startswith('image/') and + shutil.which('ueberzug') and shutil.which('bash')): + _preview_image(view, defx, context, candidate) + return + + _preview_file(view, defx, context, candidate) + + +def _preview_file(view: View, defx: Defx, + context: Context, candidate: Candidate) -> None: + filepath = str(candidate['action__path']) + + has_preview = bool(view._vim.call('defx#util#_get_preview_window')) + if (has_preview and view._previewed_target and + view._previewed_target == candidate): + view._vim.command('pclose!') + return + + prev_id = view._vim.call('win_getid') + + listed = view._vim.call('buflisted', filepath) + + view._previewed_target = candidate + view._vim.call('defx#util#preview_file', + context._replace(targets=[])._asdict(), filepath) + view._vim.current.window.options['foldenable'] = False + + if not listed: + bufnr = str(view._vim.call('bufnr', filepath)) + previewed_buffers = view._vim.vars['defx#_previewed_buffers'] + previewed_buffers[bufnr] = 1 + view._vim.vars['defx#_previewed_buffers'] = previewed_buffers + + view._vim.call('win_gotoid', prev_id) + + +def _preview_image(view: View, defx: Defx, + context: Context, candidate: Candidate) -> None: + has_nvim = view._vim.call('has', 'nvim') + filepath = str(candidate['action__path']) + + preview_image_sh = Path(__file__).parent.parent.joinpath( + 'preview_image.sh') + if has_nvim: + jobfunc = 'jobstart' + jobopts = {} + else: + jobfunc = 'job_start' + jobopts = {'in_io': 'null', 'out_io': 'null', 'err_io': 'null'} + + wincol = context.wincol + view._vim.call('winwidth', 0) + if wincol + context.preview_width > view._vim.options['columns']: + wincol -= 2 * context.preview_width + args = ['bash', str(preview_image_sh), filepath, + wincol, 1, context.preview_width] + + view._vim.call(jobfunc, args, jobopts) @action(name='remove', attr=ActionAttr.REDRAW) @@ -418,8 +525,11 @@ def _remove(view: View, defx: Defx, context: Context) -> None: else: path.unlink() + view._vim.call('defx#util#buffer_delete', + view._vim.call('bufnr', str(path))) -@action(name='remove_trash') + +@action(name='remove_trash', attr=ActionAttr.REDRAW) def _remove_trash(view: View, defx: Defx, context: Context) -> None: """ Delete the file or directory. @@ -443,7 +553,9 @@ def _remove_trash(view: View, defx: Defx, context: Context) -> None: import send2trash for target in context.targets: send2trash.send2trash(str(target['action__path'])) - view.redraw(True) + + view._vim.call('defx#util#buffer_delete', + view._vim.call('bufnr', str(target['action__path']))) @action(name='rename') @@ -475,7 +587,13 @@ def _rename(view: View, defx: Defx, context: Context) -> None: error(view._vim, f'{new} already exists') continue + if not new.parent.exists(): + new.parent.mkdir(parents=True) old.rename(new) + # Check rename + view._vim.call('defx#util#buffer_rename', + view._vim.call('bufnr', str(old)), str(new)) + view.redraw(True) - view.search_file(new, defx._index) + view.search_recursive(new, defx._index) diff --git a/bundle/defx.nvim/rplugin/python3/defx/preview_image.py b/bundle/defx.nvim/rplugin/python3/defx/preview_image.py new file mode 100644 index 000000000..1fab8cd06 --- /dev/null +++ b/bundle/defx.nvim/rplugin/python3/defx/preview_image.py @@ -0,0 +1,17 @@ +import ueberzug.lib.v0 as ueberzug +import sys +import time + +if __name__ == '__main__' and len(sys.argv) > 3: + with ueberzug.Canvas() as c: + total_width = int(sys.argv[2]) + preview_width = int(sys.argv[3]) + ratio = preview_width / total_width + width = preview_width + + demo = c.create_placement( + 'demo', x=(total_width - preview_width) * ratio, y=1, + width=width, scaler=ueberzug.ScalerOption.COVER.value) + demo.path = sys.argv[1] + demo.visibility = ueberzug.Visibility.VISIBLE + time.sleep(1) diff --git a/bundle/defx.nvim/rplugin/python3/defx/preview_image.sh b/bundle/defx.nvim/rplugin/python3/defx/preview_image.sh new file mode 100644 index 000000000..2752db687 --- /dev/null +++ b/bundle/defx.nvim/rplugin/python3/defx/preview_image.sh @@ -0,0 +1,4 @@ +{ + declare -pA addCommand=([action]="add" [identifier]="defx_preview" [x]="$2" [y]="$3" [width]="$4" [path]="$1") + sleep 5 +} | ueberzug layer --parser bash diff --git a/bundle/defx.nvim/rplugin/python3/defx/rplugin.py b/bundle/defx.nvim/rplugin/python3/defx/rplugin.py index c875bb491..c51b9e7e4 100644 --- a/bundle/defx.nvim/rplugin/python3/defx/rplugin.py +++ b/bundle/defx.nvim/rplugin/python3/defx/rplugin.py @@ -23,13 +23,8 @@ class Rplugin: def start(self, args: typing.List[typing.Any]) -> None: [paths, context] = args - views = [x for x in self._views - if context['buffer_name'] == x._context.buffer_name] - if not views or context['new']: - view = View(self._vim, len(self._views)) - views = [view] - self._views.append(view) - views[0].init(paths, context, self._clipboard) + self.get_view(context).init_paths( + paths, context, self._clipboard) def do_action(self, args: typing.List[typing.Any]) -> None: views = [x for x in self._views @@ -67,6 +62,15 @@ class Rplugin: return view._context._asdict() return {} + def get_view(self, context: typing.Dict[str, typing.Any]) -> View: + views = [x for x in self._views + if context['buffer_name'] == x._context.buffer_name] + if not views or context['new']: + view = View(self._vim, len(self._views)) + views = [view] + self._views.append(view) + return views[0] + def redraw(self, views: typing.List[View]) -> None: call = self._vim.call for view in [x for x in views diff --git a/bundle/defx.nvim/rplugin/python3/defx/source/file.py b/bundle/defx.nvim/rplugin/python3/defx/source/file/__init__.py similarity index 79% rename from bundle/defx.nvim/rplugin/python3/defx/source/file.py rename to bundle/defx.nvim/rplugin/python3/defx/source/file/__init__.py index c00cbd7a1..5ee5c20d2 100644 --- a/bundle/defx.nvim/rplugin/python3/defx/source/file.py +++ b/bundle/defx.nvim/rplugin/python3/defx/source/file/__init__.py @@ -51,11 +51,14 @@ class Source(Base): if not readable(path) or not path.is_dir(): error(self.vim, f'"{path}" is not readable directory.') return [] - for entry in path.iterdir(): - candidates.append({ - 'word': entry.name.replace('\n', '\\n') + ( - '/' if safe_call(entry.is_dir, False) else ''), - 'is_directory': safe_call(entry.is_dir, False), - 'action__path': entry, - }) + try: + for entry in path.iterdir(): + candidates.append({ + 'word': entry.name.replace('\n', '\\n') + ( + '/' if safe_call(entry.is_dir, False) else ''), + 'is_directory': safe_call(entry.is_dir, False), + 'action__path': entry, + }) + except OSError: + pass return candidates diff --git a/bundle/defx.nvim/rplugin/python3/defx/source/file/list.py b/bundle/defx.nvim/rplugin/python3/defx/source/file/list.py new file mode 100644 index 000000000..04b38d634 --- /dev/null +++ b/bundle/defx.nvim/rplugin/python3/defx/source/file/list.py @@ -0,0 +1,71 @@ +# ============================================================================ +# FILE: file/list.py +# AUTHOR: Shougo Matsushita +# License: MIT license +# ============================================================================ + +from pathlib import Path +import typing + +from defx.base.source import Base +from defx.source.file import Source as File +from defx.context import Context +from defx.util import error, readable, safe_call, Nvim + + +class Source(Base): + + def __init__(self, vim: Nvim) -> None: + super().__init__(vim) + + self.name = 'file/list' + + from defx.kind.file import Kind + self.kind: Kind = Kind(self.vim) + + self.vars = { + 'root': None, + } + + def get_root_candidate( + self, context: Context, path: Path + ) -> typing.Dict[str, typing.Any]: + word = self.vim.call('fnamemodify', str(path), ':~') + if self.vim.call('defx#util#is_windows'): + word = word.replace('\\', '/') + if word[-1:] != '/': + word += '/' + if self.vars['root']: + word = self.vim.call(self.vars['root'], str(path)) + word = word.replace('\n', '\\n') + + return { + 'word': word, + 'is_directory': False, + 'action__path': path, + } + + def gather_candidates( + self, context: Context, path: Path + ) -> typing.List[typing.Dict[str, typing.Any]]: + if not readable(path): + error(self.vim, f'"{path}" is not readable file.') + return [] + + if path.is_dir(): + # Fallback to file source + return File(self.vim).gather_candidates(context, path) + + candidates = [] + with path.open() as f: + for line in f: + entry = Path(line.rstrip('\n')) + if not entry.exists(): + continue + candidates.append({ + 'word': str(entry) + ( + '/' if safe_call(entry.is_dir, False) else ''), + 'is_directory': safe_call(entry.is_dir, False), + 'action__path': entry, + }) + return candidates diff --git a/bundle/defx.nvim/rplugin/python3/defx/util.py b/bundle/defx.nvim/rplugin/python3/defx/util.py index ecf57604d..8943ac548 100644 --- a/bundle/defx.nvim/rplugin/python3/defx/util.py +++ b/bundle/defx.nvim/rplugin/python3/defx/util.py @@ -4,11 +4,13 @@ # License: MIT license # ============================================================================ -import importlib.util -import os -import typing from pathlib import Path from pynvim import Nvim +from sys import executable, base_exec_prefix +import importlib.util +import os +import shutil +import typing UserContext = typing.Dict[str, typing.Any] Candidate = typing.Dict[str, typing.Any] @@ -39,7 +41,9 @@ def error(vim: Nvim, expr: typing.Any) -> None: """ Prints the error messages to Vim/Nvim's :messages buffer. """ - vim.call('defx#util#print_error', expr) + if isinstance(expr, set): + expr = [str(x) for x in expr] + vim.call('defx#util#print_error', str(expr)) def confirm(vim: Nvim, question: str) -> bool: @@ -88,3 +92,34 @@ def safe_call(fn: typing.Callable[..., typing.Any], return fn() except OSError: return fallback + + +def get_python_exe() -> str: + if 'py' in str(Path(executable).name): + return executable + + for exe in ['python3', 'python']: + which = shutil.which(exe) + if which is not None: + return which + + for name in (Path(base_exec_prefix).joinpath(v) for v in [ + 'python3', 'python', + str(Path('bin').joinpath('python3')), + str(Path('bin').joinpath('python')), + ]): + if name.exists(): + return str(name) + + # return sys.executable anyway. This may not work on windows + return executable + + +def strwidth(vim: Nvim, word: str) -> int: + return (int(vim.call('strwidth', word)) + if len(word) != len(bytes(word, 'utf-8', + 'surrogatepass')) else len(word)) + + +def len_bytes(word: str) -> int: + return len(bytes(word, 'utf-8', 'surrogatepass')) diff --git a/bundle/defx.nvim/rplugin/python3/defx/view.py b/bundle/defx.nvim/rplugin/python3/defx/view.py index f77a5dfae..5b0e19e6b 100644 --- a/bundle/defx.nvim/rplugin/python3/defx/view.py +++ b/bundle/defx.nvim/rplugin/python3/defx/view.py @@ -14,7 +14,10 @@ from defx.clipboard import Clipboard from defx.context import Context from defx.defx import Defx from defx.session import Session -from defx.util import error, import_plugin, safe_call, Nvim +from defx.util import error, import_plugin, safe_call, len_bytes +from defx.util import Nvim, Candidate + +Highlights = typing.List[typing.Tuple[str, int, int]] class View(object): @@ -34,26 +37,65 @@ class View(object): self._prev_syntaxes: typing.List[str] = [] self._prev_highlight_commands: typing.List[str] = [] self._winrestcmd = '' + self._has_preview_window = False self._session_version = '1.0' self._sessions: typing.Dict[str, Session] = {} + self._previewed_target: typing.Optional[Candidate] = None + self._previewed_job: typing.Optional[int] = None + self._ns: int = -1 + self._has_textprop = False + self._proptypes: typing.Set[str] = set() - def init(self, paths: typing.List[str], - context: typing.Dict[str, typing.Any], - clipboard: Clipboard - ) -> None: + def init(self, context: typing.Dict[str, typing.Any]) -> None: self._context = self._init_context(context) self._bufname = f'[defx] {self._context.buffer_name}-{self._index}' self._winrestcmd = self._vim.call('winrestcmd') self._prev_wininfo = self._get_wininfo() self._prev_bufnr = self._context.prev_bufnr + self._has_preview_window = len( + [x for x in range(1, self._vim.call('winnr', '$')) + if self._vim.call('getwinvar', x, '&previewwindow')]) > 0 - if not self._init_defx(paths, clipboard): - # Skipped initialize - self._winid = self._vim.call('win_getid') - if paths and self._vim.call('bufnr', '%') == self._bufnr: - self._update_defx(paths) - self._init_columns(self._context.columns.split(':')) - self.redraw(True) + if self._vim.call('defx#util#has_textprop'): + self._has_textprop = True + else: + self._ns = self._vim.call('nvim_create_namespace', 'defx') + + def init_paths(self, paths: typing.List[typing.List[str]], + context: typing.Dict[str, typing.Any], + clipboard: Clipboard + ) -> bool: + self.init(context) + + initialized = self._init_defx(clipboard) + + # Window check + if self._vim.call('win_getid') != self._winid: + # Not defx window + return False + + if not paths: + if not initialized: + # Don't initialize path + return False + paths = [['file', self._vim.call('getcwd')]] + + self._buffer.vars['defx']['paths'] = paths + self._update_defx_paths(paths) + + self._init_columns(self._context.columns.split(':')) + self.redraw(True) + + if self._context.session_file: + self.do_action('load_session', [], + self._vim.call('defx#init#_context', {})) + for [index, [source_name, path]] in enumerate(paths): + self._check_session(index, path) + + for defx in self._defxs: + self._init_cursor(defx) + + return True def do_action(self, action_name: str, action_args: typing.List[str], @@ -93,7 +135,19 @@ class View(object): def print_msg(self, expr: typing.Any) -> None: self._vim.call('defx#util#print_message', expr) + def close_preview(self) -> None: + if not self._has_preview_window: + self._vim.command('pclose!') + # Clear previewed buffers + for bufnr in self._vim.vars['defx#_previewed_buffers'].keys(): + if not self._vim.call('win_findbuf', bufnr): + self._vim.command('silent bdelete ' + str(bufnr)) + self._vim.vars['defx#_previewed_buffers'] = {} + def quit(self) -> None: + # Close preview window + self.close_preview() + winnr = self._vim.call('bufwinnr', self._bufnr) if winnr < 0: return @@ -120,6 +174,8 @@ class View(object): if self._get_wininfo() and self._get_wininfo() == self._prev_wininfo: self._vim.command(self._winrestcmd) + self.restore_previous_buffer() + def redraw(self, is_force: bool = False) -> None: """ Redraw defx buffer. @@ -138,10 +194,14 @@ class View(object): for column in self._columns: column.on_redraw(self, self._context) - lines = [ - self._get_columns_text(self._context, x) - for x in self._candidates - ] + lines = [] + columns_highlights = [] + for (i, candidate) in enumerate(self._candidates): + (text, highlights) = self._get_columns_text( + self._context, candidate) + lines.append(text) + columns_highlights += ([(x[0], i, x[1], x[1] + x[2]) + for x in highlights]) self._buffer.options['modifiable'] = True @@ -165,6 +225,11 @@ class View(object): if is_force: self._init_column_syntax() + # Update highlights + # Note: update_highlights() must be called after init_column_syntax() + if columns_highlights: + self._update_highlights(columns_highlights) + if self._context.profile: error(self._vim, f'redraw time = {time.time() - start}') @@ -193,7 +258,8 @@ class View(object): return pos return -1 - def cd(self, defx: Defx, path: str, cursor: int) -> None: + def cd(self, defx: Defx, source_name: str, + path: str, cursor: int) -> None: history = defx._cursor_history # Save previous cursor position @@ -202,9 +268,15 @@ class View(object): history[defx._cwd] = candidate['action__path'] global_histories = self._vim.vars['defx#_histories'] - global_histories.append(defx._cwd) + global_histories.append([defx._source.name, defx._cwd]) self._vim.vars['defx#_histories'] = global_histories + if source_name != defx._source.name: + # Replace with new defx + self._defxs[defx._index] = Defx(self._vim, self._context, + source_name, path, defx._index) + defx = self._defxs[defx._index] + defx.cd(path) self.redraw(True) @@ -227,6 +299,21 @@ class View(object): self._vim.call('cursor', [pos + 1, 1]) return True + def search_recursive(self, path: Path, index: int) -> None: + parents: typing.List[Path] = [] + tmppath: Path = path + while (self.get_candidate_pos(tmppath, index) < 0 and + tmppath.parent != path and tmppath.parent != tmppath): + tmppath = tmppath.parent + parents.append(tmppath) + + for parent in reversed(parents): + self.open_tree(parent, index, False, 0) + + self.update_candidates() + self.redraw() + self.search_file(path, index) + def update_candidates(self) -> None: # Update opened/selected state for defx in self._defxs: @@ -265,6 +352,8 @@ class View(object): if (enable_nested and len(children) == 1 and children[0]['is_directory']): # Merge child. + defx._nested_candidates.add(str(target['action__path'])) + target['action__path'] = children[0]['action__path'] target['word'] += children[0]['word'] target['is_opened_tree'] = False @@ -289,17 +378,38 @@ class View(object): target['is_opened_tree'] = False + defx = self._defxs[index] + self._remove_nested_path(defx, target['action__path']) + start = pos + 1 base_level = target['level'] end = start for candidate in self._candidates[start:]: if candidate['level'] <= base_level: break + self._remove_nested_path(defx, candidate['action__path']) end += 1 self._candidates = (self._candidates[: start] + self._candidates[end:]) + def restore_previous_buffer(self) -> None: + if not self._vim.call('buflisted', self._prev_bufnr): + return + + prev_bufname = self._vim.call('bufname', + self._context.prev_last_bufnr) + if not prev_bufname: + # ignore noname buffer + return + + self._vim.call('setreg', '#', + self._vim.call('fnameescape', prev_bufname)) + + def _remove_nested_path(self, defx: Defx, path: Path) -> None: + if str(path) in defx._nested_candidates: + defx._nested_candidates.remove(str(path)) + def _init_context( self, context: typing.Dict[str, typing.Any]) -> Context: # Convert to int @@ -309,7 +419,9 @@ class View(object): return Context(**context) - def _resize_window(self) -> None: + def _init_window(self) -> None: + self._winid = self._vim.call('win_getid') + window_options = self._vim.current.window.options if (self._context.split == 'vertical' and self._context.winwidth > 0): @@ -331,30 +443,22 @@ class View(object): self.update_candidates() self.redraw() - def _init_defx(self, - paths: typing.List[str], - clipboard: Clipboard) -> bool: + def _init_defx(self, clipboard: Clipboard) -> bool: if not self._switch_buffer(): return False self._buffer = self._vim.current.buffer self._bufnr = self._buffer.number - self._winid = self._vim.call('win_getid') - - if not paths: - paths = [self._vim.call('getcwd')] self._buffer.vars['defx'] = { 'context': self._context._asdict(), - 'paths': paths, + 'paths': [], } # Note: Have to use setlocal instead of "current.window.options" # "current.window.options" changes global value instead of local in # neovim. self._vim.command('setlocal colorcolumn=') - self._vim.command('setlocal conceallevel=2') - self._vim.command('setlocal concealcursor=nc') self._vim.command('setlocal nocursorcolumn') self._vim.command('setlocal nofoldenable') self._vim.command('setlocal foldcolumn=0') @@ -367,7 +471,7 @@ class View(object): if self._context.split == 'floating': self._vim.command('setlocal nocursorline') - self._resize_window() + self._init_window() buffer_options = self._buffer.options if not self._context.listed: @@ -397,22 +501,10 @@ class View(object): self._candidates = [] self._clipboard = clipboard self._defxs = [] - self._update_defx(paths) self._init_all_columns() self._init_columns(self._context.columns.split(':')) - self.redraw(True) - - if self._context.session_file: - self.do_action('load_session', [], - self._vim.call('defx#init#_context', {})) - for [index, path] in enumerate(paths): - self._check_session(index, path) - - for defx in self._defxs: - self._init_cursor(defx) - self._vim.vars['defx#_drives'] = self._context.drives return True @@ -421,13 +513,18 @@ class View(object): if self._context.split == 'tab': self._vim.command('tabnew') + if self._context.close: + self.quit() + return False + winnr = self._vim.call('bufwinnr', self._bufnr) if winnr > 0: self._vim.command(f'{winnr}wincmd w') if self._context.toggle: self.quit() else: - self._resize_window() + self._winid = self._vim.call('win_getid') + self._init_window() return False if (self._vim.current.buffer.options['modified'] and @@ -462,7 +559,7 @@ class View(object): ) ) if self._context.resume: - self._resize_window() + self._init_window() return False elif self._vim.call('exists', 'bufadd'): bufnr = self._vim.call('bufadd', self._bufname) @@ -519,6 +616,7 @@ class View(object): start = 1 for [index, column] in enumerate(self._columns): column.syntax_name = f'Defx_{column.name}_{index}' + column.highlight_name = f'Defx_{column.name}' if within_variable and not column.is_stop_variable: within_variable_columns.append(column) @@ -563,29 +661,31 @@ class View(object): commands.append( 'silent! syntax clear ' + syntax) + if self._proptypes: + self._clear_prop_types() + self._prev_syntaxes = [] for column in self._columns: source_highlights = column.highlight_commands() - if source_highlights: - if (not column.is_within_variable and - column.start > 0 and column.end > 0): - commands.append( - 'syntax region ' + column.syntax_name + - r' start=/\%' + str(column.start) + r'v/ end=/\%' + - str(column.end) + 'v/ keepend oneline') - self._prev_syntaxes += [column.syntax_name] + if not source_highlights: + continue - commands += source_highlights - self._prev_syntaxes += column.syntaxes() + commands += source_highlights + self._prev_syntaxes += column.syntaxes() - syntax_list = commands + [self._vim.call('execute', 'syntax list')] + syntax_list = commands + [ + self._vim.call('execute', 'syntax list'), + self._vim.call('execute', 'highlight'), + ] if syntax_list == self._prev_highlight_commands: # Skip highlights return self._execute_commands(commands) self._prev_highlight_commands = commands + [ - self._vim.call('execute', 'syntax list')] + self._vim.call('execute', 'syntax list'), + self._vim.call('execute', 'highlight'), + ] def _execute_commands(self, commands: typing.List[str]) -> None: # Note: If commands are too huge, vim.command() will fail. @@ -608,27 +708,43 @@ class View(object): candidate['_defx_index'] = defx._index self._candidates += candidates - def _get_columns_text(self, context: Context, - candidate: typing.Dict[str, typing.Any]) -> str: + def _get_columns_text(self, context: Context, candidate: Candidate + ) -> typing.Tuple[str, Highlights]: texts: typing.List[str] = [] variable_texts: typing.List[str] = [] + ret_highlights: typing.List[typing.Tuple[str, int, int]] = [] + start = 0 for column in self._columns: + column.start = start + if column.is_stop_variable: if variable_texts: variable_texts.append('') - text = column.get_with_variable_text( + (text, highlights) = column.get_with_variable_text( context, ' '.join(variable_texts), candidate) texts.append(text) + ret_highlights += highlights variable_texts = [] else: - text = column.get(context, candidate) + if column.has_get_with_highlights: + (text, highlights) = column.get_with_highlights( + context, candidate) + ret_highlights += highlights + else: + # Note: For old columns compatibility + text = column.get(context, candidate) if column.is_start_variable or column.is_within_variable: if text: variable_texts.append(text) else: texts.append(text) - return ' '.join(texts) + start = len_bytes(' '.join(texts)) + if texts: + start += 1 + if variable_texts: + start += len_bytes(' '.join(variable_texts)) + 1 + return (' '.join(texts), ret_highlights) def _update_paths(self, index: int, path: str) -> None: var_defx = self._buffer.vars['defx'] @@ -662,17 +778,57 @@ class View(object): return result - def _update_defx(self, paths: typing.List[str]) -> None: + def _update_defx_paths(self, + paths: typing.List[typing.List[str]]) -> None: self._defxs = self._defxs[:len(paths)] - for [index, path] in enumerate(paths): + for [index, [source_name, path]] in enumerate(paths): if index >= len(self._defxs): self._defxs.append( - Defx(self._vim, self._context, path, index)) + Defx(self._vim, self._context, source_name, path, index)) else: - self.cd(self._defxs[index], path, self._context.cursor) + defx = self._defxs[index] + self.cd(defx, defx._source.name, path, self._context.cursor) self._update_paths(index, path) def _check_bufnr(self, bufnr: int) -> bool: return (bool(self._vim.call('bufexists', bufnr)) and - bufnr != self._vim.call('bufnr', '%')) + bufnr != self._vim.call('bufnr', '%') and + self._vim.call('getbufvar', bufnr, '&filetype') != 'defx') + + def _clear_prop_types(self) -> None: + self._vim.call('defx#util#call_atomic', [ + ['prop_type_delete', [x]] for x in self._proptypes + ]) + self._proptypes = set() + + def _update_highlights(self, columns_highlights: typing.List[ + typing.Tuple[str, int, int, int]]) -> None: + commands = [] + if self._has_textprop: + for proptype in self._proptypes: + commands.append(['prop_remove', [{'type': proptype}]]) + + for highlight in columns_highlights: + if highlight[0] not in self._proptypes: + commands.append( + ['prop_type_add', + [highlight[0], {'highlight': highlight[0]}]] + ) + self._proptypes.add(highlight[0]) + commands.append( + ['prop_add', + [highlight[1] + 1, highlight[2] + 1, + {'end_col': highlight[3] + 1, 'type': highlight[0]}]] + ) + else: + commands.append(['nvim_buf_clear_namespace', + [self._bufnr, self._ns, 0, -1]]) + commands += [['nvim_buf_add_highlight', + [self._bufnr, self._ns, x[0], x[1], x[2], x[3]]] + for x in columns_highlights] + self._vim.call('defx#util#call_atomic', commands) + + if self._has_textprop: + # Note: redraw is needed for text props + self._vim.command('redraw') diff --git a/bundle/defx.nvim/rplugin/python3/denite/source/defx/history.py b/bundle/defx.nvim/rplugin/python3/denite/source/defx/history.py index 94e2d3dce..e781bc6e6 100644 --- a/bundle/defx.nvim/rplugin/python3/denite/source/defx/history.py +++ b/bundle/defx.nvim/rplugin/python3/denite/source/defx/history.py @@ -29,7 +29,8 @@ class Source(Base): def gather_candidates(self, context: UserContext) -> Candidates: return [{ 'word': x, - 'abbr': x + '/', - 'action__command': f"call defx#call_action('cd', ['{x}'])", + 'abbr': f'{source_name}:{x}/', + 'action__command': ('call defx#call_action' + + f"('cd', ['{source_name}', '{x}'])"), 'action__path': x, - } for x in self._histories] + } for [source_name, x] in self._histories] diff --git a/config/plugins/defx.vim b/config/plugins/defx.vim index dafdf0e82..0e47ae72e 100644 --- a/config/plugins/defx.vim +++ b/config/plugins/defx.vim @@ -147,6 +147,8 @@ function! s:defx_init() nnoremap yy defx#do_action('call', 'DefxYarkPath') nnoremap . \ defx#do_action('toggle_ignored_files') + nnoremap + \ defx#do_action('change_filtered_files') nnoremap ~ \ defx#do_action('cd') nnoremap j