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

482 lines
14 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
# ============================================================================
import copy
import importlib
from pathlib import Path
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
from defx.util import cd, cwd_input, confirm, error
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:
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:
ret = Path(view._vim.call('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:
ret = Path(str(dest) + '_')
return ret
@action(name='cd')
def _cd(view: View, defx: Defx, context: Context) -> None:
"""
Change the current directory.
"""
path = Path(context.args[0]) if context.args else 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')
return
prev_cwd = defx._cwd
view.cd(defx, str(path), context.cursor)
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.
"""
cwd = view._vim.call('getcwd')
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)
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')
try:
path = path.relative_to(cwd)
except ValueError:
pass
view._vim.call('defx#util#execute_path', command, str(path))
@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)
view.search_file(filename, defx._index)
@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)
view.search_file(filename, defx._index)
@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)
view.search_file(filename, defx._index)
@action(name='open')
def _open(view: View, defx: Defx, context: Context) -> None:
"""
Open the file.
"""
cwd = view._vim.call('getcwd')
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)
continue
try:
path = path.relative_to(cwd)
except ValueError:
pass
view._vim.call('defx#util#execute_path', command, str(path))
@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():
view.cd(defx, str(path), context.cursor)
@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
if path == dest:
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:
shutil.move(str(path), cwd)
view._vim.command('redraw')
view._vim.command('echo')
view.redraw(True)
if dest:
view.search_file(dest, defx._index)
@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()
@action(name='remove_trash')
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']))
view.redraw(True)
@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
old.rename(new)
view.redraw(True)
view.search_file(new, defx._index)