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