mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-06 09:20:57 +01:00
Loads completions from directories
also silences stderr when sourcing files closes #848
This commit is contained in:
parent
e92e293f9f
commit
27d01ecde0
1 changed files with 47 additions and 37 deletions
|
@ -5,6 +5,7 @@ import re
|
|||
import ast
|
||||
import sys
|
||||
import shlex
|
||||
from pathlib import Path
|
||||
import pickle
|
||||
import inspect
|
||||
import builtins
|
||||
|
@ -40,7 +41,7 @@ def _path_from_partial_string(inp, pos=None):
|
|||
else:
|
||||
return None
|
||||
string = partial[startix:endix]
|
||||
end = re.sub(RE_STRING_START,'',quote)
|
||||
end = re.sub(RE_STRING_START, '', quote)
|
||||
_string = string
|
||||
if not _string.endswith(end):
|
||||
_string = _string + end
|
||||
|
@ -84,6 +85,7 @@ for ((i=0;i<${{#COMPREPLY[*]}};i++)) do echo ${{COMPREPLY[i]}}; done
|
|||
|
||||
WS = set(' \t\r\n')
|
||||
|
||||
|
||||
def startswithlow(x, start, startlow=None):
|
||||
"""True if x starts with a string or its lowercase version. The lowercase
|
||||
version may be optionally be provided.
|
||||
|
@ -120,6 +122,7 @@ def _normpath(p):
|
|||
|
||||
return p
|
||||
|
||||
|
||||
class Completer(object):
|
||||
"""This provides a list of optional completions for the xonsh shell."""
|
||||
|
||||
|
@ -268,8 +271,9 @@ class Completer(object):
|
|||
return {max(i, key=len) for i in reps.values()}
|
||||
|
||||
def find_and_complete(self, line, idx, ctx=None):
|
||||
"""Finds the completions given only the full code line and a current cursor
|
||||
position. This represents an easier alternative to the complete() method.
|
||||
"""Finds the completions given only the full code line and a current
|
||||
cursor position. This represents an easier alternative to the
|
||||
complete() method.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
@ -308,8 +312,8 @@ class Completer(object):
|
|||
csc = builtins.__xonsh_env__.get('CASE_SENSITIVE_COMPLETIONS')
|
||||
startswither = startswithnorm if csc else startswithlow
|
||||
key = prefix[1:]
|
||||
keylow = key.lower()
|
||||
paths.update({'$' + k for k in builtins.__xonsh_env__ if startswither(k, key, keylow)})
|
||||
paths.update({'$' + k for k in builtins.__xonsh_env__
|
||||
if startswither(k, key, key.lower())})
|
||||
|
||||
def _add_dots(self, paths, prefix):
|
||||
if prefix in {'', '.'}:
|
||||
|
@ -353,7 +357,6 @@ class Completer(object):
|
|||
else:
|
||||
return single
|
||||
|
||||
|
||||
def _quote_paths(self, paths, start, end):
|
||||
out = set()
|
||||
space = ' '
|
||||
|
@ -386,7 +389,6 @@ class Completer(object):
|
|||
out.add(start + s + end)
|
||||
return out
|
||||
|
||||
|
||||
def path_complete(self, prefix, start, end, cdpath=False):
|
||||
"""Completes based on a path name."""
|
||||
tilde = '~'
|
||||
|
@ -425,38 +427,40 @@ class Completer(object):
|
|||
else:
|
||||
prefix = shlex.quote(prefix)
|
||||
|
||||
script = BASH_COMPLETE_SCRIPT.format(filename=fnme,
|
||||
line=' '.join(shlex.quote(p) for p in splt),
|
||||
comp_line=shlex.quote(line), n=n, func=func, cmd=cmd,
|
||||
end=endidx + 1, prefix=prefix, prev=shlex.quote(prev))
|
||||
script = BASH_COMPLETE_SCRIPT.format(
|
||||
filename=fnme, line=' '.join(shlex.quote(p) for p in splt),
|
||||
comp_line=shlex.quote(line), n=n, func=func, cmd=cmd,
|
||||
end=endidx + 1, prefix=prefix, prev=shlex.quote(prev))
|
||||
try:
|
||||
out = subprocess.check_output(['bash'], input=script,
|
||||
universal_newlines=True, stderr=subprocess.PIPE,
|
||||
env=builtins.__xonsh_env__.detype())
|
||||
out = subprocess.check_output(
|
||||
['bash'], input=script, universal_newlines=True,
|
||||
stderr=subprocess.PIPE, env=builtins.__xonsh_env__.detype())
|
||||
except subprocess.CalledProcessError:
|
||||
out = ''
|
||||
|
||||
rtn = set(out.splitlines())
|
||||
return rtn
|
||||
|
||||
def _source_completions(self):
|
||||
srcs = []
|
||||
for f in builtins.__xonsh_env__.get('BASH_COMPLETIONS'):
|
||||
if os.path.isfile(f):
|
||||
# We need to "Unixify" Windows paths for Bash to understand
|
||||
if ON_WINDOWS:
|
||||
f = RE_WIN_DRIVE.sub(lambda m: '/{0}/'.format(m.group(1).lower()), f).replace('\\', '/')
|
||||
srcs.append('source ' + f)
|
||||
return srcs
|
||||
@staticmethod
|
||||
def _collect_completions_sources():
|
||||
sources = []
|
||||
paths = (Path(x) for x in
|
||||
builtins.__xonsh_env__.get('BASH_COMPLETIONS'))
|
||||
for path in paths:
|
||||
if path.is_file():
|
||||
sources.append('source ' + str(path))
|
||||
elif path.is_dir():
|
||||
for _file in (x for x in path.glob('*') if x.is_file()):
|
||||
sources.append('source ' + str(_file))
|
||||
return sources
|
||||
|
||||
def _load_bash_complete_funcs(self):
|
||||
self.bash_complete_funcs = bcf = {}
|
||||
inp = self._source_completions()
|
||||
if len(inp) == 0:
|
||||
inp = self._collect_completions_sources()
|
||||
if not inp:
|
||||
return
|
||||
inp.append('complete -p\n')
|
||||
out = subprocess.check_output(['bash'], input='\n'.join(inp),
|
||||
env=builtins.__xonsh_env__.detype(), universal_newlines=True)
|
||||
out = self._source_completions(inp)
|
||||
for line in out.splitlines():
|
||||
head, _, cmd = line.rpartition(' ')
|
||||
if len(cmd) == 0 or cmd == 'cd':
|
||||
|
@ -467,8 +471,8 @@ class Completer(object):
|
|||
bcf[cmd] = m.group(1)
|
||||
|
||||
def _load_bash_complete_files(self):
|
||||
inp = self._source_completions()
|
||||
if len(inp) == 0:
|
||||
inp = self._collect_completions_sources()
|
||||
if not inp:
|
||||
self.bash_complete_files = {}
|
||||
return
|
||||
if self.bash_complete_funcs:
|
||||
|
@ -476,8 +480,7 @@ class Completer(object):
|
|||
bash_funcs = set(self.bash_complete_funcs.values())
|
||||
inp.append('declare -F ' + ' '.join([f for f in bash_funcs]))
|
||||
inp.append('shopt -u extdebug\n')
|
||||
out = subprocess.check_output(['bash'], input='\n'.join(inp),
|
||||
env=builtins.__xonsh_env__.detype(), universal_newlines=True)
|
||||
out = self._source_completions(inp)
|
||||
func_files = {}
|
||||
for line in out.splitlines():
|
||||
parts = line.split()
|
||||
|
@ -488,6 +491,11 @@ class Completer(object):
|
|||
if func in func_files
|
||||
}
|
||||
|
||||
def _source_completions(self, source):
|
||||
return subprocess.check_output(
|
||||
['bash'], input='\n'.join(source), universal_newlines=True,
|
||||
env=builtins.__xonsh_env__.detype(), stderr=subprocess.DEVNULL)
|
||||
|
||||
def attr_complete(self, prefix, ctx):
|
||||
"""Complete attributes of an object."""
|
||||
attrs = set()
|
||||
|
@ -513,10 +521,11 @@ class Completer(object):
|
|||
opts = []
|
||||
for i in _opts:
|
||||
try:
|
||||
v = eval('{0}.{1}'.format(expr, i), _ctx)
|
||||
opts.append(i)
|
||||
eval('{0}.{1}'.format(expr, i), _ctx)
|
||||
except: # pylint:disable=bare-except
|
||||
continue
|
||||
else:
|
||||
opts.append(i)
|
||||
if len(attr) == 0:
|
||||
opts = [o for o in opts if not o.startswith('_')]
|
||||
else:
|
||||
|
@ -558,11 +567,12 @@ class ManCompleter(object):
|
|||
startswither = startswithnorm if csc else startswithlow
|
||||
if cmd not in self._options.keys():
|
||||
try:
|
||||
manpage = subprocess.Popen(["man", cmd],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
||||
manpage = subprocess.Popen(
|
||||
["man", cmd], stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL)
|
||||
# This is a trick to get rid of reverse line feeds
|
||||
text = subprocess.check_output(["col", "-b"],
|
||||
stdin=manpage.stdout)
|
||||
text = subprocess.check_output(
|
||||
["col", "-b"], stdin=manpage.stdout)
|
||||
text = text.decode('utf-8')
|
||||
scraped_text = ' '.join(SCRAPE_RE.findall(text))
|
||||
matches = INNER_OPTIONS_RE.findall(scraped_text)
|
||||
|
|
Loading…
Add table
Reference in a new issue