1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-02-03 11:00:05 +08:00
SpaceVim/bundle/defx.nvim/rplugin/python3/defx/kind/file.py

600 lines
18 KiB
Python
Raw Normal View History

2020-06-13 14:06:35 +08:00
# ============================================================================
# FILE: file.py
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
# License: MIT license
# ============================================================================
2020-10-31 15:58:52 +08:00
from pathlib import Path
2020-06-13 14:06:35 +08:00
import copy
import importlib
2020-10-31 15:58:52 +08:00
import mimetypes
2020-06-13 14:06:35 +08:00
import shutil
import time
import typing
from defx.action import ActionAttr
from defx.action import ActionTable
from defx.base.kind import Base
from defx.clipboard import ClipboardAction
from defx.context import Context
from defx.defx import Defx
2020-10-31 15:58:52 +08:00
from defx.util import cd, cwd_input, confirm, error, Candidate
2020-06-13 14:06:35 +08:00
from defx.util import readable, Nvim
from defx.view import View
_action_table: typing.Dict[str, ActionTable] = {}
ACTION_FUNC = typing.Callable[[View, Defx, Context], None]
def action(name: str, attr: ActionAttr = ActionAttr.NONE
) -> typing.Callable[[ACTION_FUNC], ACTION_FUNC]:
def wrapper(func: ACTION_FUNC) -> ACTION_FUNC:
_action_table[name] = ActionTable(func=func, attr=attr)
def inner_wrapper(view: View, defx: Defx, context: Context) -> None:
return func(view, defx, context)
return inner_wrapper
return wrapper
class Kind(Base):
def __init__(self, vim: Nvim) -> None:
self.vim = vim
self.name = 'file'
def get_actions(self) -> typing.Dict[str, ActionTable]:
actions = copy.copy(super().get_actions())
actions.update(_action_table)
return actions
def check_overwrite(view: View, dest: Path, src: Path) -> Path:
2020-10-31 15:58:52 +08:00
if not src.exists() or not dest.exists():
return Path('')
2020-06-13 14:06:35 +08:00
s_stat = src.stat()
s_mtime = s_stat.st_mtime
view.print_msg(f' src: {src} {s_stat.st_size} bytes')
view.print_msg(f' {time.strftime("%c", time.localtime(s_mtime))}')
d_stat = dest.stat()
d_mtime = d_stat.st_mtime
view.print_msg(f'dest: {dest} {d_stat.st_size} bytes')
view.print_msg(f' {time.strftime("%c", time.localtime(d_mtime))}')
choice: int = view._vim.call('defx#util#confirm',
f'{dest} already exists. Overwrite?',
'&Force\n&No\n&Rename\n&Time\n&Underbar', 0)
ret: Path = Path('')
if choice == 1:
ret = dest
elif choice == 2:
ret = Path('')
elif choice == 3:
2020-10-31 15:58:52 +08:00
ret = Path(view._vim.call(
'defx#util#input',
f'{src} -> ', str(dest),
('dir' if src.is_dir() else 'file')))
2020-06-13 14:06:35 +08:00
elif choice == 4 and d_mtime < s_mtime:
ret = src
elif choice == 5:
ret = Path(str(dest) + '_')
return ret
@action(name='cd')
def _cd(view: View, defx: Defx, context: Context) -> None:
"""
Change the current directory.
"""
2020-10-31 15:58:52 +08:00
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()
2020-06-13 14:06:35 +08:00
path = Path(defx._cwd).joinpath(path).resolve()
2020-10-31 15:58:52 +08:00
if not readable(path) or (source_name == 'file' and not path.is_dir()):
error(view._vim, f'{path} is invalid.')
2020-06-13 14:06:35 +08:00
return
prev_cwd = defx._cwd
2020-10-31 15:58:52 +08:00
view.cd(defx, source_name, str(path), context.cursor)
2020-06-13 14:06:35 +08:00
if context.args and context.args[0] == '..':
view.search_file(Path(prev_cwd), defx._index)
@action(name='change_vim_cwd', attr=ActionAttr.NO_TAGETS)
def _change_vim_cwd(view: View, defx: Defx, context: Context) -> None:
"""
Change the current working directory.
"""
cd(view._vim, defx._cwd)
@action(name='check_redraw', attr=ActionAttr.NO_TAGETS)
def _check_redraw(view: View, defx: Defx, context: Context) -> None:
root = defx.get_root_candidate()['action__path']
if not root.exists():
return
mtime = root.stat().st_mtime
if mtime != defx._mtime:
view.redraw(True)
@action(name='copy')
def _copy(view: View, defx: Defx, context: Context) -> None:
if not context.targets:
return
message = 'Copy to the clipboard: {}'.format(
str(context.targets[0]['action__path'])
if len(context.targets) == 1
else str(len(context.targets)) + ' files')
view.print_msg(message)
view._clipboard.action = ClipboardAction.COPY
view._clipboard.candidates = context.targets
@action(name='drop')
def _drop(view: View, defx: Defx, context: Context) -> None:
"""
Open like :drop.
"""
2020-10-31 15:58:52 +08:00
cwd = view._vim.call('getcwd', -1)
2020-06-13 14:06:35 +08:00
command = context.args[0] if context.args else 'edit'
for target in context.targets:
path = target['action__path']
if path.is_dir():
2020-10-31 15:58:52 +08:00
view.cd(defx, defx._source.name, str(path), context.cursor)
2020-06-13 14:06:35 +08:00
continue
bufnr = view._vim.call('bufnr', f'^{path}$')
winids = view._vim.call('win_findbuf', bufnr)
if winids:
view._vim.call('win_gotoid', winids[0])
else:
if context.prev_winid != view._winid and view._vim.call(
'win_id2win', context.prev_winid):
view._vim.call('win_gotoid', context.prev_winid)
else:
view._vim.command('wincmd w')
2020-10-31 15:58:52 +08:00
if not view._vim.call('haslocaldir'):
try:
path = path.relative_to(cwd)
except ValueError:
pass
2020-06-13 14:06:35 +08:00
view._vim.call('defx#util#execute_path', command, str(path))
2020-10-31 15:58:52 +08:00
view.restore_previous_buffer()
view.close_preview()
2020-06-13 14:06:35 +08:00
@action(name='execute_command', attr=ActionAttr.NO_TAGETS)
def _execute_command(view: View, defx: Defx, context: Context) -> None:
"""
Execute the command.
"""
save_cwd = view._vim.call('getcwd')
cd(view._vim, defx._cwd)
command = context.args[0] if context.args else view._vim.call(
'input', 'Command: ', '', 'shellcmd')
output = view._vim.call('system', command)
if output:
view.print_msg(output)
cd(view._vim, save_cwd)
@action(name='execute_system')
def _execute_system(view: View, defx: Defx, context: Context) -> None:
"""
Execute the file by system associated command.
"""
for target in context.targets:
view._vim.call('defx#util#open', str(target['action__path']))
@action(name='move')
def _move(view: View, defx: Defx, context: Context) -> None:
if not context.targets:
return
message = 'Move to the clipboard: {}'.format(
str(context.targets[0]['action__path'])
if len(context.targets) == 1
else str(len(context.targets)) + ' files')
view.print_msg(message)
view._clipboard.action = ClipboardAction.MOVE
view._clipboard.candidates = context.targets
@action(name='new_directory')
def _new_directory(view: View, defx: Defx, context: Context) -> None:
"""
Create a new directory.
"""
candidate = view.get_cursor_candidate(context.cursor)
if not candidate:
return
if candidate['is_opened_tree'] or candidate['is_root']:
cwd = str(candidate['action__path'])
else:
cwd = str(Path(candidate['action__path']).parent)
new_filename = cwd_input(
view._vim, cwd,
'Please input a new directory name: ', '', 'file')
if not new_filename:
return
filename = Path(cwd).joinpath(new_filename)
if not filename:
return
if filename.exists():
error(view._vim, f'{filename} already exists')
return
filename.mkdir(parents=True)
view.redraw(True)
2020-10-31 15:58:52 +08:00
view.search_recursive(filename, defx._index)
2020-06-13 14:06:35 +08:00
@action(name='new_file')
def _new_file(view: View, defx: Defx, context: Context) -> None:
"""
Create a new file and it's parent directories.
"""
candidate = view.get_cursor_candidate(context.cursor)
if not candidate:
return
if candidate['is_opened_tree'] or candidate['is_root']:
cwd = str(candidate['action__path'])
else:
cwd = str(Path(candidate['action__path']).parent)
new_filename = cwd_input(
view._vim, cwd,
'Please input a new filename: ', '', 'file')
if not new_filename:
return
isdir = new_filename[-1] == '/'
filename = Path(cwd).joinpath(new_filename)
if not filename:
return
if filename.exists():
error(view._vim, f'{filename} already exists')
return
if isdir:
filename.mkdir(parents=True)
else:
filename.parent.mkdir(parents=True, exist_ok=True)
filename.touch()
view.redraw(True)
2020-10-31 15:58:52 +08:00
view.search_recursive(filename, defx._index)
2020-06-13 14:06:35 +08:00
@action(name='new_multiple_files')
def _new_multiple_files(view: View, defx: Defx, context: Context) -> None:
"""
Create multiple files.
"""
candidate = view.get_cursor_candidate(context.cursor)
if not candidate:
return
if candidate['is_opened_tree'] or candidate['is_root']:
cwd = str(candidate['action__path'])
else:
cwd = str(Path(candidate['action__path']).parent)
save_cwd = view._vim.call('getcwd')
cd(view._vim, cwd)
str_filenames: str = view._vim.call(
'input', 'Please input new filenames: ', '', 'file')
cd(view._vim, save_cwd)
if not str_filenames:
return None
for name in str_filenames.split():
is_dir = name[-1] == '/'
filename = Path(cwd).joinpath(name)
if filename.exists():
error(view._vim, f'{filename} already exists')
continue
if is_dir:
filename.mkdir(parents=True)
else:
if not filename.parent.exists():
filename.parent.mkdir(parents=True)
filename.touch()
view.redraw(True)
2020-10-31 15:58:52 +08:00
view.search_recursive(filename, defx._index)
2020-06-13 14:06:35 +08:00
@action(name='open')
def _open(view: View, defx: Defx, context: Context) -> None:
"""
Open the file.
"""
2020-10-31 15:58:52 +08:00
cwd = view._vim.call('getcwd', -1)
2020-06-13 14:06:35 +08:00
command = context.args[0] if context.args else 'edit'
2020-10-31 15:58:52 +08:00
previewed_buffers = view._vim.vars['defx#_previewed_buffers']
2020-06-13 14:06:35 +08:00
for target in context.targets:
path = target['action__path']
if path.is_dir():
2020-10-31 15:58:52 +08:00
view.cd(defx, defx._source.name, str(path), context.cursor)
2020-06-13 14:06:35 +08:00
continue
2020-10-31 15:58:52 +08:00
if not view._vim.call('haslocaldir'):
try:
path = path.relative_to(cwd)
except ValueError:
pass
2020-06-13 14:06:35 +08:00
view._vim.call('defx#util#execute_path', command, str(path))
2020-10-31 15:58:52 +08:00
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()
2020-06-13 14:06:35 +08:00
@action(name='open_directory')
def _open_directory(view: View, defx: Defx, context: Context) -> None:
"""
Open the directory.
"""
if context.args:
path = Path(context.args[0])
else:
for target in context.targets:
path = target['action__path']
if path.is_dir():
2020-10-31 15:58:52 +08:00
view.cd(defx, 'file', str(path), context.cursor)
2020-06-13 14:06:35 +08:00
@action(name='paste', attr=ActionAttr.NO_TAGETS)
def _paste(view: View, defx: Defx, context: Context) -> None:
candidate = view.get_cursor_candidate(context.cursor)
if not candidate:
return
if candidate['is_opened_tree'] or candidate['is_root']:
cwd = str(candidate['action__path'])
else:
cwd = str(Path(candidate['action__path']).parent)
action = view._clipboard.action
dest = None
for index, candidate in enumerate(view._clipboard.candidates):
path = candidate['action__path']
dest = Path(cwd).joinpath(path.name)
if dest.exists():
overwrite = check_overwrite(view, dest, path)
if overwrite == Path(''):
continue
dest = overwrite
2020-10-31 15:58:52 +08:00
if not path.exists() or path == dest:
2020-06-13 14:06:35 +08:00
continue
view.print_msg(
f'[{index + 1}/{len(view._clipboard.candidates)}] {path}')
if action == ClipboardAction.COPY:
if path.is_dir():
shutil.copytree(str(path), dest)
else:
shutil.copy2(str(path), dest)
elif action == ClipboardAction.MOVE:
2020-10-31 15:58:52 +08:00
if dest.exists():
# Must remove dest before
if dest.is_dir():
shutil.rmtree(str(dest))
else:
dest.unlink()
2020-06-13 14:06:35 +08:00
shutil.move(str(path), cwd)
view._vim.command('redraw')
2020-10-31 15:58:52 +08:00
if action == ClipboardAction.MOVE:
# Clear clipboard after move
view._clipboard.candidates = []
2020-06-13 14:06:35 +08:00
view._vim.command('echo')
view.redraw(True)
if dest:
2020-10-31 15:58:52 +08:00
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)
2020-06-13 14:06:35 +08:00
@action(name='remove', attr=ActionAttr.REDRAW)
def _remove(view: View, defx: Defx, context: Context) -> None:
"""
Delete the file or directory.
"""
if not context.targets:
return
force = context.args[0] == 'force' if context.args else False
if not force:
message = 'Are you sure you want to delete {}?'.format(
str(context.targets[0]['action__path'])
if len(context.targets) == 1
else str(len(context.targets)) + ' files')
if not confirm(view._vim, message):
return
for target in context.targets:
path = target['action__path']
if path.is_dir():
shutil.rmtree(str(path))
else:
path.unlink()
2020-10-31 15:58:52 +08:00
view._vim.call('defx#util#buffer_delete',
view._vim.call('bufnr', str(path)))
2020-06-13 14:06:35 +08:00
2020-10-31 15:58:52 +08:00
@action(name='remove_trash', attr=ActionAttr.REDRAW)
2020-06-13 14:06:35 +08:00
def _remove_trash(view: View, defx: Defx, context: Context) -> None:
"""
Delete the file or directory.
"""
if not context.targets:
return
if not importlib.util.find_spec('send2trash'):
error(view._vim, '"Send2Trash" is not installed')
return
force = context.args[0] == 'force' if context.args else False
if not force:
message = 'Are you sure you want to delete {}?'.format(
str(context.targets[0]['action__path'])
if len(context.targets) == 1
else str(len(context.targets)) + ' files')
if not confirm(view._vim, message):
return
import send2trash
for target in context.targets:
send2trash.send2trash(str(target['action__path']))
2020-10-31 15:58:52 +08:00
view._vim.call('defx#util#buffer_delete',
view._vim.call('bufnr', str(target['action__path'])))
2020-06-13 14:06:35 +08:00
@action(name='rename')
def _rename(view: View, defx: Defx, context: Context) -> None:
"""
Rename the file or directory.
"""
if len(context.targets) > 1:
# ex rename
view._vim.call('defx#exrename#create_buffer',
[{'action__path': str(x['action__path'])}
for x in context.targets],
{'buffer_name': 'defx'})
return
for target in context.targets:
old = target['action__path']
new_filename = cwd_input(
view._vim, defx._cwd,
f'Old name: {old}\nNew name: ', str(old), 'file')
view._vim.command('redraw')
if not new_filename:
return
new = Path(defx._cwd).joinpath(new_filename)
if not new or new == old:
continue
if str(new).lower() != str(old).lower() and new.exists():
error(view._vim, f'{new} already exists')
continue
2020-10-31 15:58:52 +08:00
if not new.parent.exists():
new.parent.mkdir(parents=True)
2020-06-13 14:06:35 +08:00
old.rename(new)
2020-10-31 15:58:52 +08:00
# Check rename
view._vim.call('defx#util#buffer_rename',
view._vim.call('bufnr', str(old)), str(new))
2020-06-13 14:06:35 +08:00
view.redraw(True)
2020-10-31 15:58:52 +08:00
view.search_recursive(new, defx._index)