示例#1
0
    def Matches(self, comp):
        # type: (Api) -> Iterator[Union[Iterator, Iterator[str]]]
        to_complete = comp.to_complete

        # Problem: .. and ../.. don't complete /.
        # TODO: Set display_pos before fixing this.

        #import os
        #to_complete = os.path.normpath(to_complete)

        dirname, basename = os_path.split(to_complete)
        if dirname == '':  # We're completing in this directory
            to_list = '.'
        else:  # We're completing in some other directory
            to_list = dirname

        if 0:
            log('basename %r', basename)
            log('to_list %r', to_list)
            log('dirname %r', dirname)

        try:
            names = posix.listdir(to_list)
        except OSError as e:
            return  # nothing

        for name in names:
            path = os_path.join(dirname, name)

            if path.startswith(to_complete):
                if self.dirs_only:  # add_slash not used here
                    # NOTE: There is a duplicate isdir() check later to add a trailing
                    # slash.  Consolidate the checks for fewer stat() ops.  This is hard
                    # because all the completion actions must obey the same interface.
                    # We could have another type like candidate = File | Dir |
                    # OtherString ?
                    if path_stat.isdir(path):
                        yield path
                    continue

                if self.exec_only:
                    # TODO: Handle exception if file gets deleted in between listing and
                    # check?
                    if not posix.access(path, posix.X_OK_):
                        continue

                if self.add_slash and path_stat.isdir(path):
                    yield path + '/'
                else:
                    yield path
示例#2
0
    def Matches(self, comp):
        to_complete = comp.to_complete
        i = to_complete.rfind('/')
        if i == -1:  # it looks like 'foo'
            to_list = '.'
            base = ''
        elif i == 0:  # it's an absolute path to_complete like / or /b
            to_list = '/'
            base = '/'
        else:
            to_list = to_complete[:i]
            base = to_list
            #log('to_list %r', to_list)

        try:
            names = posix.listdir(to_list)
        except OSError as e:
            return  # nothing

        for name in names:
            path = os_path.join(base, name)
            if path.startswith(to_complete):
                if self.dirs_only:  # add_slash not used here
                    # NOTE: There is a duplicate isdir() check later to add a trailing
                    # slash.  Consolidate the checks for fewer stat() ops.  This is hard
                    # because all the completion actions must obey the same interface.
                    # We could have another type like candidate = File | Dir |
                    # OtherString ?
                    if path_stat.isdir(path):
                        yield path
                    continue

                if self.exec_only:
                    # TODO: Handle exception if file gets deleted in between listing and
                    # check?
                    if not posix.access(path, posix.X_OK):
                        continue

                if self.add_slash and path_stat.isdir(path):
                    yield path + '/'
                else:
                    yield path
