1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-23 12:50:04 +08:00

Update defx.nvim (#3803)

This commit is contained in:
Wang Shidong 2020-10-31 15:58:52 +08:00 committed by GitHub
parent 15a511c32f
commit af773b6bff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1099 additions and 364 deletions

View File

@ -1,5 +1,8 @@
## Forked repos
- [defx.nvim](https://github.com/Shougo/defx.nvim/commit/df5e6ea6734dc002919ea41786668069fa0b497d)
### checkers layer
- neomake

View File

@ -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/
```

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)
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 new_file !~# '^\%(\a\a\+:\)\|^\%(\a:\|/\)'
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
endif
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

View File

@ -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

View File

@ -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('\%(\\\@<!\s\)\+'))
let arg = substitute(s, '\\\( \)', '\1', 'g')
let arg_key = substitute(arg, '=\zs.*$', '', '')
let s = substitute(s, '\\\( \)', '\1', 'g')
let splits = split(s, '\a\a\+\zs:')
if len(splits) == 1
let source_name = 'file'
let source_arg = s
else
let source_name = splits[0]
let source_arg = join(splits[1:], ':')
endif
let arg_key = substitute(s, '=\zs.*$', '', '')
let name = substitute(tr(arg_key, '-', '_'), '=$', '', '')[1:]
if name =~# '^no_'
@ -136,13 +147,13 @@ function! s:parse_options(cmdline) abort
let value = v:false
else
let value = (arg_key =~# '=$') ?
\ s:remove_quote_pairs(arg[len(arg_key) :]) : v:true
\ s:remove_quote_pairs(s[len(arg_key) :]) : v:true
endif
if index(keys(defx#init#_user_options()), name) >= 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

View File

@ -1,6 +1,6 @@
*defx.txt* Dark powered file explorer for neovim/Vim8.
Version: 1.0
Version: 2.0
Author: Shougo <Shougo.Matsu at gmail.com>
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 <silent><buffer><expr> E
\ defx#do_action('open', 'vsplit')
nnoremap <silent><buffer><expr> P
\ defx#do_action('open', 'pedit')
\ defx#do_action('preview')
nnoremap <silent><buffer><expr> o
\ defx#do_action('open_tree', 'toggle')
nnoremap <silent><buffer><expr> K
@ -805,6 +876,12 @@ Q: I want to open file by double click.
A: >
nnoremap <silent><buffer><expr> <2-LeftMouse> defx#do_action('open')
Q: I want to separate defx state by tabs.
A: >
Defx -buffer-name=`'defx' . tabpagenr()`
==============================================================================
COMPATIBILITY *defx-compatibility*

View File

@ -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:

View File

@ -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:

View File

@ -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')):

View File

@ -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'))):

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:
if not self._opened_candidates and not self._nested_candidates:
return gathered_candidates
candidates = []
for candidate in gathered_candidates:
candidates.append(candidate)
candidate['level'] = base_level
candidate_path = str(candidate['action__path'])
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
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

View File

@ -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,7 +72,9 @@ 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),
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
@ -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')
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
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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -51,6 +51,7 @@ class Source(Base):
if not readable(path) or not path.is_dir():
error(self.vim, f'"{path}" is not readable directory.')
return []
try:
for entry in path.iterdir():
candidates.append({
'word': entry.name.replace('\n', '\\n') + (
@ -58,4 +59,6 @@ class Source(Base):
'is_directory': safe_call(entry.is_dir, False),
'action__path': entry,
})
except OSError:
pass
return candidates

View File

@ -0,0 +1,71 @@
# ============================================================================
# FILE: file/list.py
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
# 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

View File

@ -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'))

View File

@ -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,27 +37,66 @@ 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 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)
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._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],
new_context: typing.Dict[str, typing.Any]) -> None:
@ -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()
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:
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')

View File

@ -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]

View File

@ -147,6 +147,8 @@ function! s:defx_init()
nnoremap <silent><buffer><expr> yy defx#do_action('call', 'DefxYarkPath')
nnoremap <silent><buffer><expr> .
\ defx#do_action('toggle_ignored_files')
nnoremap <silent><buffer><expr> <C-f>
\ defx#do_action('change_filtered_files')
nnoremap <silent><buffer><expr> ~
\ defx#do_action('cd')
nnoremap <silent><buffer><expr> j