angri avatar angri committed 0d76aa9

codeassist: improve completion types

This is a rather large change:

* add NamedParamProposal for function keyword parameters
* better CompletionProposal kind and name values
* merge CodeAssistProposal and CompletionProposal

This change is a bit backward incompatible in that sorted_proposals()
and the usages of 'kind' and 'name' fields of CompletionProposals need
to be updated.

Comments (0)

Files changed (2)

rope/contrib/codeassist.py

 
 import rope.base.codeanalyze
 import rope.base.evaluate
-from rope.base import pyobjects, pynames, builtins, exceptions, worder
+from rope.base import pyobjects, pyobjectsdef, pynames, builtins, exceptions, worder
 from rope.base.codeanalyze import SourceLinesAdapter
 from rope.contrib import fixsyntax
 from rope.refactor import functionutils
     return rope.contrib.findit.find_occurrences(*args, **kwds)
 
 
-class CodeAssistProposal(object):
-    """The base class for proposals reported by `code_assist`
+class CompletionProposal(object):
+    """A completion proposal
 
-    The `kind` instance variable shows the kind of the proposal and
-    can be 'global', 'local', 'builtin', 'attribute', 'keyword',
+    The `kind` instance variable shows where proposed name came from
+    and can be 'global', 'local', 'builtin', 'attribute', 'keyword',
     'parameter_keyword'.
 
+    The `type` instance variable shows the aproximate type of the
+    proposed object and can be 'variable', 'class', 'function', 'module',
+    'exception' and `None`.
+
+    All possible relations between proposal's `kind` and `type` are shown
+    in the table below (different kinds in rows and types in columns.
+    NB: some kind names are shortened):
+
+             | variable | class | function | module | exception | None
+       local |    +     |   +   |    +     |   +    |           |
+      global |    +     |   +   |    +     |   +    |           |
+     builtin |    +     |   +   |    +     |        |     +     |
+      attrib |    +     |   +   |    +     |   +    |           |
+     keyword |          |       |          |        |           |  +
+    param_kw |          |       |          |        |           |  +
+
     """
 
-    def __init__(self, name, kind):
+    def __init__(self, name, kind, pyname=None):
         self.name = name
         self.kind = kind
-
-
-class CompletionProposal(CodeAssistProposal):
-    """A completion proposal
-
-    The `type` instance variable shows the type of the proposal and
-    can be 'variable', 'class', 'function', 'imported' , 'paramter'
-    and `None`.
-
-    """
-
-    def __init__(self, name, kind, type=None, pyname=None):
-        super(CompletionProposal, self).__init__(name, kind)
-        self.type = type
         self.pyname = pyname
+        if pyname is not None:
+            self.type = self._get_type()
+        else:
+            self.type = None
 
     def __str__(self):
         return '%s (%s, %s)' % (self.name, self.kind, self.type)
             if isinstance(pyobject, pyobject.AbstractFunction):
                 return pyobject.get_param_names()
 
+    def _get_type(self):
+        pyname = self.pyname
+        if isinstance(pyname, builtins.BuiltinName):
+            self.kind = 'builtin'
+            pyobject = pyname.get_object()
+            if isinstance(pyobject, builtins.BuiltinFunction):
+                return 'function'
+            elif isinstance(pyobject, builtins.BuiltinClass):
+                clsobj = pyobject.builtin
+                if issubclass(clsobj, BaseException):
+                    return 'exception'
+                return 'class'
+            elif isinstance(pyobject, builtins.BuiltinObject) or \
+                 isinstance(pyobject, builtins.BuiltinName):
+                return 'object'
+        elif isinstance(pyname, pynames.ImportedModule):
+            return 'module'
+        elif isinstance(pyname, pynames.ImportedName) or \
+           isinstance(pyname, pynames.DefinedName):
+            pyobject = pyname.get_object()
+            if isinstance(pyobject, pyobjects.AbstractFunction):
+                return 'function'
+            if isinstance(pyobject, pyobjects.AbstractClass):
+                return 'class'
+        return 'variable'
+
+    def get_doc(self):
+        """Get the proposed object's docstring.
+
+        Returns None if it can not be get.
+        """
+        if not self.pyname:
+            return None
+        pyobject = self.pyname.get_object()
+        if not hasattr(pyobject, 'get_doc'):
+            return None
+        return self.pyname.get_object().get_doc()
+
+
+# leaved for backward compatibility
+CodeAssistProposal = CompletionProposal
+
+
+class NamedParamProposal(CompletionProposal):
+    """A parameter keyword completion proposal
+
+    Holds reference to ``_function`` -- the function which
+    parameter ``name`` belongs to. This allows to determine
+    default value for this parameter.
+    """
+    def __init__(self, name, function):
+        self.argname = name
+        name = '%s=' % name
+        super(NamedParamProposal, self).__init__(name, 'parameter_keyword')
+        self._function = function
+
+    def get_default(self):
+        """Get a string representation of a param's default value.
+
+        Returns None if there is no default value for this param.
+        """
+        definfo = functionutils.DefinitionInfo.read(self._function)
+        for arg, default in definfo.args_with_defaults:
+            if self.argname == arg:
+                return default
+        return None
+
 
 def sorted_proposals(proposals, kindpref=None, typepref=None):
     """Sort a list of proposals
     Return a sorted list of the given `CodeAssistProposal`\s.
 
     `kindpref` can be a list of proposal kinds.  Defaults to