示例#3
0
    def _PostProcess(
            self,
            base_opts,  # type: Dict
            dynamic_opts,  # type: Dict
            user_spec,  # type: UserSpec
            comp,  # type: Api
    ):
        # type: (...) -> Iterator[Union[Iterator, Iterator[str]]]
        """
    Add trailing spaces / slashes to completion candidates, and time them.

    NOTE: This post-processing MUST go here, and not in UserSpec, because it's
    in READLINE in bash.  compgen doesn't see it.
    """
        self.debug_f.log('Completing %r ... (Ctrl-C to cancel)', comp.line)
        start_time = time.time()

        # TODO: dedupe candidates?  You can get two 'echo' in bash, which is dumb.

        i = 0
        for candidate, is_fs_action in user_spec.Matches(comp):
            # SUBTLE: dynamic_opts is part of compopt_state, which ShellFuncAction
            # can mutate!  So we don't want to pull this out of the loop.
            #
            # TODO: The candidates from each actions shouldn't be flattened.
            # for action in user_spec.Actions():
            #   if action.IsFileSystem():  # this returns is_dir too
            #
            #   action.Run()  # might set dynamic opts
            #   opt_nospace = base_opts...
            #   if 'nospace' in dynamic_opts:
            #     opt_nosspace = dynamic_opts['nospace']
            #   for candidate in action.Matches():
            #     add space or /
            #     and do escaping too
            #
            # Or maybe you can request them on demand?  Most actions are EAGER.
            # While the ShellacAction is LAZY?  And you should be able to cancel it!

            # NOTE: User-defined plugins (and the -P flag) can REWRITE what the user
            # already typed.  So
            #
            # $ echo 'dir with spaces'/f<TAB>
            #
            # can be rewritten to:
            #
            # $ echo dir\ with\ spaces/foo
            line_until_tab = self.comp_ui_state.line_until_tab
            line_until_word = line_until_tab[:self.comp_ui_state.display_pos]

            opt_filenames = base_opts.get('filenames', False)
            if 'filenames' in dynamic_opts:
                opt_filenames = dynamic_opts['filenames']

            # compopt -o filenames is for user-defined actions.  Or any
            # FileSystemAction needs it.
            if is_fs_action or opt_filenames:
                if path_stat.isdir(candidate):  # TODO: test coverage
                    yield line_until_word + ShellQuoteB(candidate) + '/'
                    continue

            opt_nospace = base_opts.get('nospace', False)
            if 'nospace' in dynamic_opts:
                opt_nospace = dynamic_opts['nospace']

            sp = '' if opt_nospace else ' '
            yield line_until_word + ShellQuoteB(candidate) + sp

            # NOTE: Can't use %.2f in production build!
            i += 1
            elapsed_ms = (time.time() - start_time) * 1000.0
            plural = '' if i == 1 else 'es'

            # TODO: Show this in the UI if it takes too long!
            if 0:
                self.debug_f.log(
                    '... %d match%s for %r in %d ms (Ctrl-C to cancel)', i,
                    plural, comp.line, elapsed_ms)

        elapsed_ms = (time.time() - start_time) * 1000.0
        plural = '' if i == 1 else 'es'
        self.debug_f.log('Found %d match%s for %r in %d ms', i, plural,
                         comp.line, elapsed_ms)
示例#4
0
    def _PostProcess(self, base_opts, dynamic_opts, user_spec, comp):
        """
    Add trailing spaces / slashes to completion candidates, and time them.

    NOTE: This post-processing MUST go here, and not in UserSpec, because it's
    in READLINE in bash.  compgen doesn't see it.
    """
        self.progress_f.Write('Completing %r ... (Ctrl-C to cancel)',
                              comp.line)
        start_time = time.time()

        # TODO: dedupe candidates?  You can get two 'echo' in bash, which is dumb.

        i = 0
        for m, is_fs_action in user_spec.Matches(comp):

            # - Do shell QUOTING here. Not just for filenames, but for everything!
            #   User-defined functions can't emit $var, only \$var
            # Problem: COMP_WORDBREAKS messes things up!  How can I account for that?
            # '_tmp/spam\ '
            # it stops at the first ' ' char.
            #
            # I guess you can add a COMP_WORDBREAKS suffix?
            # Or should you get rid of completion_delims altogether?
            # Then you would be constantly completing the beginning of the line?
            # TODO: write a terminal program to show that

            #m = util.BackslashEscape(m, SHELL_META_CHARS)
            #self.debug_f.log('after shell escaping: %s', m)

            # SUBTLE: dynamic_opts is part of comp_state, which ShellFuncAction can
            # mutate!  So we don't want to pull this out of the loop.
            opt_filenames = False
            if 'filenames' in dynamic_opts:
                opt_filenames = dynamic_opts['filenames']
            if 'filenames' in base_opts:
                opt_filenames = base_opts['filenames']

            # compopt -o filenames is for user-defined actions.  Or any
            # FileSystemAction needs it.
            if is_fs_action or opt_filenames:
                if path_stat.isdir(m):  # TODO: test coverage
                    yield m + '/'
                    continue

            opt_nospace = False
            if 'nospace' in dynamic_opts:
                opt_nospace = dynamic_opts['nospace']
            if 'nospace' in base_opts:
                opt_nospace = base_opts['nospace']

            if opt_nospace:
                yield m
            else:
                yield m + ' '

            # NOTE: Can't use %.2f in production build!
            i += 1
            elapsed_ms = (time.time() - start_time) * 1000.0
            plural = '' if i == 1 else 'es'
            self.progress_f.Write(
                '... %d match%s for %r in %d ms (Ctrl-C to cancel)', i, plural,
                comp.line, elapsed_ms)

        elapsed_ms = (time.time() - start_time) * 1000.0
        plural = '' if i == 1 else 'es'
        self.progress_f.Write('Found %d match%s for %r in %d ms', i, plural,
                              comp.line, elapsed_ms)