1. zjes
  2. rope_py3k

Commits

Ali Gholami Rudi  committed 9e56059

Refactored PythonCodeAssist._get_code_completions

  • Participants
  • Parent commits 5b43a66
  • Branches trunk

Comments (0)

Files changed (5)

File README.txt

View file
 Overview
 ========
 
-'rope' is a Python IDE. Its main goal is to provide features like
-auto-completion, refactorings, content assists and outlines. It is 
+*rope* is a Python IDE.  Its main goal is to provide features like
+auto-completion, refactorings, content assists and outlines.  It is
 written in python and uses the Tkinter library.
 
 
 * Auto-completing "self."s
 * Auto completion after "."s for modules and classes
 
-You can use <Alt-/> for auto-completion. The letter before the actual
+You can use <Alt-/> for auto-completion.  The letter before the actual
 proposal in the code assist proposals dialog indicates the type of
 the proposal; G: global, L: local, A: attribute, K: keyword, B: builtin and
 T: template.
 
 Right now, you can't complete as you type but that will be probably
-implemented before 0.2 release. Lots of interesting features are
+implemented before 0.2 release.  Lots of interesting features are
 planned to be added before 0.3 release like quick outline, show pydoc,
-goto definition and a bit of type inferencing. Keep waiting!
+goto definition and a bit of type inferencing.  Keep waiting!
 
 
 Keybinding
 Description
 ===========
 
-*rope* is a python IDE. It tries to give users lots of things
+*rope* is a python IDE.  It tries to give users lots of things
 that are not available in python IDEs yet.
 
 Refactoring
 -----------
 In recent years refactoring has become a basic task of everyday programing,
-specially in java community. In the agile programing methodologies, like
+specially in java community.  In the agile programing methodologies, like
 Extreme Programing, Refactoring is one of the core practices.
 
 Some IDEs support some basic refactorings like 'PyDev' (which uses bicycle
-repair man). These IDEs have a limited set of refactorings and fail
+repair man).  These IDEs have a limited set of refactorings and fail
 when doing refactorings that need to know the type of objects in the
-source code (specially for relatively large projects). rope tries to provide a
-rich set of refactorings. Some of the refactorings require type
+source code (specially for relatively large projects).  rope tries to provide a
+rich set of refactorings.  Some of the refactorings require type
 inferencing which is described later.
 
 Auto Completion
 ---------------
 One of the basic features of modern IDEs is the availability of auto-completion.
-Some Python IDEs have auto-completion support but in a limited form. Since
+Some Python IDEs have auto-completion support but in a limited form.  Since
 the type of many variables cannot be deduced from simple analysis of the source code.
 Auto-completing modules names, class names, static methods, class methods,
-function names and variable names are easy. But auto-completing the methods and
-attributes of an object is hard. Because the IDE needs to know the type of
+function names and variable names are easy.  But auto-completing the methods and
+attributes of an object is hard.  Because the IDE needs to know the type of
 the object that cannot be achieved easily most of the time in dynamic languages.
 rope uses Type Inferencing algorithms to solve this problem.
 
 ----------------
 One disadvantage of dynamic languages like python is that you cannot
 know the type of variables by a simple analysis of program source code
-most of the time. Knowing the type of variables is very essential for
-providing many of the refactorings and auto-completions. rope will use
+most of the time.  Knowing the type of variables is very essential for
+providing many of the refactorings and auto-completions.  rope will use
 type inferencing to overcome this problem.
 
 Static type inferencing uses program source code to guess the type
-of objects. But type inferencing python programs is very hard.
+of objects.  But type inferencing python programs is very hard.
 There have been some attempts though not very successful (examples:
 psycho: only str and int types, StarKiller: wasn't released and
-ShedSkin: good but limited). They where mostly directed at speeding up
+ShedSkin: good but limited).  They where mostly directed at speeding up
 python programs by transforming its code to other typed languages 
