예제 #1
0
def _call(view: View, defx: Defx, context: Context) -> None:
    """
    Call the function.
    """
    function = context.args[0] if context.args else None
    if not function:
        return

    dict_context = context._asdict()
    dict_context['targets'] = [str(x['action__path']) for x in context.targets]
    view._vim.call(function, dict_context)
예제 #2
0
class View(object):
    def __init__(self, vim: Nvim, index: int) -> None:
        self._vim: Nvim = vim
        self._candidates: typing.List[dict] = []
        self._selected_candidates: typing.List[int] = []
        self._clipboard = Clipboard()
        self._bufnr = -1
        self._index = index
        self._bufname = '[defx]'

    def init(self, paths: typing.List[str], context: dict,
             clipboard: Clipboard) -> None:
        self._candidates = []
        self._selected_candidates = []
        self._context = Context(**context)
        self._clipboard = clipboard

        context['fnamewidth'] = int(context['fnamewidth'])
        context['winheight'] = int(context['winheight'])
        context['winwidth'] = int(context['winwidth'])
        self._context = Context(**context)
        self._bufname = f'[defx] {self._context.buffer_name}-{self._index}'

        # Initialize defx
        self._defxs: typing.List[Defx] = []
        for [index, path] in enumerate(paths):
            self._defxs.append(Defx(self._vim, self._context, path, index))

        if not self.init_buffer():
            return

        # Initialize columns
        self._columns: typing.List[Column] = []
        self._all_columns: typing.Dict[str, Column] = {
            'mark': Mark(self._vim),
            'filename': Filename(self._vim),
            'type': Type(self._vim),
        }
        self._columns = [
            self._all_columns[x] for x in self._context.columns.split(':')
            if x in self._all_columns
        ]
        start = 1
        for column in self._columns:
            column.on_init()
            column.start = start
            length = column.length(self._context)
            column.end = start + length - 1
            column.syntax_name = 'Defx_' + column.name
            start += length

        self.init_syntax()
        self.redraw(True)

        if self._context.search:
            for defx in self._defxs:
                if self.search_tree(self._context.search, defx._index):
                    break

    def init_buffer(self) -> bool:
        if self._context.split == 'tab':
            self._vim.command('tabnew')

        winnr = self._vim.call('bufwinnr', self._bufname)
        if winnr > 0:
            self._vim.command(f'{winnr}wincmd w')
            if self._context.toggle:
                self.quit()
                return False
            return True

        # Create new buffer
        self._vim.call(
            'defx#util#execute_path', 'silent keepalt %s %s %s ' % (
                self._context.direction,
                ('vertical' if self._context.split == 'vertical' else ''),
                ('edit' if self._context.split == 'no'
                 or self._context.split == 'tab' else 'new'),
            ), self._bufname)

        window_options = self._vim.current.window.options
        window_options['list'] = False
        window_options['wrap'] = False

        if (self._context.split == 'vertical' and self._context.winwidth > 0):
            window_options['winfixwidth'] = True
            self._vim.command(f'vertical resize {self._context.winwidth}')
        elif (self._context.split == 'horizontal'
              and self._context.winheight > 0):
            window_options['winfixheight'] = True
            self._vim.command(f'resize {self._context.winheight}')

        buffer_options = self._vim.current.buffer.options
        buffer_options['buftype'] = 'nofile'
        buffer_options['swapfile'] = False
        buffer_options['modeline'] = False
        buffer_options['filetype'] = 'defx'
        buffer_options['modifiable'] = False
        buffer_options['modified'] = False

        self._vim.current.buffer.vars['defx'] = {
            'context': self._context._asdict(),
        }

        if not self._context.listed:
            buffer_options['buflisted'] = False
            buffer_options['bufhidden'] = 'wipe'

        self._vim.command('silent doautocmd FileType defx')
        self._vim.command('autocmd! FocusGained <buffer>')
        self._vim.command('autocmd defx FocusGained <buffer> ' +
                          'call defx#_do_action("redraw", [])')
        self._bufnr = self._vim.current.buffer.number

        return True

    def init_syntax(self) -> None:
        for column in self._columns:
            self._vim.command('silent! syntax clear ' + column.syntax_name)
            self._vim.command('syntax region ' + column.syntax_name +
                              ' start=/\%' + str(column.start) + 'v/ end=/\%' +
                              str(column.end) + 'v/ keepend oneline')
            column.highlight()

    def debug(self, expr: typing.Any) -> None:
        error(self._vim, expr)

    def print_msg(self, expr: typing.Any) -> None:
        self._vim.call('defx#util#print_message', expr)

    def quit(self) -> None:
        if self._vim.call('winnr', '$') != 1:
            self._vim.command('close')
        else:
            self._vim.command('enew')

    def init_candidates(self) -> None:
        self._candidates = []
        for defx in self._defxs:
            candidates = [defx.get_root_candidate()]
            candidates += defx.gather_candidates()
            for candidate in candidates:
                candidate['_defx_index'] = defx._index
            self._candidates += candidates

    def redraw(self, is_force: bool = False) -> None:
        """
        Redraw defx buffer.
        """

        buffer_options = self._vim.current.buffer.options
        if buffer_options['filetype'] != 'defx':
            return

        start = time.time()

        prev = self.get_cursor_candidate(self._vim.call('line', '.'))

        if is_force:
            self._selected_candidates = []
            self.init_candidates()

        is_busy = time.time() - start > 0.5

        if is_busy:
            self.print_msg('Waiting...')

        # Set is_selected flag
        for candidate in self._candidates:
            candidate['is_selected'] = False
        for index in self._selected_candidates:
            self._candidates[index]['is_selected'] = True

        buffer_options['modifiable'] = True
        self._vim.current.buffer[:] = [
            self.get_columns_text(self._context, x) for x in self._candidates
        ]
        buffer_options['modifiable'] = False
        buffer_options['modified'] = False

        if prev:
            self.search_file(prev['action__path'], prev['_defx_index'])

        if is_busy:
            self._vim.command('redraw')
            self.print_msg('Done.')

        if self._context.profile:
            error(self._vim, f'redraw time = {time.time() - start}')

    def get_columns_text(self, context: Context, candidate: dict) -> str:
        text = ''
        for column in self._columns:
            text += column.get(context, candidate)
        return text

    def get_cursor_candidate(self, cursor: int) -> dict:
        if len(self._candidates) < cursor:
            return {}
        else:
            return self._candidates[cursor - 1]

    def get_selected_candidates(self, cursor: int,
                                index: int) -> typing.List[dict]:
        if not self._selected_candidates:
            candidates = [self.get_cursor_candidate(cursor)]
        else:
            candidates = [
                self._candidates[x] for x in self._selected_candidates
            ]
        return [x for x in candidates if x['_defx_index'] == index]

    def cd(self, defx: Defx, path: str, cursor: int) -> None:
        # Save previous cursor position
        history = defx._cursor_history
        history[defx._cwd] = self.get_cursor_candidate(cursor)['action__path']

        global_histories = self._vim.vars['defx#_histories']
        global_histories.append(defx._cwd)
        self._vim.vars['defx#_histories'] = global_histories

        defx.cd(path)
        self.redraw(True)
        if path in history:
            self.search_file(history[path], defx._index)
        self._selected_candidates = []

    def search_tree(self, start: str, index: int) -> bool:
        path = Path(start)
        while True:
            if self.search_file(str(path), index):
                return True
            if path.parent == path:
                break
            path = path.parent
        return False

    def search_file(self, path: str, index: int) -> bool:
        linenr = 1
        for candidate in self._candidates:
            if (candidate['_defx_index'] == index
                    and str(candidate['action__path']) == path):
                self._vim.call('cursor', [linenr, 1])
                return True
            linenr += 1
        return False

    def do_action(self, action_name: str, action_args: typing.List[str],
                  new_context: dict) -> None:
        """
        Do "action" action.
        """
        if not self._candidates:
            return

        cursor = new_context['cursor']

        defx_targets = {
            x._index: self.get_selected_candidates(cursor, x._index)
            for x in self._defxs
        }

        import defx.action as action
        for defx in self._defxs:
            targets = defx_targets[defx._index]
            if not targets:
                continue
            context = self._context._replace(targets=targets,
                                             args=action_args,
                                             cursor=cursor)
            ret = action.do_action(self, defx, action_name, context)
            if ret:
                error(self._vim, 'Invalid action_name:' + action_name)
                return
