Patch from Stani for autocomplete
authorCampbell Barton <ideasman42@gmail.com>
Fri, 30 Oct 2009 09:34:57 +0000 (09:34 +0000)
committerCampbell Barton <ideasman42@gmail.com>
Fri, 30 Oct 2009 09:34:57 +0000 (09:34 +0000)
adds ability to complete in these situations
 bpy -> bpy.
 bpy.data.objects -> bpy.data.objects["Mesh"]

my autocomplete could only do bpy -> bpy.

release/scripts/modules/console/complete_import.py
release/scripts/modules/console/complete_namespace.py
release/scripts/modules/console/intellisense.py

index 02ded3eef6d99a3b4a07e576b9ac89dafd57fbad..9166dee2bb2769d1be29099b2acba91bb6588ca0 100644 (file)
@@ -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])
index a2836a60b290dfac0aac15e9a7989ac3d9639220..4aa0de558f23f2cf9abf5bc32d627b679dd84c7c 100644 (file)
 
 """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)]
index 2658f79a4ccdac79b42b6396c1dde25b1458a4bc..eda34c9ff6bc1f1cf2813fe80f5552ce7a570e62 100644 (file)
@@ -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