-rather than building IDEs. Such algorithms might be helpful.
+rather than building IDEs.  Such algorithms might be helpful.
 
-There is another approach toward type inferencing. That is the analysis
-of running programs. This dynamic approach records the types variables are
-assigned to during the program execution. Although this approach is
-a lot easier to implement than the alternative, it is limited. Only the
-parts of the program that are executed are analyzed. If developers write
+There is another approach toward type inferencing.  That is the analysis
+of running programs.  This dynamic approach records the types variables are
+assigned to during the program execution.  Although this approach is
+a lot easier to implement than the alternative, it is limited.  Only the
+parts of the program that are executed are analyzed.  If developers write
 unit tests and use test driven development this approach works very well.
 
 
 ================
 
 The main motive for starting this project was the lack of good
-refactoring tools for python language. Refactoring programs like "bicycle repair man"
+refactoring tools for python language.  Refactoring programs like "bicycle repair man"
 aren't reliable due to type inferencing problems discussed earlier and they
 support a limited number of refactorings.
 
 smalltalk writes in his doctoral thesis:
 
   "An early implementation of the Refactoring Browser for Smalltalk was a
-  separate tool from the standard Smalltalk development tools. What we found
-  was that no one used it. We did not even use it ourselves. Once we integrated
+  separate tool from the standard Smalltalk development tools.  What we found
+  was that no one used it.  We did not even use it ourselves.  Once we integrated
   the refactorings directly into the Smalltalk Browser, we used them extensively."
 
 The main goal of rope is to concentrate on type inferencing, auto-completion
 Get Involved!
 =============
 
-Rope has just started. Right now rope's design changes rapidly and it's not
-yet ready for code contributions in its central parts. I hope in soon future,
+Rope has just started.  Right now rope's design changes rapidly and it's not
+yet ready for code contributions in its central parts.  I hope in soon future,
 somewhere about version 0.5 or 0.6, rope would be mature enough for being
 extended easily in those parts.
 
 Right now contributions are really needed in UI part and
-patches and extensions in the UI part are extremely welcome. Have a look at the
-UI enhancement stories (docs/stories.txt). Send your patches in sourceforge.net
-project page, http://sf.net/projects/rope. Patches should use python coding
-style, PEP 8, and should have good unit tests. rope uses a local repository
+patches and extensions in the UI part are extremely welcome.  Have a look at the
+UI enhancement stories (docs/stories.txt).  Send your patches in sourceforge.net
+project page, http://sf.net/projects/rope.  Patches should use python coding
+style, PEP 8, and should have good unit tests.  rope uses a local repository
 right now, but it will be moved to SVN repository on sourceforge.net some time
-before the 0.3 release. If you're making your patches using source package
+before the 0.3 release.  If you're making your patches using source package
 distributions specify the version of that package.
 
 
 License
 =======
 
-This program is under the terms of GPL(GNU General Public License). Have a
+This program is under the terms of GPL(GNU General Public License).  Have a
 look at copying file for more information.
 

File docs/workingon.txt

View file
 Refactor CodeAssist @ 2
 =======================
 
-- Move from Project to PyCore
-  - find_module
-  - find_package
-  - get_source_folders
-  - create_module
-  - create_package
-  - PythonFileRunner
+- Refactor codeassist._get_code_completions
 
-! UI depends on those
-* Correct indentation
-* Refactor codeassist._get_code_completions
 * Refactor _ScopeVisitor.visitFrom and visitImport
 
 ? Folders and change_observers

File rope/codeassist.py

