diff options
author | Campbell Barton <ideasman42@gmail.com> | 2009-10-30 12:34:57 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2009-10-30 12:34:57 +0300 |
commit | ae9eae222c0a92bea637f1023a46fe44e4a2a534 (patch) | |
tree | a3845fa59eeb9bec53c675792fe1f6176be4445c /release/scripts/modules/console | |
parent | ae3cf92491be73f353b278f1632a68990d0e44d9 (diff) |
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.
Diffstat (limited to 'release/scripts/modules/console')
-rw-r--r-- | release/scripts/modules/console/complete_import.py | 14 | ||||
-rw-r--r-- | release/scripts/modules/console/complete_namespace.py | 147 | ||||
-rw-r--r-- | release/scripts/modules/console/intellisense.py | 26 |
3 files changed, 162 insertions, 25 deletions
diff --git a/release/scripts/modules/console/complete_import.py b/release/scripts/modules/console/complete_import.py index 02ded3eef6d..9166dee2bb2 100644 --- a/release/scripts/modules/console/complete_import.py +++ b/release/scripts/modules/console/complete_import.py @@ -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]) diff --git a/release/scripts/modules/console/complete_namespace.py b/release/scripts/modules/console/complete_namespace.py index a2836a60b29..4aa0de558f2 100644 --- a/release/scripts/modules/console/complete_namespace.py +++ b/release/scripts/modules/console/complete_namespace.py @@ -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,32 +105,77 @@ 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 [] + + 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 - # brackets are normally not allowed -> work around (only in this case) - if '[' in word: + # 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 + matches = complete_names(TEMP + '.' + attr, namespace) + matches = [obj + match[TEMP_N:] for match in matches] + del namespace[TEMP] + else: - _word = word + # safety net, but when would this occur? + return [] - # find matches with stdlibrary (don't try to implement this yourself) - completer.complete(_word, 0) - matches = completer.matches + if not matches: + return [] - # brackets are normally not allowed -> clean up - if '[' in word: - matches = [obj + match[TEMP_N:] for match in matches] - del namespace[TEMP] + # 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)] diff --git a/release/scripts/modules/console/intellisense.py b/release/scripts/modules/console/intellisense.py index 2658f79a4cc..eda34c9ff6b 100644 --- a/release/scripts/modules/console/intellisense.py +++ b/release/scripts/modules/console/intellisense.py @@ -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 |