From 688e8a08c366472055c6519323aa07293413d988 Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Tue, 3 Mar 2015 00:52:33 -0600 Subject: [PATCH] have bash completion --- xonsh/completer.py | 51 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/xonsh/completer.py b/xonsh/completer.py index def15c8b5..04a6f6ffa 100644 --- a/xonsh/completer.py +++ b/xonsh/completer.py @@ -18,6 +18,16 @@ XONSH_TOKENS = {'and ', 'as ', 'assert ', 'break', 'class ', 'continue', '>>=', '<<=', '&=', '^=', '|=', '//=', ',', ';', ':', '?', '??', '$(', '${', '$[', '..', '...'} +BASH_COMPLETE_SCRIPT = """source {filename} +COMP_WORDS=({line}) +COMP_LINE="{line}" +COMP_POINT=${{#COMP_LINE}} +COMP_COUNT={end} +COMP_CWORD={n} +{func} {cmd} {prefix} {prev} +for ((i=0;i<${{#COMPREPLY[*]}};i++)) do echo ${{COMPREPLY[i]}}; done +""" + class Completer(object): """This provides a list of optional completions for the xonsh shell.""" @@ -54,7 +64,13 @@ class Completer(object): """ space = ' ' # intern some strings for faster appending slash = '/' - rtn = {s for s in XONSH_TOKENS if s.startswith(prefix)} + if begidx == 0: + rtn = self.cmd_complete(prefix) + elif line.split(' ', 1)[0] in self.bash_complete_funcs: + return sorted(self.bash_complete(prefix, line, begidx, endidx)) + else: + rtn = set() + rtn |= {s for s in XONSH_TOKENS if s.startswith(prefix)} if ctx is not None: rtn |= {s for s in ctx if s.startswith(prefix)} rtn |= {s for s in dir(builtins) if s.startswith(prefix)} @@ -63,20 +79,47 @@ class Completer(object): key = prefix[1:] rtn |= {'$'+k for k in builtins.__xonsh_env__ if k.startswith(key)} rtn |= {s + (slash if os.path.isdir(s) else space) for s in iglob(prefix + '*')} - if begidx == 0: - rtn |= self.cmd_complete(prefix) return sorted(rtn) def cmd_complete(self, cmd): + """Completes a command name based on what is on the $PATH""" path = builtins.__xonsh_env__.get('PATH', None) if path is None: return set() cmds = set() + space = ' ' for d in path: if os.path.isdir(d): - cmds |= {s for s in os.listdir(d) if s.startswith(cmd)} + cmds |= {s + space for s in os.listdir(d) if s.startswith(cmd)} return cmds + def bash_complete(self, prefix, line, begidx, endidx): + """Attempts BASH completion.""" + splt = line.split() + cmd = splt[0] + func = self.bash_complete_funcs.get(cmd, None) + fnme = self.bash_complete_files.get(cmd, None) + if func is None or fnme is None: + return set() + idx = 0 + for n, tok in enumerate(splt): + if tok == prefix: + idx = line.find(prefix, idx) + if idx >= begidx: + break + prev = tok + if len(prefix) == 0: + prefix = '""' + n += 1 + script = BASH_COMPLETE_SCRIPT.format(filename=fnme, line=line, n=n, + func=func, cmd=cmd, end=endidx+1, prefix=prefix, prev=prev) + out = subprocess.check_output(['bash'], input=script, + universal_newlines=True) + space = ' ' + rtn = {s+space if s[-1:].isalnum() else s for s in out.splitlines()} + return rtn + + def _load_bash_complete_funcs(self): input = 'source /etc/bash_completion\n' if os.path.isfile('/usr/share/bash-completion/completions/git'):