View file
     
     """
 
-    def __init__(self, completions=[], templates=[], start_offset=0, end_offset=0):
+    def __init__(self, completions=[], templates=[], start_offset=0,
+                 end_offset=0):
         self.completions = completions
         self.templates = templates
         self.start_offset = start_offset
         return Proposals()
 
 
+class _CodeCompletionCollector(object):
+
+    def __init__(self, project, source_code, offset, starting):
+        self.project = project
+        self.starting = starting
+        self.pycore = self.project.get_pycore()
+        self.lines = source_code.split('\n')
+        current_pos = 0
+        lineno = 0
+        while current_pos + len(self.lines[lineno]) < offset:
+            current_pos += len(self.lines[lineno]) + 1
+            lineno += 1
+        self.lineno = lineno
+        self.current_indents = self._get_line_indents(lineno)
+        self._comment_current_statement()
+
+    def _get_line_indents(self, line_number):
+        indents = 0
+        for char in self.lines[line_number]:
+            if char == ' ':
+                indents += 1
+            else:
+                break
+        return indents
+
+    def _comment_current_statement(self):
+        range_finder = StatementRangeFinder(ArrayLinesAdapter(self.lines), self.lineno + 1)
+        range_finder.analyze()
+        start = range_finder.get_statement_start() - 1
+        end = range_finder.get_scope_end() - 1
+        last_indents = self._get_line_indents(start)
+        self.lines[start] = last_indents * ' ' + 'pass'
+        for line in range(start + 1, end + 1):
+            self.lines[line] = '#' # + lines[line]
+        self.lines.append('\n')
+
+    def _find_inner_holding_scope(self, base_scope):
+        current_scope = base_scope
+        inner_scope = current_scope
+        while current_scope is not None and \
+              (current_scope.get_kind() == 'Module' or
+               self._get_line_indents(current_scope.get_lineno() - 1) < self.current_indents):
+            inner_scope = current_scope
+            new_scope = None
+            for scope in current_scope.get_scopes():
+                if scope.get_lineno() - 1 <= self.lineno:
+                    new_scope = scope
+                else:
+                    break
+            current_scope = new_scope
+        return inner_scope
+
+    def _get_dotted_completions(self, scope):
+        result = {}
+        if '.' in self.starting:
+            tokens = self.starting.split('.')
+            element = scope.lookup(tokens[0])
+            if element is not None:
+                consistent = True
+                for token in tokens[1:-1]:
+                    if token in element.get_attributes():
+                        element = element.get_attributes()[token]
+                    else:
+                        consistent = False
+                        break
+                if consistent:
+                    for name, pyname in element.get_attributes().iteritems():
+                        if name.startswith(tokens[-1]) or tokens[-1] == '':
+                            complete_name = '.'.join(tokens[:-1]) + '.' + name
+                            result[complete_name] = CompletionProposal(complete_name, 'attribute')
+        return result
+
+    def _get_undotted_completions(self, scope, result):
+        if scope.parent != None:
+            self._get_undotted_completions(scope.parent, result)
+        for name, pyname in scope.get_names().iteritems():
+            if name.startswith(self.starting):
+                from rope.pycore import PyObject
+                kind = 'local'
+                if scope.get_kind() == 'Module':
+                    kind = 'global'
+                result[name] = CompletionProposal(name, kind)
+
+    def get_code_completions(self):
+        try:
+            module_scope = self.pycore.get_string_scope('\n'.join(self.lines))
+        except SyntaxError, e:
+            raise RopeSyntaxError(e)
+        current_scope = module_scope
+        result = {}
+        inner_scope = self._find_inner_holding_scope(module_scope)
+        if '.' in self.starting:
+            result.update(self._get_dotted_completions(inner_scope))
+        else:
+            self._get_undotted_completions(inner_scope, result)
+        return result
+
+
 class PythonCodeAssist(CodeAssist):
+
     def __init__(self, project):
         self.project = project
         self.builtins = [str(name) for name in dir(__builtin__)
             current_offset -= 1;
         return current_offset + 1
 
-    def _comment_current_statement(self, lines, lineno):
-        range_finder = StatementRangeFinder(ArrayLinesAdapter(lines), lineno + 1)
-        range_finder.analyze()
-        start = range_finder.get_statement_start() - 1
-        end = range_finder.get_scope_end() - 1
-        last_indents = self._get_line_indents(lines, start)
-        lines[start] = last_indents * ' ' + 'pass'
-        for line in range(start + 1, end + 1):
-            lines[line] = '#' # + lines[line]
-        lines.append('\n')
-
     def _get_matching_builtins(self, starting):
         result = {}
         for builtin in self.builtins:
                 result[kw] = CompletionProposal(kw, 'keyword')
         return result
 
-    def _get_line_indents(self, lines, line_number):
-        indents = 0
-        for char in lines[line_number]:
-            if char == ' ':
-                indents += 1
-            else:
-                break
-        return indents
-
-
-    def _get_code_completions(self, source_code, offset, starting):
-        lines = source_code.split('\n')
-        current_pos = 0
-        lineno = 0
-        while current_pos + len(lines[lineno]) < offset:
-            current_pos += len(lines[lineno]) + 1
-            lineno += 1
-        current_indents = self._get_line_indents(lines, lineno)
-        self._comment_current_statement(lines, lineno)
-        source_code = '\n'.join(lines)
-        pycore = self.project.get_pycore()
-        try:
-            current_scope = pycore.get_string_scope(source_code)
-        except SyntaxError, e:
-            raise RopeSyntaxError(e)
-        result = {}
-        inner_scope = current_scope
-        while current_scope is not None and \
-              (current_scope.get_kind() == 'Module' or
-               self._get_line_indents(lines, current_scope.get_lineno() - 1) < current_indents):
-            inner_scope = current_scope
-            for name, pyname in current_scope.get_names().iteritems():
-                if name.startswith(starting):
-                    from rope.pycore import PyObject
-                    kind = 'local'
-                    if current_scope.get_kind() == 'Module':
-                        kind = 'global'
-                    result[name] = CompletionProposal(name, kind)
-            new_scope = None
-            for scope in current_scope.get_scopes():
-                if scope.get_lineno() - 1 <= lineno:
-                    new_scope = scope
-                else:
-                    break
-            current_scope = new_scope
-        if '.' in starting:
-            tokens = starting.split('.')
-            element = inner_scope.lookup(tokens[0])
-            if element is not None:
-                consistent = True
-                for token in tokens[1:-1]:
-                    if token in element.get_attributes():
-                        element = element.get_attributes()[token]
-                    else:
-                        consistent = False
-                        break
-                if consistent:
-                    for name, pyname in element.get_attributes().iteritems():
-                        if name.startswith(tokens[-1]) or tokens[-1] == '':
-                            complete_name = '.'.join(tokens[:-1]) + '.' + name
-                            result[complete_name] = CompletionProposal(complete_name, 'attribute')
-        return result
-
     def add_template(self, name, definition):
         self.templates.append(TemplateProposal(name, Template(definition)))
 
                 result.append(template)
         return result
 
+    def _get_code_completions(self, source_code, offset, starting):
+        collector = _CodeCompletionCollector(self.project, source_code, offset, starting)
+        return collector.get_code_completions()
+
     def assist(self, source_code, offset):
         if offset > len(source_code):
             return Proposals([], [], 0, 0)
             completions.update(self._get_matching_builtins(starting))
             completions.update(self._get_matching_keywords(starting))
             templates = self._get_template_proposals(starting)
-        return Proposals(completions.values(), templates, starting_offset, offset)
+        return Proposals(completions.values(), templates,
+                         starting_offset, offset)
 

File rope/core.py

View file
         def do_create_package(source_folder, package_name):
             new_package = self.project.get_pycore().create_package(source_folder,
                                                                    package_name)
-            self.editor_manager.get_resource_editor(new_module)
+            self.editor_manager.get_resource_editor(new_package.get_child('__init__.py'))
         self._create_resource_dialog(do_create_package, 'Package', 'Source Folder')
         if event:
             return 'break'

File rope/pycore.py

View file
             if module.is_folder() and module.has_child(packages[-1]) and\
                module.get_child(packages[-1]).is_folder():
                 module = module.get_child(packages[-1])
-            elif module.is_folder() and module.has_child(packages[-1] + '.py') and\
-               not module.get_child(packages[-1] + '.py').is_folder():
+            elif module.is_folder() and \
+                 module.has_child(packages[-1] + '.py') and \
+                 not module.get_child(packages[-1] + '.py').is_folder():
                 module = module.get_child(packages[-1] + '.py')
             else:
                 found = False
                 result[self.parameters[0]] = PyName()
         if len(self.parameters) > 1:
             for parameter in self.parameters[1:]:
-                result[parameter] = PyName()            
+                result[parameter] = PyName()
         return result
 
 
     def _get_bases(self):
         result = []
         for base_name in self.ast_node.bases:
-            base = _AttributeListFinder.get_attribute(base_name, self.parent.get_scope())
+            base = _AttributeListFinder.get_attribute(base_name,
+                                                      self.parent.get_scope())
             if base:
                 result.append(base)
         return result
             for child in self.resource.get_children():
                 if child.is_folder():
                     attributes[child.get_name()] = PyName(self.pycore._create(child))
-                elif child.get_name().endswith('.py') and child.get_name() != '__init__.py':
+                elif child.get_name().endswith('.py') and \
+                     child.get_name() != '__init__.py':
                     name = child.get_name()[:-3]
                     attributes[name] = PyName(self.pycore._create(child))
             self.attributes = attributes
             return PyObject.get_base_type('Unknown')
 
     def _has_block(self):
-        return self.is_defined_here and isinstance(self.object, PyDefinedObject)
+        return self.is_defined_here and isinstance(self.object,
+                                                   PyDefinedObject)
     
     def _get_ast(self):
         return self.object._get_ast()
         return self.scopes
 
     def _create_scopes(self):
-        block_objects = [pyname.object for pyname in self.pyobject.get_attributes().values()
+        block_objects = [pyname.object for pyname in
+                         self.pyobject.get_attributes().values()
                          if pyname._has_block()]
         def block_compare(x, y):
             return cmp(x._get_ast().lineno, y._get_ast().lineno)
 class FunctionScope(Scope):
     
     def __init__(self, pycore, pyobject):
-        super(FunctionScope, self).__init__(pycore, pyobject, pyobject.parent.get_scope())
+        super(FunctionScope, self).__init__(pycore, pyobject,
+                                            pyobject.parent.get_scope())
         self.names = None
     
     def _get_names(self):
 class ClassScope(Scope):
 
     def __init__(self, pycore, pyobject):
-        super(ClassScope, self).__init__(pycore, pyobject, pyobject.parent.get_scope())
+        super(ClassScope, self).__init__(pycore, pyobject,
+                                         pyobject.parent.get_scope())
     
     def get_names(self):
         return {}
         self.owner_object = owner_object
     
     def visitClass(self, node):
-        self.names[node.name] = PyName(PyClass(self.pycore, node, self.owner_object), True)
+        self.names[node.name] = PyName(PyClass(self.pycore,
+                                               node, self.owner_object), True)
 
     def visitFunction(self, node):
         pyobject = PyFunction(self.pycore, node, self.owner_object)
                     self.names[tokens[0]] = PyName(pypkg)
                 for token in tokens[1:-1]:
                     if token in pypkg.get_attributes() and \
-                       isinstance(pypkg.get_attributes()[token].object, PyFilteredPackage):
+                       isinstance(pypkg.get_attributes()[token].object,
+                                  PyFilteredPackage):
                         newpkg = pypkg.get_attributes()[token].object
                     else:
                         newpkg = PyFilteredPackage()
         self.names[node.name] = PyName()
 
     def visitClass(self, node):
-        self.names[node.name] = PyName(PyClass(self.pycore, node, self.owner_object), True)
+        self.names[node.name] = PyName(PyClass(self.pycore, node,
+                                               self.owner_object), True)
 
 
 class _FunctionVisitor(_ScopeVisitor):