-    ``['local', 'parameter_keyword', 'global', 'attribute',
-    'keyword']``.
+    ``['local', 'parameter_keyword', 'global', 'builtin',
+    'attribute', 'keyword']``.
 
     `typepref` can be a list of proposal types.  Defaults to
-    ``['class', 'function', 'variable', 'parameter', 'imported',
-    'builtin', None]``.  (`None` stands for completions with no type
+    ``['class', 'function', 'variable', 'module', 'exception',
+    None]``.  (`None` stands for completions with no type
     like keywords.)
     """
     sorter = _ProposalSorter(proposals, kindpref, typepref)
             element = found_pyname.get_object()
             for name, pyname in element.get_attributes().items():
                 if name.startswith(self.starting):
-                    result[name] = CompletionProposal(
-                        name, 'attribute', self._get_pyname_type(pyname), pyname)
+                    result[name] = CompletionProposal(name, 'attribute', pyname)
         return result
 
     def _undotted_completions(self, scope, result, lineno=None):
                     kind = 'global'
                 if lineno is None or self.later_locals or \
                    not self._is_defined_after(scope, pyname, lineno):
-                    result[name] = CompletionProposal(
-                        name, kind, self._get_pyname_type(pyname), pyname)
+                    result[name] = CompletionProposal(name, kind, pyname)
 
     def _from_import_completions(self, pymodule):
         module_name = self.word_finder.get_from_module(self.offset)
         for name in pymodule:
             if name.startswith(self.starting):
                 result[name] = CompletionProposal(name, kind='global',
-                                                  type='imported',
                                                   pyname=pymodule[name])
         return result
 
                lineno <= location[1] <= scope.get_end():
                 return True
 
-    def _get_pyname_type(self, pyname):
-        if isinstance(pyname, builtins.BuiltinName):
-            return 'builtin'
-        if isinstance(pyname, pynames.ImportedName) or \
-           isinstance(pyname, pynames.ImportedModule):
-            return 'imported'
-        if isinstance(pyname, pynames.ParameterName):
-            return 'parameter'
-        if isinstance(pyname, builtins.BuiltinName) or \
-           isinstance(pyname, pynames.DefinedName):
-            pyobject = pyname.get_object()
-            if isinstance(pyobject, pyobjects.AbstractFunction):
-                return 'function'
-            if isinstance(pyobject, pyobjects.AbstractClass):
-                return 'class'
-        return 'variable'
-
     def _code_completions(self):
         lineno = self.code.count('\n', 0, self.offset) + 1
         fixer = fixsyntax.FixSyntax(self.pycore, self.code,
                     result = {}
                     for name in param_names:
                         if name.startswith(self.starting):
-                            result[name + '='] = CompletionProposal(
-                                name + '=', 'parameter_keyword')
+                            result[name + '='] = NamedParamProposal(
+                                name, pyobject
+                            )
                     return result
         return {}
 
         self.proposals = code_assist_proposals
         if kindpref is None:
             kindpref = ['local', 'parameter_keyword', 'global',
-                        'attribute', 'keyword']
+                        'attribute', 'builtin', 'keyword']
         self.kindpref = kindpref
         if typepref is None:
-            typepref = ['class', 'function', 'variable',
-                        'parameter', 'imported', 'builtin', None]
+            typepref = ['class', 'function', 'variable', 'module',
+                        'exception', None]
         self.typerank = dict((type, index)
                               for index, type in enumerate(typepref))
 

ropetest/contrib/codeassisttest.py

     def test_simple_assist(self):
         self._assist('', 0)
 
-    def assert_completion_in_result(self, name, kind, result):
+    def assert_completion_in_result(self, name, kind, result, type=None):
         for proposal in result:
-            if proposal.name == name and proposal.kind == kind:
+            if proposal.name == name:
+                self.assertEqual(kind, proposal.kind,
+                        "proposal <%s> has wrong kind, expected " \
+                        "%r, got %r" % (name, kind, proposal.kind))
+                if type is not None:
+                    self.assertEqual(type, proposal.type,
+                            "proposal <%s> has wrong type, expected " \
+                            "%r, got %r" % (name, type, proposal.type))
                 return
         self.fail('completion <%s> not proposed' % name)
 
     def test_including_matching_builtins_types(self):
         code = 'my_var = Excep'
         result = self._assist(code)
-        self.assert_completion_in_result('Exception', 'global', result)
-        self.assert_completion_not_in_result('zip', 'global', result)
+        self.assert_completion_in_result('Exception', 'builtin', result)
+        self.assert_completion_not_in_result('zip', 'builtin', result)
 
     def test_including_matching_builtins_functions(self):
         code = 'my_var = zi'
         result = self._assist(code)
-        self.assert_completion_in_result('zip', 'global', result)
+        self.assert_completion_in_result('zip', 'builtin', result)
 
     def test_including_keywords(self):
         code = 'fo'
     def test_completing_excepts_in_uncomplete_try_blocks(self):
         code = 'try:\n    pass\nexcept Exc'
         result = self._assist(code)
-        self.assert_completion_in_result('Exception', 'global', result)
+        self.assert_completion_in_result('Exception', 'builtin', result)
 
     def test_and_normal_complete_blocks_and_single_fixing(self):
         code = 'try:\n    range.\nexcept:\n    pass\n'
         doc = get_calltip(self.project, src, src.rindex('f'), remove_self=True)
         self.assertEquals('C.f(p1)', doc)
 
+    # TESTING PROPOSAL'S KINDS AND TYPES.
+    # SEE RELATION MATRIX IN `CompletionProposal`'s DOCSTRING
+
+    def test_local_variable_completion_proposal(self):
+        code = 'def foo():\n  xvar = 5\n  x'
+        result = self._assist(code)
+        self.assert_completion_in_result('xvar', 'local', result, 'variable')
+
+    def test_global_variable_completion_proposal(self):
+        code = 'yvar = 5\ny'
+        result = self._assist(code)
+        self.assert_completion_in_result('yvar', 'global', result, 'variable')
+
+    def test_builtin_variable_completion_proposal(self):
+        for varname in ('False', 'True'):
+            result = self._assist(varname[0])
+            self.assert_completion_in_result(varname, 'builtin', result,
+                                             type='variable')
+
+    def test_attribute_variable_completion_proposal(self):
+        code = 'class AClass(object):\n  def foo(self):\n    ' \
+               'self.bar = 1\n    self.b'
+        result = self._assist(code)
+        self.assert_completion_in_result('bar', 'attribute', result,
+                                         type='variable')
+
+    def test_local_class_completion_proposal(self):
+        code = 'def foo():\n  class LocalClass(object): pass\n  Lo'
+        result = self._assist(code)
+        self.assert_completion_in_result('LocalClass', 'local', result,
+                                         type='class')
+
+    def test_global_class_completion_proposal(self):
+        code = 'class GlobalClass(object): pass\nGl'
+        result = self._assist(code)
+        self.assert_completion_in_result('GlobalClass', 'global', result,
+                                         type='class')
+
+    def test_builtin_class_completion_proposal(self):
+        for varname in ('object', 'dict', 'file'):
+            result = self._assist(varname[0])
+            self.assert_completion_in_result(varname, 'builtin', result,
+                                             type='class')
+
+    def test_attribute_class_completion_proposal(self):
+        code = 'class Outer(object):\n  class Inner(object): pass\nOuter.'
+        result = self._assist(code)
+        self.assert_completion_in_result('Inner', 'attribute', result,
+                                         type='class')
+
+    def test_local_function_completion_proposal(self):
+        code = 'def outer():\n  def inner(): pass\n  in'
+        result = self._assist(code)
+        self.assert_completion_in_result('inner', 'local', result,
+                                         type='function')
+
+    def test_global_function_completion_proposal(self):
+        code = 'def foo(): pass\nf'
+        result = self._assist(code)
+        self.assert_completion_in_result('foo', 'global', result,
+                                         type='function')
+
+    def test_builtin_function_completion_proposal(self):
+        code = 'a'
+        result = self._assist(code)
+        for expected in ('all', 'any', 'abs'):
+            self.assert_completion_in_result(expected, 'builtin', result,
+                                             type='function')
+
+    def test_attribute_function_completion_proposal(self):
+        code = 'class Some(object):\n  def method(self):\n    self.'
+        result = self._assist(code)
+        self.assert_completion_in_result('method', 'attribute', result,
+                                         type='function')
+
+    def test_local_module_completion_proposal(self):
+        code = 'def foo():\n  import types\n  t'
+        result = self._assist(code)
+        self.assert_completion_in_result('types', 'local', result,
+                                         type='module')
+
+    def test_global_module_completion_proposal(self):
+        code = 'import operator\no'
+        result = self._assist(code)
+        self.assert_completion_in_result('operator', 'global', result,
+                                         type='module')
+
+    def test_attribute_module_completion_proposal(self):
+        code = 'class Some(object):\n  import os\nSome.o'
+        result = self._assist(code)
+        self.assert_completion_in_result('os', 'attribute', result,
+                                         type='module')
+
+    def test_builtin_exception_completion_proposal(self):
+        code = 'def blah():\n  Z'
+        result = self._assist(code)
+        self.assert_completion_in_result('ZeroDivisionError', 'builtin',
+                                         result, type='exception')
+
+    def test_keyword_completion_proposal(self):
+        code = 'f'
+        result = self._assist(code)
+        self.assert_completion_in_result('for', 'keyword', result, type=None)
+        self.assert_completion_in_result('from', 'keyword', result, type=None)
+
+    def test_parameter_keyword_completion_proposal(self):
+        code = 'def func(abc, aloha, alpha, amigo): pass\nfunc(a'
+        result = self._assist(code)
+        for expected in ('abc=', 'aloha=', 'alpha=', 'amigo='):
+            self.assert_completion_in_result(expected, 'parameter_keyword',
+                                             result, type=None)
+
 
 class CodeAssistInProjectsTest(unittest.TestCase):
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.