add completers module

This commit is contained in:
adam j hartz 2016-05-28 13:56:56 -04:00
parent 67ac9cd5e1
commit 6b25c35f27
4 changed files with 206 additions and 1 deletions

View file

@ -172,7 +172,7 @@ def main():
platforms='Cross Platform',
classifiers=['Programming Language :: Python :: 3'],
packages=['xonsh', 'xonsh.ply', 'xonsh.ptk', 'xonsh.parsers',
'xonsh.xoreutils', 'xontrib'],
'xonsh.xoreutils', 'xontrib', 'xonsh.completers'],
package_dir={'xonsh': 'xonsh', 'xontrib': 'xontrib'},
package_data={'xonsh': ['*.json'], 'xontrib': ['*.xsh']},
cmdclass=cmdclass

View file

@ -0,0 +1,13 @@
from collections import OrderedDict
from xonsh.completers.path import complete_path
from xonsh.completers.dirs import (complete_cd, complete_rmdir)
#from xonsh.completers.commands import complete_command
completers = OrderedDict()
completers['cd'] = complete_cd
completers['rmdir'] = complete_cd
completers['path'] = complete_path
all_completers = list(completers.keys())
completers_enabled = list(all_completers)

15
xonsh/completers/dirs.py Normal file
View file

@ -0,0 +1,15 @@
import os
from xonsh.completers.path import complete_dir
PREVENT_OTHERS = ['path']
def complete_cd(prefix, line, start, end, ctx):
if start != 0 and line.split(' ')[0] == 'cd':
return complete_dir(prefix, line, start, end, ctx)
return set()
def complete_rmdir(prefix, line, start, end, ctx):
if start != 0 and line.split(' ')[0] == 'rmdir':
return complete_dir(prefix, line, start, end, ctx)
return set()

177
xonsh/completers/path.py Normal file
View file

@ -0,0 +1,177 @@
import os
import builtins
from xonsh.platform import ON_WINDOWS
from xonsh.built_ins import iglobpath, expand_path
from xonsh.tools import (subexpr_from_unbalanced, get_sep,
check_for_partial_string, RE_STRING_START)
CHARACTERS_NEED_QUOTES = ' `\t\r\n${}*()"\',?&'
if ON_WINDOWS:
CHARACTERS_NEED_QUOTES += '%'
def _path_from_partial_string(inp, pos=None):
if pos is None:
pos = len(inp)
partial = inp[:pos]
startix, endix, quote = check_for_partial_string(partial)
_post = ""
if startix is None:
return None
elif endix is None:
string = partial[startix:]
else:
if endix != pos:
_test = partial[endix:pos]
if not any(i == ' ' for i in _test):
_post = _test
else:
return None
string = partial[startix:endix]
end = re.sub(RE_STRING_START,'',quote)
_string = string
if not _string.endswith(end):
_string = _string + end
try:
val = ast.literal_eval(_string)
except SyntaxError:
return None
if isinstance(val, bytes):
env = builtins.__xonsh_env__
val = val.decode(encoding=env.get('XONSH_ENCODING'),
errors=env.get('XONSH_ENCODING_ERRORS'))
return string + _post, val + _post, quote, end
def _normpath(p):
""" Wraps os.normpath() to avoid removing './' at the beginning
and '/' at the end. On windows it does the same with backslases
"""
initial_dotslash = p.startswith(os.curdir + os.sep)
initial_dotslash |= (ON_WINDOWS and p.startswith(os.curdir + os.altsep))
p = p.rstrip()
trailing_slash = p.endswith(os.sep)
trailing_slash |= (ON_WINDOWS and p.endswith(os.altsep))
p = os.path.normpath(p)
if initial_dotslash and p != '.':
p = os.path.join(os.curdir, p)
if trailing_slash:
p = os.path.join(p, '')
if ON_WINDOWS and builtins.__xonsh_env__.get('FORCE_POSIX_PATHS'):
p = p.replace(os.sep, os.altsep)
return p
def _startswithlow(x, start, startlow=None):
if startlow is None:
startlow = start.lower()
return x.startswith(start) or x.lower().startswith(startlow)
def _startswithnorm(x, start, startlow=None):
return x.startswith(start)
def _add_env(paths, prefix):
if prefix.startswith('$'):
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)})
def _add_dots(paths, prefix):
if prefix in {'', '.'}:
paths.update({'./', '../'})
if prefix == '..':
paths.add('../')
def _add_cdpaths(paths, prefix):
"""Completes current prefix using CDPATH"""
env = builtins.__xonsh_env__
csc = env.get('CASE_SENSITIVE_COMPLETIONS')
for cdp in env.get('CDPATH'):
test_glob = os.path.join(cdp, prefix) + '*'
for s in iglobpath(test_glob, ignore_case=(not csc)):
if os.path.isdir(s):
paths.add(os.path.basename(s))
def _quote_to_use(x):
single = "'"
double = '"'
if single in x and double not in x:
return double
else:
return single
def _quote_paths(paths, start, end):
out = set()
space = ' '
backslash = '\\'
double_backslash = '\\\\'
slash = get_sep()
orig_start = start
orig_end = end
for s in paths:
start = orig_start
end = orig_end
if (start == '' and
(any(i in s for i in CHARACTERS_NEED_QUOTES) or
(backslash in s and slash != backslash))):
start = end = _quote_to_use(s)
if os.path.isdir(expand_path(s)):
_tail = slash
elif end == '':
_tail = space
else:
_tail = ''
if start != '' and 'r' not in start and backslash in s:
start = 'r%s' % start
s = s + _tail
if end != '':
if "r" not in start.lower():
s = s.replace(backslash, double_backslash)
if s.endswith(backslash) and not s.endswith(double_backslash):
s += backslash
if end in s:
s = s.replace(end, ''.join('\\%s' % i for i in end))
out.add(start + s + end)
return out
def complete_path(prefix, line, start, end, ctx, cdpath=False):
"""Completes based on a path name."""
# string stuff for automatic quoting
path_str_start = ''
path_str_end = ''
p = _path_from_partial_string(line, end)
lprefix = len(prefix)
if p is not None:
lprefix = len(p[0])
prefix = p[1]
path_str_start = p[2]
path_str_end = p[3]
tilde = '~'
paths = set()
csc = builtins.__xonsh_env__.get('CASE_SENSITIVE_COMPLETIONS')
for s in iglobpath(prefix + '*', ignore_case=(not csc)):
paths.add(s)
if tilde in prefix:
home = os.path.expanduser(tilde)
paths = {s.replace(home, tilde) for s in paths}
if cdpath:
_add_cdpaths(paths, prefix)
paths = _quote_paths({_normpath(s) for s in paths}, '', '')
_add_env(paths, prefix)
_add_dots(paths, prefix)
return paths, lprefix
def complete_dir(prefix, line, start, end, ctx, cdpath=False):
o, lp = complete_path(prefix, line, start, end, cdpath)
return {i for i in o if os.path.isdir(i)}, lp