hy/hy/completer.py

133 lines
3.5 KiB
Python

# Copyright 2018 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
import contextlib
import os
import re
import sys
import hy.macros
import hy.compiler
from hy._compat import builtins, string_types
docomplete = True
try:
import readline
except ImportError:
try:
import pyreadline.rlmain
import pyreadline.unicode_helper # NOQA
import readline
except ImportError:
docomplete = False
if docomplete:
if sys.platform == 'darwin' and 'libedit' in readline.__doc__:
readline_bind = "bind ^I rl_complete"
else:
readline_bind = "tab: complete"
class Completer(object):
def __init__(self, namespace={}):
if not isinstance(namespace, dict):
raise TypeError('namespace must be a dictionary')
self.namespace = namespace
self.path = [hy.compiler._compile_table,
builtins.__dict__,
hy.macros._hy_macros[None],
namespace]
self.tag_path = [hy.macros._hy_tag[None]]
if '__name__' in namespace:
module_name = namespace['__name__']
self.path.append(hy.macros._hy_macros[module_name])
self.tag_path.append(hy.macros._hy_tag[module_name])
def attr_matches(self, text):
# Borrowed from IPython's completer
m = re.match(r"(\S+(\.[\w-]+)*)\.([\w-]*)$", text)
if m:
expr, attr = m.group(1, 3)
attr = attr.replace("-", "_")
expr = expr.replace("-", "_")
else:
return []
try:
obj = eval(expr, self.namespace)
words = dir(obj)
except Exception:
return []
n = len(attr)
matches = []
for w in words:
if w[:n] == attr:
matches.append("{}.{}".format(
expr.replace("_", "-"), w.replace("_", "-")))
return matches
def global_matches(self, text):
matches = []
for p in self.path:
for k in p.keys():
if isinstance(k, string_types):
k = k.replace("_", "-")
if k.startswith(text):
matches.append(k)
return matches
def tag_matches(self, text):
text = text[1:]
matches = []
for p in self.tag_path:
for k in p.keys():
if isinstance(k, string_types):
if k.startswith(text):
matches.append("#{}".format(k))
return matches
def complete(self, text, state):
if text.startswith("#"):
matches = self.tag_matches(text)
elif "." in text:
matches = self.attr_matches(text)
else:
matches = self.global_matches(text)
try:
return matches[state]
except IndexError:
return None
@contextlib.contextmanager
def completion(completer=None):
delims = "()[]{} "
if not completer:
completer = Completer()
if docomplete:
readline.set_completer(completer.complete)
readline.set_completer_delims(delims)
history = os.path.expanduser("~/.hy-history")
readline.parse_and_bind("set blink-matching-paren on")
try:
readline.read_history_file(history)
except IOError:
open(history, 'a').close()
readline.parse_and_bind(readline_bind)
try:
yield
finally:
if docomplete:
readline.write_history_file(history)