예제 #3
0
class View(object):
    def __init__(self, vim: Nvim, index: int) -> None:
        self._vim: Nvim = vim
        self._candidates: typing.List[typing.Dict[str, typing.Any]] = []
        self._selected_candidates: typing.List[int] = []
        self._clipboard = Clipboard()
        self._bufnr = -1
        self._index = index
        self._bufname = '[defx]'
        self._buffer: Nvim.buffer = None
        self._prev_action = ''
        self._prev_highlight_commands: typing.List[str] = []

    def init(self, paths: typing.List[str], context: typing.Dict[str,
                                                                 typing.Any],
             clipboard: Clipboard) -> None:
        context['winheight'] = int(context['winheight'])
        context['winwidth'] = int(context['winwidth'])
        context['prev_bufnr'] = int(context['prev_bufnr'])
        context['prev_winid'] = int(context['prev_winid'])
        self._context = Context(**context)
        self._bufname = f'[defx] {self._context.buffer_name}-{self._index}'

        if not self.init_buffer(paths):
            return

        self._candidates = []
        self._selected_candidates = []
        self._context = Context(**context)
        self._clipboard = clipboard

        # Initialize defx
        self._defxs: typing.List[Defx] = []
        self._buffer.vars['defx']['paths'] = paths
        for [index, path] in enumerate(paths):
            self._defxs.append(Defx(self._vim, self._context, path, index))

        self.init_columns(self._context.columns.split(':'))

        for defx in self._defxs:
            if self._context.search:
                self.search_tree(self._context.search, defx._index)
            else:
                self.init_cursor(defx)

    def init_columns(self, columns: typing.List[str]) -> None:
        # Initialize columns
        self._columns: typing.List[Column] = []
        self._all_columns: typing.Dict[str, Column] = {}

        for path_column in self.load_custom_columns():
            column = import_plugin(path_column, 'column', 'Column')
            if not column:
                continue

            column = column(self._vim)
            if column.name not in self._all_columns:
                self._all_columns[column.name] = column

        custom = self._vim.call('defx#custom#_get')['column']
        self._columns = [
            self._all_columns[x] for x in columns if x in self._all_columns
        ]
        for column in self._columns:
            if column.name in custom:
                column.vars.update(custom[column.name])
            column.on_init(self._context)
            column.syntax_name = 'Defx_' + column.name

    def init_buffer(self, paths: typing.List[str]) -> bool:
        if self._context.split == 'tab':
            self._vim.command('tabnew')

        winnr = self._vim.call('bufwinnr', self._bufname)
        if winnr > 0:
            self._vim.command(f'{winnr}wincmd w')
            if self._context.toggle:
                self.quit()
                return False
            return True

        if (self._vim.current.buffer.options['modified']
                and not self._vim.options['hidden']
                and self._context.split == 'no'):
            self._context = self._context._replace(split='vertical')

        # Create new buffer
        vertical = 'vertical' if self._context.split == 'vertical' else ''
        if self._vim.call('bufexists', self._bufnr):
            command = ('buffer'
                       if self._context.split in ['no', 'tab'] else 'sbuffer')
            self._vim.command('silent keepalt %s %s %s %s' % (
                self._context.direction,
                vertical,
                command,
                self._bufnr,
            ))
            if self._context.resume:
                return False
        else:
            command = ('edit'
                       if self._context.split in ['no', 'tab'] else 'new')
            self._vim.call(
                'defx#util#execute_path', 'silent keepalt %s %s %s ' % (
                    self._context.direction,
                    vertical,
                    command,
                ), self._bufname)

        self._buffer = self._vim.current.buffer
        self._bufnr = self._buffer.number

        window_options = self._vim.current.window.options
        window_options['list'] = False
        window_options['wrap'] = False

        if (self._context.split == 'vertical' and self._context.winwidth > 0):
            window_options['winfixwidth'] = True
            self._vim.command(f'vertical resize {self._context.winwidth}')
        elif (self._context.split == 'horizontal'
              and self._context.winheight > 0):
            window_options['winfixheight'] = True
            self._vim.command(f'resize {self._context.winheight}')

        buffer_options = self._buffer.options
        buffer_options['buftype'] = 'nofile'
        buffer_options['swapfile'] = False
        buffer_options['modeline'] = False
        buffer_options['filetype'] = 'defx'
        buffer_options['modifiable'] = False
        buffer_options['modified'] = False

        self._buffer.vars['defx'] = {
            'context': self._context._asdict(),
            'paths': paths,
        }

        if not self._context.listed:
            buffer_options['buflisted'] = False
            buffer_options['bufhidden'] = 'wipe'

        self.execute_commands([
            'silent doautocmd FileType defx',
            'autocmd! defx * <buffer>',
        ])
        self._vim.command('autocmd defx '
                          'CursorHold,WinEnter,FocusGained <buffer> '
                          'call defx#call_async_action("check_redraw")')

        self._prev_highlight_commands = []

        return True

    def init_length(self) -> None:
        start = 1
        for column in self._columns:
            column.start = start
            length = column.length(
                self._context._replace(targets=self._candidates))
            column.end = start + length
            start += length + 1

    def update_syntax(self) -> None:
        highlight_commands: typing.List[str] = []
        for column in self._columns:
            highlight_commands += column.highlight_commands()

        if highlight_commands == self._prev_highlight_commands:
            # Skip highlights
            return

        self._prev_highlight_commands = highlight_commands

        commands: typing.List[str] = []
        for column in self._columns:
            commands.append('silent! syntax clear ' + column.syntax_name)
            for syntax in column.syntaxes():
                commands.append('silent! syntax clear ' + syntax)
            commands.append('syntax region ' + column.syntax_name +
                            r' start=/\%' + str(column.start) + r'v/ end=/\%' +
                            str(column.end) + 'v/ keepend oneline')
        commands += highlight_commands

        self.execute_commands(commands)

    def debug(self, expr: typing.Any) -> None:
        error(self._vim, expr)

    def print_msg(self, expr: typing.Any) -> None:
        self._vim.call('defx#util#print_message', expr)

    def execute_commands(self, commands: typing.List[str]) -> None:
        self._vim.command(' | '.join(commands))

    def quit(self) -> None:
        winnr = self._vim.call('bufwinnr', self._bufname)
        if winnr < 0:
            return

        self._vim.command(f'{winnr}wincmd w')

        if self._context.split in ['no', 'tab']:
            if self._vim.call('bufexists', self._context.prev_bufnr):
                self._vim.command('buffer ' + str(self._context.prev_bufnr))
            else:
                self._vim.command('enew')
        else:
            if self._vim.call('winnr', '$') != 1:
                self._vim.command('close')
                self._vim.call('win_gotoid', self._context.prev_winid)
            else:
                self._vim.command('enew')

    def init_candidates(self) -> None:
        self._candidates = []
        for defx in self._defxs:
            root = defx.get_root_candidate()
            defx._mtime = root['action__path'].stat().st_mtime

            candidates = [root]
            candidates += defx.gather_candidates()
            for candidate in candidates:
                candidate['_defx_index'] = defx._index
            self._candidates += candidates

    def redraw(self, is_force: bool = False) -> None:
        """
        Redraw defx buffer.
        """

        if self._buffer != self._vim.current.buffer:
            return

        start = time.time()

        prev_linenr = self._vim.call('line', '.')
        prev = self.get_cursor_candidate(prev_linenr)

        if is_force:
            self._selected_candidates = []
            self.init_candidates()
            self.init_length()
            self.update_syntax()

        # Set is_selected flag
        for candidate in self._candidates:
            candidate['is_selected'] = False
        for index in self._selected_candidates:
            self._candidates[index]['is_selected'] = True

        for column in self._columns:
            column.on_redraw(self._context)

        if self._buffer != self._vim.current.buffer:
            return

        self._buffer.options['modifiable'] = True
        self._buffer[:] = [
            self.get_columns_text(self._context, x) for x in self._candidates
        ]
        self._buffer.options['modifiable'] = False
        self._buffer.options['modified'] = False

        self._vim.call('cursor', [prev_linenr, 0])

        if prev:
            self.search_file(prev['action__path'], prev['_defx_index'])

        if self._context.profile:
            error(self._vim, f'redraw time = {time.time() - start}')

    def get_columns_text(self, context: Context,
                         candidate: typing.Dict[str, typing.Any]) -> str:
        text = ''
        for column in self._columns:
            if text:
                text += ' '
            text += column.get(context, candidate)
        return text

    def get_cursor_candidate(self,
                             cursor: int) -> typing.Dict[str, typing.Any]:
        if len(self._candidates) < cursor:
            return {}
        else:
            return self._candidates[cursor - 1]

    def get_selected_candidates(
            self, cursor: int,
            index: int) -> typing.List[typing.Dict[str, typing.Any]]:
        if not self._candidates:
            return []
        if not self._selected_candidates:
            candidates = [self.get_cursor_candidate(cursor)]
        else:
            candidates = [
                self._candidates[x] for x in self._selected_candidates
            ]
        return [x for x in candidates if x.get('_defx_index', -1) == index]

    def cd(self, defx: Defx, path: str, cursor: int) -> None:
        # Save previous cursor position
        history = defx._cursor_history
        history[defx._cwd] = self.get_cursor_candidate(cursor)['action__path']

        global_histories = self._vim.vars['defx#_histories']
        global_histories.append(defx._cwd)
        self._vim.vars['defx#_histories'] = global_histories

        defx.cd(path)
        self.redraw(True)
        self.init_cursor(defx)
        if path in history:
            self.search_file(history[path], defx._index)
        self._selected_candidates = []

        self.update_paths(defx._index, path)

    def update_paths(self, index: int, path: str) -> None:
        var_defx = self._buffer.vars['defx']
        var_defx['paths'][index] = path
        self._buffer.vars['defx'] = var_defx

    def search_tree(self, start: str, index: int) -> bool:
        path = Path(start)
        while True:
            if self.search_file(path, index):
                return True
            if path.parent == path:
                break
            path = path.parent
        return False

    def search_file(self, path: Path, index: int) -> bool:
        linenr = 1
        target = str(path)
        if target and target[-1] == '/':
            target = target[:-1]
        for candidate in self._candidates:
            if (candidate['_defx_index'] == index
                    and str(candidate['action__path']) == target):
                self._vim.call('cursor', [linenr, 1])
                return True
            linenr += 1
        return False

    def init_cursor(self, defx: Defx) -> None:
        self.search_file(Path(defx._cwd), defx._index)

        # Move to next
        self._vim.call('cursor', [self._vim.call('line', '.') + 1, 1])

    def do_action(self, action_name: str, action_args: typing.List[str],
                  new_context: typing.Dict[str, typing.Any]) -> None:
        """
        Do "action" action.
        """
        cursor = new_context['cursor']

        defx_targets = {
            x._index: self.get_selected_candidates(cursor, x._index)
            for x in self._defxs
        }
        all_targets: typing.List[typing.Dict[str, typing.Any]] = []
        for targets in defx_targets.values():
            all_targets += targets

        import defx.action as action
        for defx in [
                x for x in self._defxs
                if not all_targets or defx_targets[x._index]
        ]:
            context = self._context._replace(targets=defx_targets[defx._index],
                                             args=action_args,
                                             cursor=cursor)
            ret = action.do_action(self, defx, action_name, context)
            if ret:
                error(self._vim, 'Invalid action_name:' + action_name)
                return

    def load_custom_columns(self) -> typing.List[Path]:
        rtp_list = self._vim.options['runtimepath'].split(',')
        result: typing.List[Path] = []

        for path in rtp_list:
            column_path = Path(path).joinpath('rplugin', 'python3', 'defx',
                                              'column')
            if safe_call(column_path.is_dir):
                result += column_path.glob('*.py')

        return result