Patch from Stani for autocomplete
adds ability to complete in these situations bpy -> bpy. bpy.data.objects -> bpy.data.objects["Mesh"] my autocomplete could only do bpy -> bpy.
This commit is contained in:
@@ -126,6 +126,8 @@ def complete(line):
|
||||
|
||||
>>> complete('import weak')
|
||||
['weakref']
|
||||
>>> complete('from weakref import C')
|
||||
['CallableProxyType']
|
||||
"""
|
||||
import inspect
|
||||
|
||||
@@ -148,6 +150,8 @@ def complete(line):
|
||||
(hasattr(m, '__file__') and '__init__' in m.__file__):
|
||||
completion_list = [attr for attr in dir(m)
|
||||
if is_importable(m, attr)]
|
||||
else:
|
||||
completion_list = []
|
||||
completion_list.extend(getattr(m, '__all__', []))
|
||||
if hasattr(m, '__file__') and '__init__' in m.__file__:
|
||||
completion_list.extend(module_list(os.path.dirname(m.__file__)))
|
||||
@@ -156,6 +160,9 @@ def complete(line):
|
||||
completion_list.remove('__init__')
|
||||
return completion_list
|
||||
|
||||
def filter_prefix(names, prefix):
|
||||
return [name for name in names if name.startswith(prefix)]
|
||||
|
||||
words = line.split(' ')
|
||||
if len(words) == 3 and words[0] == 'from':
|
||||
return ['import ']
|
||||
@@ -164,11 +171,10 @@ def complete(line):
|
||||
return get_root_modules()
|
||||
mod = words[1].split('.')
|
||||
if len(mod) < 2:
|
||||
mod0 = mod[0]
|
||||
return [m for m in get_root_modules() if m.startswith(mod0)]
|
||||
return filter_prefix(get_root_modules(), words[-1])
|
||||
completion_list = try_import('.'.join(mod[:-1]), True)
|
||||
completion_list = ['.'.join(mod[:-1] + [el]) for el in completion_list]
|
||||
return completion_list
|
||||
return filter_prefix(completion_list, words[-1])
|
||||
if len(words) >= 3 and words[0] == 'from':
|
||||
mod = words[1]
|
||||
return try_import(mod)
|
||||
return filter_prefix(try_import(mod), words[-1])
|
||||
|
||||
@@ -15,12 +15,86 @@
|
||||
|
||||
"""Autocomplete with the standard library"""
|
||||
|
||||
import re
|
||||
import rlcompleter
|
||||
|
||||
|
||||
RE_INCOMPLETE_INDEX = re.compile('(.*?)\[[^\]]+$')
|
||||
|
||||
TEMP = '__tEmP__' # only \w characters are allowed!
|
||||
TEMP_N = len(TEMP)
|
||||
|
||||
|
||||
def is_dict(obj):
|
||||
"""Returns whether obj is a dictionary"""
|
||||
return hasattr(obj, 'keys') and hasattr(getattr(obj, 'keys'), '__call__')
|
||||
|
||||
|
||||
def complete_names(word, namespace):
|
||||
"""Complete variable names or attributes
|
||||
|
||||
:param word: word to be completed
|
||||
:type word: str
|
||||
:param namespace: namespace
|
||||
:type namespace: dict
|
||||
:returns: completion matches
|
||||
:rtype: list of str
|
||||
|
||||
>>> complete_names('fo', {'foo': 'bar'})
|
||||
['foo', 'for', 'format(']
|
||||
"""
|
||||
# start completer
|
||||
completer = rlcompleter.Completer(namespace)
|
||||
# find matches with std library (don't try to implement this yourself)
|
||||
completer.complete(word, 0)
|
||||
return sorted(set(completer.matches))
|
||||
|
||||
|
||||
def complete_indices(word, namespace, obj=None, base=None):
|
||||
"""Complete a list or dictionary with its indices:
|
||||
|
||||
* integer numbers for list
|
||||
* any keys for dictionary
|
||||
|
||||
:param word: word to be completed
|
||||
:type word: str
|
||||
:param namespace: namespace
|
||||
:type namespace: dict
|
||||
:param obj: object evaluated from base
|
||||
:param base: substring which can be evaluated into an object
|
||||
:type base: str
|
||||
:returns: completion matches
|
||||
:rtype: list of str
|
||||
|
||||
>>> complete_indices('foo', {'foo': range(5)})
|
||||
['foo[0]', 'foo[1]', 'foo[2]', 'foo[3]', 'foo[4]']
|
||||
>>> complete_indices('foo', {'foo': {'bar':0, 1:2}})
|
||||
['foo[1]', "foo['bar']"]
|
||||
>>> complete_indices("foo['b", {'foo': {'bar':0, 1:2}}, base='foo')
|
||||
["foo['bar']"]
|
||||
"""
|
||||
#FIXME: 'foo["b'
|
||||
if base is None:
|
||||
base = word
|
||||
if obj is None:
|
||||
try:
|
||||
obj = eval(base, namespace)
|
||||
except Exception:
|
||||
return []
|
||||
if not hasattr(obj, '__getitem__'):
|
||||
# obj is not a list or dictionary
|
||||
return []
|
||||
if is_dict(obj):
|
||||
# dictionary type
|
||||
matches = ['%s[%r]' % (base, key) for key in sorted(obj.keys())]
|
||||
else:
|
||||
# list type
|
||||
matches = ['%s[%d]' % (base, idx) for idx in range(len(obj))]
|
||||
if word != base:
|
||||
matches = [match for match in matches if match.startswith(word)]
|
||||
return matches
|
||||
|
||||
|
||||
def complete(word, namespace, private=True):
|
||||
"""Complete word within a namespace with the standard rlcompleter
|
||||
module. Also supports index or key access [].
|
||||
@@ -31,33 +105,78 @@ def complete(word, namespace, private=True):
|
||||
:type namespace: dict
|
||||
:param private: whether private attribute/methods should be returned
|
||||
:type private: bool
|
||||
:returns: completion matches
|
||||
:rtype: list of str
|
||||
|
||||
>>> complete('fo', {'foo': 'bar'})
|
||||
['foo']
|
||||
>>> complete('foo[1', {'foo': range(14)})
|
||||
['foo[1]', 'foo[10]', 'foo[11]', 'foo[12]', 'foo[13]']
|
||||
>>> complete('foo[0]', {'foo': [range(5)]})
|
||||
['foo[0][0]', 'foo[0][1]', 'foo[0][2]', 'foo[0][3]', 'foo[0][4]']
|
||||
>>> complete('foo[0].i', {'foo': [range(5)]})
|
||||
['foo[0].index(', 'foo[0].insert(']
|
||||
>>> complete('rlcompleter', {'rlcompleter': rlcompleter})
|
||||
['rlcompleter.']
|
||||
"""
|
||||
completer = rlcompleter.Completer(namespace)
|
||||
#
|
||||
# if word is empty -> nothing to complete
|
||||
if not word:
|
||||
return []
|
||||
|
||||
# brackets are normally not allowed -> work around (only in this case)
|
||||
if '[' in word:
|
||||
re_incomplete_index = RE_INCOMPLETE_INDEX.search(word)
|
||||
if re_incomplete_index:
|
||||
# ignore incomplete index at the end, e.g 'a[1' -> 'a'
|
||||
matches = complete_indices(word, namespace,
|
||||
base=re_incomplete_index.group(1))
|
||||
|
||||
elif not('[' in word):
|
||||
matches = complete_names(word, namespace)
|
||||
|
||||
elif word[-1] == ']':
|
||||
matches = [word]
|
||||
|
||||
elif '.' in word:
|
||||
# brackets are normally not allowed -> work around
|
||||
|
||||
# remove brackets by using a temp var without brackets
|
||||
obj, attr = word.rsplit('.', 1)
|
||||
try:
|
||||
# do not run the obj expression in the console
|
||||
namespace[TEMP] = eval(obj, namespace)
|
||||
except Exception:
|
||||
return []
|
||||
_word = TEMP + '.' + attr
|
||||
else:
|
||||
_word = word
|
||||
|
||||
# find matches with stdlibrary (don't try to implement this yourself)
|
||||
completer.complete(_word, 0)
|
||||
matches = completer.matches
|
||||
|
||||
# brackets are normally not allowed -> clean up
|
||||
if '[' in word:
|
||||
matches = complete_names(TEMP + '.' + attr, namespace)
|
||||
matches = [obj + match[TEMP_N:] for match in matches]
|
||||
del namespace[TEMP]
|
||||
|
||||
else:
|
||||
# safety net, but when would this occur?
|
||||
return []
|
||||
|
||||
if not matches:
|
||||
return []
|
||||
|
||||
# add '.', '(' or '[' if no match has been found
|
||||
elif len(matches) == 1 and matches[0] == word:
|
||||
|
||||
# try to retrieve the object
|
||||
try:
|
||||
obj = eval(word, namespace)
|
||||
except Exception:
|
||||
return []
|
||||
# ignore basic types
|
||||
if type(obj) in (bool, float, int, str):
|
||||
return []
|
||||
# an extra char '[', '(' or '.' will be added
|
||||
if hasattr(obj, '__getitem__'):
|
||||
# list or dictionary
|
||||
matches = complete_indices(word, namespace, obj)
|
||||
elif hasattr(obj, '__call__'):
|
||||
# callables
|
||||
matches = [word + '(']
|
||||
else:
|
||||
# any other type
|
||||
matches = [word + '.']
|
||||
|
||||
# separate public from private
|
||||
public_matches = [match for match in matches if not('._' in match)]
|
||||
if private:
|
||||
|
||||
@@ -29,17 +29,29 @@ import re
|
||||
# line which starts with an import statement
|
||||
RE_MODULE = re.compile('^import|from.+')
|
||||
|
||||
# The following regular expression means a word which:
|
||||
# - doesn't start with a quote (quoted words are not py objects)
|
||||
# - starts with a [a-zA-Z0-9_]
|
||||
# - afterwards dots are allowed as well
|
||||
# - square bracket pairs [] are allowed (should be closed)
|
||||
# The following regular expression means an 'unquoted' word
|
||||
RE_UNQUOTED_WORD = re.compile(
|
||||
'''(?:^|[^"'])((?:\w+(?:\w|[.]|\[.+?\])*|))$''', re.UNICODE)
|
||||
# don't start with a quote
|
||||
'''(?:^|[^"'a-zA-Z0-9_])'''
|
||||
# start with a \w = [a-zA-Z0-9_]
|
||||
'''((?:\w+'''
|
||||
# allow also dots and closed bracket pairs []
|
||||
'''(?:\w|[.]|\[.+?\])*'''
|
||||
# allow empty string
|
||||
'''|)'''
|
||||
# allow an unfinished index at the end (including quotes)
|
||||
'''(?:\[[^\]]*$)?)$''',
|
||||
# allow unicode as theoretically this is possible
|
||||
re.UNICODE)
|
||||
|
||||
|
||||
def complete(line, cursor, namespace, private=True):
|
||||
"""Returns a list of possible completions.
|
||||
"""Returns a list of possible completions:
|
||||
|
||||
* name completion
|
||||
* attribute completion (obj.attr)
|
||||
* index completion for lists and dictionaries
|
||||
* module completion (from/import)
|
||||
|
||||
:param line: incomplete text line
|
||||
:type line: str
|
||||
|
||||
Reference in New Issue
Block a user