Commits

Anonymous committed 4cb368e

Removing imports from the same module

Comments (0)

Files changed (25)

docs/dev/done.txt

 ===========
 
 
+- Removing imports from the same module : December 22, 2006
+
+
+- Goto last edit location; ``C-q`` : December 20, 2006
+
+
+- Trying ``utf-8`` if defaults don't work : December 19, 2006
+
+
+- Comment line and region; ``C-c c``, ``C-c C-c`` : December 18, 2006
+
+
 > Public Release 0.4m3 : December 17, 2006
 
 

docs/dev/issues.txt

 Hot Topics
 ==========
 
+* `Having Virtual PyModules`_
 * `Library Movement`_
 
 
 * Undo Unification
 
 
+Refactoring Import Utils
+========================
+
+The special thing about `importutils` module is that it is used by
+other refactorings on an already changed module.  Most of the time
+its methods return the changed source code of a module.
+
+
+Having Virtual `PyModule`\s
+===========================
+
+What Do We Gain?
+----------------
+
+After this refactoring we'll be able to mix any of the refactorings
+and move toward bigger refactorings.
+
+* Handling import changes for all modules
+
+  One of the problems we are facing when performing refactorings is
+  that imports need to be changed in some of the modules involved in
+  that refactoring, but since those modules are already changed for
+  that refactoring, it cannot be changed once more easily.
+
+* Performing multiple refactorings in sequence
+
+  For example for performing move method refactoring we can rename the
+  self parameter of the method and then move the method itself.  Then
+  move the imports used.
+
+* Support for bigger refactorings
+
+  Examples:
+
+
+Consequences
+------------
+
+* Complexity of implementation
+
+  After a change the internal representation of project should be
+  updated.  This requires for example changes to
+  `Folder.get_children` and `File.read`.
+
+* Inefficiency because of multiple changes while refactoring
+
+  If in a refactoring we perform ``3`` changes we might also need to
+  recompute the information calculated in some of the `PyModule`\s
+  ``3`` times.  This seems inefficient.
+
+* Inefficiency due to missing computed information computed locally
+
+  One of the difficulties of having a set of main and many local
+  `PyModule`\s is that when we compute some information in local
+  ones this information might be no longer valid in global pymodules.
+  
+
+* Not convincing uses
+
+  Right know the only need for performing multiple refactorings is
+  for changing imports after performing a refactoring and it has been
+  handled using some kind of virtual `PyModule` already.  We could
+  not think of any good refactoring that needs to perform multiple
+  basic refactorings.
+
+* Lots of changes
+  
+  This refactoring needs lots of changes to `PyCore` and modules that
+  use it.  Some of these changes need to change the design very much.
+
+* Complex design
+
+  Managing multiple `PyCore`\s and changing `PyObject`\s to work with
+  many of them at the same time seems to be hard.
+
+
+A Bit in Details
+----------------
+
+If we change all refactoring modules to work on a pymodule we would
+probably be able to have a structure like this::
+  
+  class PythonProjectFiles(object):
+      
+      def resource_to_pyobject(self, resource):
+          pass
+      
+      def get_module(self, module_name):
+          pass
+  
+  
+  class ChangedPythonProjectFiles(PythonProjectFiles):
+      
+      def resource_to_pyobject(self, resource):
+          pass
+      
+      def get_module(self, module_name):
+          pass
+      
+      def file_changed(self, resource, new_content):
+          pass
+      
+      def move_resource(self, resource, new_location):
+          pass
+
+      def get_changes(self):
+          pass
+          
+
+This way we can perform any number of refactorings in sequence.  But
+it seems as if we should break `PyCore` into two separate classes.
+What's more after this change we would probably be able to remove
+`rope.refactor.change`.  Maybe we can add `Change...` interfaces to 
+`ChangedPythonProjectFiles`.
+
+One of the main obstacles for doing this change is that all of the
+`PyObjects` have a reference to the `PyCore` and ask them about
+other modules.  This causes problems because we want all of the
+`PyObjects` to use a local `PyCore`.
+
+What's more computed information might be valid in a local scope
+but might be invalid in the rest of project.
+
+
+Preventing Unnecessary Recomputations
+-------------------------------------
+
+* Using copy on write
+* Updating global `PyCore` after performing changes
+
+
+Gradual Implementation
+----------------------
+
+Since this would be a big refactoring we need to divide this
+refactoring into a few smaller tasks.
+
+
+Simple Implementation
+---------------------
+
+A simple implementation would be possible by using plain and new
+`PythonProjectFiles` from the beginning.  But the performance might
+really hurt if we actually use it but it won't change if we don't.
+So actually it is a extract class refactorings.
+
+
 Updating Changed Files
 ======================
 

docs/dev/stories.txt

 * Having multiple clipboards
 * Auto-importing modules
 * Adding tool bar
-* Last edit location; C-q
 * Replacement; M-%
 * Remembering last open project
 * Enhancing searching
 * ``global`` keyword issues for pycore
 
 
-* Removing imports from the same module
-
-
 * Sorting imports; standard, third party, project
 
 
 
 
 * Moving fields/methods to attribute classes
-
-
-* Extending and having more control on file reading and writing

docs/dev/workingon.txt

-Small Stories
-=============
+Removing Imports From The Same Module
+=====================================
 
-- Adding ``refresh project`` to file menu
-- Commenting current line and region ``C-c c`` and ``C-c C-c``
-- Supporting ReST comments
+- Adding `rope.base.fscommands.FileAccess` class
+- Refactoring `importutils` module
+- Moving operations from `ImportInfo`\s to visitors
+- Adding `rope.refactor.importutils` package; needs changing ``setup.py``
+- Not using `ImportInfoVisitor.dispatch` in favor of `ImportStatement.accept`
+- Changing organize imports to use `ImportTools.organize_imports`
+- Organizing imports after move refactorings
+- What if there is no dot after ``mod``
+
+* Importing star
+* Definition location for variables inside loops
+* Choosing better names for import related modules
+  visitors -> actions
+* Adding tools to ease working on long files
+* Supporting ``from w.x.y import z`` for organizing long imports;
+  ``Use long import list format`` refactoring
 
 
 Remaining Stories
 =================
 
+* Allowing running code to call rope functions while rope is running?
+* Extract constant
 * Method inlining and pydocs
 * What to do if a file is removed while editing
 * Allowing non-existent resources?
-* Using utf-8 as default encoding for files?
-* Problems for inside generator assists
+* Problems for inside list comprehension assists
 * Performing actions on individual functions or imports
 * `PyClass.superclasses` should be concluded data
 * Better `ropetest` package structure
 * Decide when to use `difflib` in `Editor.set_text`
 * Handling `AssList` for inline variable and encapsulate field
-* Changing `Rename` to take an `OccuranceFinder`
+* Changing `Rename` to take an `OccurrenceFinder`
 * ``break`` and ``continue`` in extract method
 
 * Considering logical lines in `rope.codeanalyze`
     * Change relative imports to absolute
     * Change from imports to normal imports
     * Expand ``from ... import *`` imports
-    * Remove unused and duplicate imports(organize imports)
+    * Remove unused, duplicate and self imports (organize imports)
 
 * Auto-completion
 

docs/user/overview.txt

 M-c            capitalize word
 M-u            upcase word
 C-s            start searching
+C-q            last edit location
 C-x C-u        undo
 C-x C-r        redo
 C-space        set mark

rope/base/fscommands.py

 
 import os
 import shutil
+import re
+
 
 try:
     import pysvn
     
     def remove(self, path):
         self.client.remove(path, force=True)
+
+
+class FileAccess(object):
+    
+    def read(self, path):
+        """Read the content of the file at `path`.
+        
+        Returns a `Unicode` object
+        """
+        source_bytes = open(path).read()
+        return self._file_data_to_unicode(source_bytes)
+    
+    def _file_data_to_unicode(self, data):
+        encoding = self._conclude_file_encoding(data)
+        if encoding is not None:
+            return unicode(data, encoding)
+        try:
+            return unicode(data)
+        except UnicodeDecodeError:
+            # Using ``utf-8`` if guessed encoding fails
+            return unicode(data, 'utf-8')
+    
+    def _find_line_end(self, source_bytes, start):
+        try:
+            return source_bytes.index('\n', start)
+        except ValueError:
+            return len(source_bytes)
+    
+    def _get_second_line_end(self, source_bytes):
+        line1_end = self._find_line_end(source_bytes, 0)
+        if line1_end != len(source_bytes):
+            return self._find_line_end(source_bytes, line1_end)
+        else:
+            return line1_end
+    
+    encoding_pattern = re.compile(r'coding[=:]\s*([-\w.]+)')
+    
+    def _conclude_file_encoding(self, source_bytes):
+        first_two_lines = source_bytes[:self._get_second_line_end(source_bytes)]
+        match = FileAccess.encoding_pattern.search(first_two_lines)
+        if match is not None:
+            return match.group(1)
+
+    def write(self, path, contents):
+        """Write the `contents` to the file at `path`.
+        
+        contents should be a `Unicode` object.
+        """
+        file_ = open(path, 'w')
+        encoding = self._conclude_file_encoding(contents)
+        if encoding is not None and isinstance(contents, unicode):
+            contents = contents.encode(encoding)
+        try:
+            file_.write(contents)
+        except UnicodeEncodeError:
+            # Using ``utf-8`` if guessed encoding fails
+            file_.write(contents.encode('utf-8'))
+        file_.close()
+

rope/base/project.py

 
     def __init__(self, project, name):
         super(File, self).__init__(project, name)
+        self.file_access = rope.base.fscommands.FileAccess()
     
     def read(self):
-        source_bytes = open(self._get_real_path()).read()
-        return self._file_data_to_unicode(source_bytes)
+        return self.file_access.read(self._get_real_path())
     
-    def _file_data_to_unicode(self, data):
-        encoding = self._conclude_file_encoding(data)
-        if encoding is not None:
-            return unicode(data, encoding)
-        return unicode(data)
-    
-    def _find_line_end(self, source_bytes, start):
-        try:
-            return source_bytes.index('\n', start)
-        except ValueError:
-            return len(source_bytes)
-    
-    def _get_second_line_end(self, source_bytes):
-        line1_end = self._find_line_end(source_bytes, 0)
-        if line1_end != len(source_bytes):
-            return self._find_line_end(source_bytes, line1_end)
-        else:
-            return line1_end
-    
-    encoding_pattern = re.compile(r'coding[=:]\s*([-\w.]+)')
-    
-    def _conclude_file_encoding(self, source_bytes):
-        first_two_lines = source_bytes[:self._get_second_line_end(source_bytes)]
-        match = File.encoding_pattern.search(first_two_lines)
-        if match is not None:
-            return match.group(1)
-
     def write(self, contents):
-        file_ = open(self._get_real_path(), 'w')
-        encoding = self._conclude_file_encoding(contents)
-        if encoding is not None and isinstance(contents, unicode):
-            contents = contents.encode(encoding)
-        file_.write(contents)
-        file_.close()
+        self.file_access.write(self._get_real_path(), contents)
         for observer in list(self.observers):
             observer.resource_changed(self)
 
         return True
 
     def get_children(self):
-        """Returns the children of this folder"""
+        """Return the children of this folder"""
         path = self._get_real_path()
         result = []
         content = os.listdir(path)

rope/refactor/__init__.py

 refactorings.
 
 """
-import rope.refactor.importutils
+import rope.refactor.importutils.module_imports
 from rope.refactor.change import (ChangeSet, ChangeContents,
                                   MoveResource, CreateFolder)
 from rope.refactor.rename import RenameRefactoring
             changes.add_change(ChangeContents(resource, source))
             self.refactoring.add_and_commit_changes(changes)
 
+    def organize_imports(self, resource):
+        pymodule = self.pycore.resource_to_pyobject(resource)
+        result = self.import_tools.organize_imports(pymodule)
+        if result is not None:
+            changes = ChangeSet()
+            changes.add_change(ChangeContents(resource, result))
+            self.refactoring.add_and_commit_changes(changes)
+
     def expand_star_imports(self, resource):
         source = self._perform_command_on_module_with_imports(
-            resource, rope.refactor.importutils.ModuleWithImports.expand_stars)
+            resource, rope.refactor.importutils.module_imports.ModuleImports.expand_stars)
         if source is not None:
             changes = ChangeSet()
             changes.add_change(ChangeContents(resource, source))

rope/refactor/importutils.py

-import compiler
-
-import rope.base.pynames
-import rope.base.exceptions
-import rope.refactor.rename
-
-
-class ImportTools(object):
-    
-    def __init__(self, pycore):
-        self.pycore = pycore
-    
-    def get_import_for_module(self, module):
-        module_name = ImportTools.get_module_name(self.pycore, module.get_resource())
-        return NormalImport(((module_name, None), ))
-    
-    def get_from_import_for_module(self, module, name):
-        module_name = ImportTools.get_module_name(self.pycore, module.get_resource())
-        return FromImport(module_name, 0, ((name, None),),
-                          module.get_resource().get_parent(), self.pycore)
-
-    def get_module_with_imports(self, module):
-        return ModuleWithImports(self.pycore, module)
-    
-    def transform_froms_to_normal_imports(self, pymodule):
-        resource = pymodule.get_resource()
-        pymodule = self._clean_up_imports(pymodule)
-        module_with_imports = self.get_module_with_imports(pymodule)
-        for import_stmt in module_with_imports.get_import_statements():
-            if not self._can_import_be_transformed_to_normal_import(import_stmt.import_info):
-                continue
-            pymodule = self._from_to_normal(pymodule, import_stmt)
-        
-        # Adding normal imports in place of froms
-        module_with_imports = self.get_module_with_imports(pymodule)
-        for import_stmt in module_with_imports.get_import_statements():
-            if self._can_import_be_transformed_to_normal_import(import_stmt.import_info):
-                import_stmt.import_info = \
-                    NormalImport(((import_stmt.import_info.module_name, None),))
-        module_with_imports.remove_duplicates()
-        return module_with_imports.get_changed_source()
-
-    def _from_to_normal(self, pymodule, import_stmt):
-        resource = pymodule.get_resource()
-        from_import = import_stmt.import_info
-        module_name = from_import.module_name
-        imported_pymodule = self.pycore.get_module(module_name)
-        for name, alias in from_import.names_and_aliases:
-            imported = name
-            if alias is not None:
-                imported = alias
-            rename_in_module = rope.refactor.rename.RenameInModule(
-                self.pycore, [imported_pymodule.get_attribute(name)], imported,
-                module_name + '.' + name, replace_primary=True, imports=False)
-            source = rename_in_module.get_changed_module(pymodule=pymodule)
-            if source is not None:
-                pymodule = self.pycore.get_string_module(source, resource)
-        return pymodule
-
-    def _clean_up_imports(self, pymodule):
-        resource = pymodule.get_resource()
-        module_with_imports = self.get_module_with_imports(pymodule)
-        module_with_imports.expand_stars()
-        source = module_with_imports.get_changed_source()
-        if source is not None:
-            pymodule = self.pycore.get_string_module(source, resource)
-        source = self.transform_relative_imports_to_absolute(pymodule)
-        if source is not None:
-            pymodule = self.pycore.get_string_module(source, resource)
-
-        module_with_imports = self.get_module_with_imports(pymodule)
-        module_with_imports.remove_duplicates()
-        module_with_imports.remove_unused_imports()
-        source = module_with_imports.get_changed_source()
-        if source is not None:
-            pymodule = self.pycore.get_string_module(source, resource)
-        return pymodule
-    
-    def transform_relative_imports_to_absolute(self, pymodule):
-        module_with_imports = self.get_module_with_imports(pymodule)
-        to_be_absolute_list = module_with_imports.get_relative_to_absolute_list()
-        source = module_with_imports.get_changed_source()
-        if source is not None:
-            pymodule = self.pycore.get_string_module(source, pymodule.get_resource())
-        for name, absolute_name in to_be_absolute_list:
-            old_name = name.split('.')[-1]
-            old_pyname = rope.base.codeanalyze.StatementEvaluator.get_string_result(
-                pymodule.get_scope(), name)
-            rename_in_module = rope.refactor.rename.RenameInModule(
-                self.pycore, [old_pyname], old_name,
-                absolute_name, replace_primary=True, imports=False)
-            source = rename_in_module.get_changed_module(pymodule=pymodule)
-            if source is not None:
-                pymodule = self.pycore.get_string_module(source, pymodule.get_resource())
-        return pymodule.source_code
-    
-    def _can_import_be_transformed_to_normal_import(self, import_info):
-        if not isinstance(import_info, FromImport):
-            return False
-        return True
-
-    @staticmethod
-    def get_module_name(pycore, resource):
-        if resource.is_folder():
-            module_name = resource.get_name()
-            source_folder = resource.get_parent()
-        elif resource.get_name() == '__init__.py':
-            module_name = resource.get_parent().get_name()
-            source_folder = resource.get_parent().get_parent()
-        else:
-            module_name = resource.get_name()[:-3]
-            source_folder = resource.get_parent()
-
-        source_folders = pycore.get_source_folders()
-        source_folders.extend(pycore.get_python_path_folders())
-        while source_folder != source_folder.get_parent() and \
-              source_folder not in source_folders:
-            module_name = source_folder.get_name() + '.' + module_name
-            source_folder = source_folder.get_parent()
-        return module_name
-
-
-class ModuleWithImports(object):
-    
-    def __init__(self, pycore, pymodule):
-        self.pycore = pycore
-        self.pymodule = pymodule
-        self.import_statements = None
-    
-    def get_import_statements(self):
-        if self.import_statements is None:
-            self.import_statements = _GlobalImportFinder(self.pymodule,
-                                                         self.pycore).\
-                                     find_import_statements()
-        return self.import_statements
-    
-    def _get_unbound_names(self, defined_pyobject):
-        visitor = _GlobalUnboundNameFinder(self.pymodule, defined_pyobject)
-        compiler.walk(self.pymodule._get_ast(), visitor)
-        return visitor.unbound
-    
-    def remove_unused_imports(self):
-        can_select = _OneTimeSelector(self._get_unbound_names(self.pymodule))
-        for import_statement in self.get_import_statements():
-            import_statement.filter_names(can_select)
-    
-    def get_used_imports(self, defined_pyobject):
-        all_import_statements = self.get_import_statements()
-        result = []
-        can_select = _OneTimeSelector(self._get_unbound_names(defined_pyobject))
-        for import_statement in all_import_statements:
-            new_import = import_statement.import_info.filter_names(can_select)
-            if new_import is not None and not new_import.is_empty():
-                result.append(new_import)
-        return result
-
-    def get_changed_source(self):
-        lines = self.pymodule.source_code.splitlines(True)
-        result = []
-        last_index = 0
-        for import_statement in self.get_import_statements():
-            start = import_statement.start_line - 1
-            result.extend(lines[last_index:start])
-            last_index = import_statement.end_line - 1
-            if not import_statement.import_info.is_empty():
-                result.append(import_statement.get_import_statement() + '\n')
-        result.extend(lines[last_index:])
-        return ''.join(result)
-    
-    def add_import(self, import_info):
-        for import_statement in self.get_import_statements():
-            if import_statement.add_import(import_info):
-                break
-        else:
-            all_imports = self.get_import_statements()
-            last_line = 1
-            if all_imports:
-                last_line = all_imports[-1].end_line
-            all_imports.append(ImportStatement(import_info, last_line, last_line))
-    
-    def filter_names(self, can_select):
-        all_import_statements = self.get_import_statements()
-        for import_statement in all_import_statements:
-            new_import = import_statement.filter_names(can_select)
-    
-    def expand_stars(self):
-        can_select = _OneTimeSelector(self._get_unbound_names(self.pymodule))
-        for import_statement in self.get_import_statements():
-            import_statement.expand_star(can_select)
-    
-    def remove_duplicates(self):
-        imports = self.get_import_statements()
-        added_imports = []
-        for import_stmt in imports:
-            for added_import in added_imports:
-                if added_import.add_import(import_stmt.import_info):
-                    import_stmt.empty_import()
-            else:
-                added_imports.append(import_stmt)
-    
-    def get_relative_to_absolute_list(self):
-        visitor = _ImportInfoRelativeToAbsoluteVisitor(
-            self.pycore, self.pymodule.get_resource().get_parent())
-        for import_stmt in self.get_import_statements():
-            import_stmt.accept_visitor(visitor)
-        return visitor.to_be_absolute
-
-
-class _ImportInfoRelativeToAbsoluteVisitor(object):
-    
-    def __init__(self, pycore, current_folder):
-        self.to_be_absolute = []
-        self.pycore = pycore
-        self.current_folder = current_folder
-    
-    def visitFromImport(self, import_info):
-        return import_info.relative_to_absolute(self.pycore, self.current_folder)
-    
-    def visitNormalImport(self, import_info):
-        self.to_be_absolute.extend(import_info.get_relative_to_absolute_list(
-                                   self.pycore, self.current_folder))
-        return import_info.relative_to_absolute(
-            self.pycore, self.current_folder)
-    
-    def visitEmptyImport(self, import_info):
-        pass
-    
-    def dispatch(self, import_info):
-        method = getattr(self, 'visit' + import_info.__class__.__name__)
-        return method(import_info)
-
-
-class _OneTimeSelector(object):
-    
-    def __init__(self, names):
-        self.names = names
-        self.selected_names = set()
-    
-    def __call__(self, imported_primary):
-        if self._can_name_be_added(imported_primary):
-            for name in self._get_dotted_tokens(imported_primary):
-                self.selected_names.add(name)
-            return True
-        return False
-    
-    def _get_dotted_tokens(self, imported_primary):
-        tokens = imported_primary.split('.')
-        for i in range(len(tokens)):
-            yield '.'.join(tokens[:i + 1])
-    
-    def _can_name_be_added(self, imported_primary):
-        for name in self._get_dotted_tokens(imported_primary):
-            if name in self.names and name not in self.selected_names:
-                return True
-        return False
-
-
-class ImportStatement(object):
-    
-    def __init__(self, import_info, start_line, end_line, main_statement=None):
-        self.start_line = start_line
-        self.end_line = end_line
-        self.main_statement = main_statement
-        self._import_info = None
-        self.import_info = import_info
-        self.is_changed = False
-    
-    def _get_import_info(self):
-        return self._import_info
-    
-    def _set_import_info(self, new_import):
-        if new_import is not None and not new_import == self._import_info:
-            self.is_changed = True
-            self._import_info = new_import
-    
-    import_info = property(_get_import_info, _set_import_info)
-    
-    def filter_names(self, can_select):
-        try:
-            new_import = self.import_info.filter_names(can_select)
-            self.import_info = new_import
-        except rope.base.exceptions.ModuleNotFoundException:
-            pass
-    
-    def add_import(self, import_info):
-        result = self.import_info.add_import(import_info)
-        if result is not None:
-            self.import_info = result
-            return True
-        return False
-    
-    def get_import_statement(self):
-        if self.is_changed or self.main_statement is None:
-            return self.import_info.get_import_statement()
-        else:
-            return self.main_statement
-    
-    def expand_star(self, can_select):
-        if isinstance(self.import_info, FromImport) and \
-           self.import_info.is_star_import():
-            self.import_info = self.import_info.expand_star(can_select)
-        else:
-            for primary in self.import_info.get_imported_primaries():
-                can_select(primary)
-    
-    def empty_import(self):
-        self.import_info = ImportInfo.get_empty_import()
-    
-    def accept_visitor(self, visitor):
-        self.import_info = visitor.dispatch(self.import_info)
-
-
-class ImportInfo(object):
-    
-    def get_imported_primaries(self):
-        pass
-    
-    def get_imported_names(self):
-        return [primary.split('.')[0]
-                for primary in self.get_imported_primaries()]
-    
-    def get_import_statement(self):
-        pass
-    
-    def filter_names(self, can_select):
-        def can_select_name_and_alias(name, alias):
-            imported = name
-            if alias:
-                imported = alias
-            return can_select(imported)
-        return self.filter_names_and_aliases(can_select_name_and_alias)
-    
-    def is_empty(self):
-        pass
-    
-    def add_import(self, import_info):
-        pass
-    
-    def __hash__(self):
-        return hash(self.get_import_statement())
-    
-    def _are_name_and_alias_lists_equal(self, list1, list2):
-        if len(list1) != len(list2):
-            return False
-        for pair1, pair2 in zip(list1, list2):
-            if pair1 != pair2:
-                return False
-        return True
-    
-    def __eq__(self, obj):
-        return isinstance(obj, self.__class__) and \
-               self.get_import_statement() == obj.get_import_statement()
-
-    @staticmethod
-    def get_empty_import():
-        class EmptyImport(ImportInfo):
-            names_and_aliases = []
-            def is_empty(self):
-                return True
-            def get_imported_primaries(self):
-                return []
-        return EmptyImport()
-    
-
-class NormalImport(ImportInfo):
-    
-    def __init__(self, names_and_aliases):
-        self.names_and_aliases = names_and_aliases
-    
-    def get_imported_primaries(self):
-        result = []
-        for name, alias in self.names_and_aliases:
-            if alias:
-                result.append(alias)
-            else:
-                result.append(name)
-        return result
-    
-    def get_import_statement(self):
-        result = 'import '
-        for name, alias in self.names_and_aliases:
-            result += name
-            if alias:
-                result += ' as ' + alias
-            result += ', '
-        return result[:-2]
-    
-    def filter_names_and_aliases(self, can_select):
-        new_pairs = []
-        for name, alias in self.names_and_aliases:
-            if can_select(name, alias):
-                new_pairs.append((name, alias))
-        return NormalImport(new_pairs)
-    
-    def is_empty(self):
-        return len(self.names_and_aliases) == 0
-
-    def add_import(self, import_info):
-        if not isinstance(import_info, self.__class__):
-            return None
-        # Adding ``import x`` and ``import x.y`` that results ``import x.y``
-        if len(self.names_and_aliases) == len(import_info.names_and_aliases) == 1:
-            imported1 = self.names_and_aliases[0]
-            imported2 = import_info.names_and_aliases[0]
-            if imported1[1] == imported2[1] == None:
-                if imported1[0].startswith(imported2[0] + '.'):
-                    return self
-                if imported2[0].startswith(imported1[0] + '.'):
-                    return import_info
-        # Multiple imports using a single import statement is discouraged
-        # so we won't bother adding them.
-        if self._are_name_and_alias_lists_equal(self.names_and_aliases,
-                                                import_info.names_and_aliases):
-            return self
-        return None
-    
-    def relative_to_absolute(self, pycore, current_folder):
-        new_pairs = []
-        for name, alias in self.names_and_aliases:
-            resource = pycore.find_module(name, current_folder=current_folder)
-            if resource is None:
-                new_pairs.append((name, alias))
-                continue
-            absolute_name = ImportTools.get_module_name(pycore, resource)
-            new_pairs.append((absolute_name, alias))
-        if not self._are_name_and_alias_lists_equal(new_pairs, self.names_and_aliases):
-            return NormalImport(new_pairs)
-        return None
-    
-    def get_relative_to_absolute_list(self, pycore, current_folder):
-        result = []
-        for name, alias in self.names_and_aliases:
-            if alias is not None:
-                continue
-            resource = pycore.find_module(name, current_folder=current_folder)
-            if resource is None:
-                continue
-            absolute_name = ImportTools.get_module_name(pycore, resource)
-            if absolute_name != name:
-                result.append((name, absolute_name))
-        return result
-    
-
-class FromImport(ImportInfo):
-    
-    def __init__(self, module_name, level, names_and_aliases, current_folder, pycore):
-        self.module_name = module_name
-        self.level = level
-        self.names_and_aliases = names_and_aliases
-        self.current_folder = current_folder
-        self.pycore = pycore
-
-    def get_imported_primaries(self):
-        if self.names_and_aliases[0][0] == '*':
-            module = self.get_imported_module()
-            return [name for name in module.get_attributes().keys()
-                    if not name.startswith('_')]
-        result = []
-        for name, alias in self.names_and_aliases:
-            if alias:
-                result.append(alias)
-            else:
-                result.append(name)
-        return result
-    
-    def get_imported_module(self):
-        if self.level == 0:
-            return self.pycore.get_module(self.module_name,
-                                          self.current_folder)
-        else:
-            return self.pycore.get_relative_module(self.module_name,
-                                                   self.current_folder,
-                                                   self.level)
-    
-    def get_import_statement(self):
-        result = 'from ' + '.' * self.level + self.module_name + ' import '
-        for name, alias in self.names_and_aliases:
-            result += name
-            if alias:
-                result += ' as ' + alias
-            result += ', '
-        return result[:-2]
-
-    def filter_names_and_aliases(self, can_select):
-        new_pairs = []
-        if self.names_and_aliases and self.names_and_aliases[0][0] == '*':
-            for name in self.get_imported_names():
-                if can_select(name, None):
-                    new_pairs.append(self.names_and_aliases[0])
-                    break
-        else:
-            for name, alias in self.names_and_aliases:
-                if can_select(name, alias):
-                    new_pairs.append((name, alias))
-        return FromImport(self.module_name, self.level, new_pairs,
-                          self.current_folder, self.pycore)
-    
-    def is_empty(self):
-        return len(self.names_and_aliases) == 0
-    
-    def add_import(self, import_info):
-        if isinstance(import_info, self.__class__) and \
-           self.module_name == import_info.module_name and \
-           self.level == import_info.level:
-            if self.is_star_import():
-                return self
-            if import_info.is_star_import():
-                return import_info
-            new_pairs = list(self.names_and_aliases)
-            for pair in import_info.names_and_aliases:
-                if pair not in new_pairs:
-                    new_pairs.append(pair)
-            return FromImport(self.module_name, self.level, new_pairs,
-                              self.current_folder, self.pycore)
-        return None
-    
-    def is_star_import(self):
-        return len(self.names_and_aliases) > 0 and self.names_and_aliases[0][0] == '*'
-    
-    def expand_star(self, can_select):
-        if not self.is_star_import():
-            return None
-        new_pairs = []
-        for name in self.get_imported_names():
-            new_pairs.append((name, None))
-        new_import = FromImport(self.module_name, self.level, new_pairs,
-                                self.current_folder, self.pycore)
-        return new_import.filter_names(can_select)
-
-    def relative_to_absolute(self, pycore, current_folder):
-        if self.level == 0:
-            resource = pycore.find_module(self.module_name,
-                                          current_folder=current_folder)
-        else:
-            resource = pycore.find_relative_module(
-                self.module_name, current_folder, self.level)
-        if resource is None:
-            return None
-        absolute_name = ImportTools.get_module_name(pycore, resource)
-        if self.module_name != absolute_name:
-            return FromImport(absolute_name, 0, self.names_and_aliases,
-                              current_folder, pycore)
-        return None
-    
-
-class _UnboundNameFinder(object):
-    
-    def __init__(self, pyobject):
-        self.pyobject = pyobject
-    
-    def _visit_child_scope(self, node):
-        pyobject = self.pyobject.get_module().get_scope().\
-                   get_inner_scope_for_line(node.lineno).pyobject
-        visitor = _LocalUnboundNameFinder(pyobject, self)
-        for child in node.getChildNodes():
-            compiler.walk(child, visitor)
-    
-    def visitFunction(self, node):
-        self._visit_child_scope(node)
-
-    def visitClass(self, node):
-        self._visit_child_scope(node)
-    
-    def visitName(self, node):
-        if self._get_root()._is_node_interesting(node) and not self.is_bound(node.name):
-            self.add_unbound(node.name)
-    
-    def visitGetattr(self, node):
-        result = []
-        while isinstance(node, compiler.ast.Getattr):
-            result.append(node.attrname)
-            node = node.expr
-        if isinstance(node, compiler.ast.Name):
-            result.append(node.name)
-            primary = '.'.join(reversed(result))
-            if self._get_root()._is_node_interesting(node) and not self.is_bound(primary):
-                self.add_unbound(primary)
-        else:
-            compiler.walk(node, self)
-
-    def _get_root(self):
-        pass
-    
-    def is_bound(self, name):
-        pass
-    
-    def add_unbound(self, name):
-        pass
-    
-
-class _GlobalUnboundNameFinder(_UnboundNameFinder):
-    
-    def __init__(self, pymodule, wanted_pyobject):
-        super(_GlobalUnboundNameFinder, self).__init__(pymodule)
-        self.unbound = set()
-        self.names = set()
-        for name, pyname in pymodule._get_structural_attributes().iteritems():
-            if not isinstance(pyname, (rope.base.pynames.ImportedName,
-                                       rope.base.pynames.ImportedModule)):
-                self.names.add(name)
-        wanted_scope = wanted_pyobject.get_scope()
-        self.start = wanted_scope.get_start()
-        self.end = wanted_scope.get_end() + 1
-    
-    def _get_root(self):
-        return self
-    
-    def is_bound(self, primary):
-        name = primary.split('.')[0]
-        if name in self.names:
-            return True
-        return False
-    
-    def add_unbound(self, name):
-        names = name.split('.')
-        for i in range(len(names)):
-            self.unbound.add('.'.join(names[:i + 1]))
-
-    def _is_node_interesting(self, node):
-        start = self.start
-        end = self.end
-        return start <= node.lineno < end
-
-
-class _LocalUnboundNameFinder(_UnboundNameFinder):
-    
-    def __init__(self, pyobject, parent):
-        super(_LocalUnboundNameFinder, self).__init__(pyobject)
-        self.parent = parent
-    
-    def _get_root(self):
-        return self.parent._get_root()
-    
-    def is_bound(self, primary):
-        name = primary.split('.')[0]
-        if name in self.pyobject.get_scope().get_names() or \
-           self.parent.is_bound(name):
-            return True
-        return False
-    
-    def add_unbound(self, name):
-        self.parent.add_unbound(name)
-
-
-class _GlobalImportFinder(object):
-    
-    def __init__(self, pymodule, pycore):
-        self.current_folder = None
-        if pymodule.get_resource():
-            self.current_folder = pymodule.get_resource().get_parent()
-            self.pymodule = pymodule
-        self.pycore = pycore
-        self.imports = []
-        self.lines = self.pymodule.lines
-    
-    def visit_import(self, node, end_line):
-        start_line = node.lineno
-        import_statement = ImportStatement(NormalImport(node.names),
-                                           start_line, end_line,
-                                           self._get_text(start_line, end_line))
-        self.imports.append(import_statement)
-    
-    def _get_text(self, start_line, end_line):
-        result = []
-        for index in range(start_line, end_line):
-            result.append(self.lines.get_line(index))
-        return '\n'.join(result)
-
-    def visit_from(self, node, end_line):
-        level = 0
-        if hasattr(node, 'level'):
-            level = node.level
-        import_info = FromImport(node.modname, level, node.names,
-                                 self.current_folder, self.pycore)
-        start_line = node.lineno
-        self.imports.append(ImportStatement(import_info, node.lineno, end_line,
-                                            self._get_text(start_line, end_line)))
-    
-    def find_import_statements(self):
-        nodes = self.pymodule._get_ast().node.nodes
-        for index, node in enumerate(nodes):
-            if isinstance(node, (compiler.ast.Import, compiler.ast.From)):
-                end_line = self.lines.length() + 1
-                if index + 1 < len(nodes):
-                    end_line = nodes[index + 1].lineno
-                while self.lines.get_line(end_line - 1).strip() == '':
-                    end_line -= 1
-            if isinstance(node, compiler.ast.Import):
-                self.visit_import(node, end_line)
-            if isinstance(node, compiler.ast.From):
-                self.visit_from(node, end_line)
-        return self.imports

rope/refactor/importutils/__init__.py

+"""A package for handling imports
+
+This package provides tools for modifing module imports after
+refactorings or as a separate task.
+
+"""
+
+
+import rope.base.pynames
+import rope.refactor.rename
+
+from rope.refactor.importutils.importinfo import \
+     (NormalImport, FromImport, get_module_name)
+from rope.refactor.importutils import module_imports
+
+
+class ImportTools(object):
+    
+    def __init__(self, pycore):
+        self.pycore = pycore
+    
+    def get_import_for_module(self, module):
+        module_name = get_module_name(self.pycore, module.get_resource())
+        return NormalImport(((module_name, None), ))
+    
+    def get_from_import_for_module(self, module, name):
+        module_name = get_module_name(self.pycore, module.get_resource())
+        return FromImport(module_name, 0, ((name, None),),
+                          module.get_resource().get_parent(), self.pycore)
+
+    def get_module_with_imports(self, module):
+        return module_imports.ModuleImports(self.pycore, module)
+    
+    def transform_froms_to_normal_imports(self, pymodule):
+        resource = pymodule.get_resource()
+        pymodule = self._clean_up_imports(pymodule)
+        module_with_imports = self.get_module_with_imports(pymodule)
+        for import_stmt in module_with_imports.get_import_statements():
+            if not self._can_import_be_transformed_to_normal_import(import_stmt.import_info):
+                continue
+            pymodule = self._from_to_normal(pymodule, import_stmt)
+        
+        # Adding normal imports in place of froms
+        module_with_imports = self.get_module_with_imports(pymodule)
+        for import_stmt in module_with_imports.get_import_statements():
+            if self._can_import_be_transformed_to_normal_import(import_stmt.import_info):
+                import_stmt.import_info = \
+                    NormalImport(((import_stmt.import_info.module_name, None),))
+        module_with_imports.remove_duplicates()
+        return module_with_imports.get_changed_source()
+
+    def _from_to_normal(self, pymodule, import_stmt):
+        resource = pymodule.get_resource()
+        from_import = import_stmt.import_info
+        module_name = from_import.module_name
+        imported_pymodule = self.pycore.get_module(module_name)
+        for name, alias in from_import.names_and_aliases:
+            imported = name
+            if alias is not None:
+                imported = alias
+            rename_in_module = rope.refactor.rename.RenameInModule(
+                self.pycore, [imported_pymodule.get_attribute(name)], imported,
+                module_name + '.' + name, replace_primary=True, imports=False)
+            source = rename_in_module.get_changed_module(pymodule=pymodule)
+            if source is not None:
+                pymodule = self.pycore.get_string_module(source, resource)
+        return pymodule
+
+    def _clean_up_imports(self, pymodule):
+        resource = pymodule.get_resource()
+        module_with_imports = self.get_module_with_imports(pymodule)
+        module_with_imports.expand_stars()
+        source = module_with_imports.get_changed_source()
+        if source is not None:
+            pymodule = self.pycore.get_string_module(source, resource)
+        source = self.transform_relative_imports_to_absolute(pymodule)
+        if source is not None:
+            pymodule = self.pycore.get_string_module(source, resource)
+
+        module_with_imports = self.get_module_with_imports(pymodule)
+        module_with_imports.remove_duplicates()
+        module_with_imports.remove_unused_imports()
+        source = module_with_imports.get_changed_source()
+        if source is not None:
+            pymodule = self.pycore.get_string_module(source, resource)
+        return pymodule
+    
+    def transform_relative_imports_to_absolute(self, pymodule):
+        module_with_imports = self.get_module_with_imports(pymodule)
+        to_be_absolute_list = module_with_imports.get_relative_to_absolute_list()
+        source = module_with_imports.get_changed_source()
+        if source is not None:
+            pymodule = self.pycore.get_string_module(source, pymodule.get_resource())
+        for name, absolute_name in to_be_absolute_list:
+            pymodule = self._rename_in_module(pymodule, name, absolute_name)
+        return pymodule.source_code
+    
+    def _can_import_be_transformed_to_normal_import(self, import_info):
+        if not isinstance(import_info, FromImport):
+            return False
+        return True
+    
+    def organize_imports(self, pymodule):
+        module_with_imports = self.get_module_with_imports(pymodule)
+        module_with_imports.remove_unused_imports()
+        module_with_imports.remove_duplicates()
+        before_removing_self_import = module_with_imports.get_changed_source()
+        to_be_fixed, to_be_renamed = module_with_imports.get_self_import_fix_and_rename_list()
+        source = module_with_imports.get_changed_source()
+        if source is not None:
+            pymodule = self.pycore.get_string_module(source, pymodule.get_resource())
+        for name in to_be_fixed:
+            try:
+                pymodule = self._rename_in_module(pymodule, name, '', till_dot=True)
+            except ValueError:
+                # There is a self import with direct access to it
+                return before_removing_self_import
+        for name, new_name in to_be_renamed:
+            pymodule = self._rename_in_module(pymodule, name, new_name)
+        return pymodule.source_code
+
+    def _rename_in_module(self, pymodule, name, new_name, till_dot=False):
+        old_name = name.split('.')[-1]
+        old_pyname = rope.base.codeanalyze.StatementEvaluator.get_string_result(
+            pymodule.get_scope(), name)
+        occurrence_finder = rope.refactor.occurrences.FilteredOccurrenceFinder(
+            self.pycore, old_name, [old_pyname], imports=False)
+        changes = rope.refactor.sourceutils.ChangeCollector(pymodule.source_code)
+        for occurrence in occurrence_finder.find_occurrences(pymodule=pymodule):
+            start, end = occurrence.get_primary_range()
+            if till_dot:
+                new_end = pymodule.source_code.index('.', end) + 1
+                space = pymodule.source_code[end:new_end - 1].strip()
+                if not space == '':
+                    for c in space:
+                        if not c.isspace() and c not in '\\':
+                            raise ValueError()
+                end = new_end
+            changes.add_change(start, end, new_name)
+        source = changes.get_changed()
+        if source is not None:
+            pymodule = self.pycore.get_string_module(source, pymodule.get_resource())
+        return pymodule

rope/refactor/importutils/importinfo.py

+class ImportStatement(object):
+    
+    def __init__(self, import_info, start_line, end_line, main_statement=None):
+        self.start_line = start_line
+        self.end_line = end_line
+        self.main_statement = main_statement
+        self._import_info = None
+        self.import_info = import_info
+        self.is_changed = False
+    
+    def _get_import_info(self):
+        return self._import_info
+    
+    def _set_import_info(self, new_import):
+        if new_import is not None and not new_import == self._import_info:
+            self.is_changed = True
+            self._import_info = new_import
+    
+    import_info = property(_get_import_info, _set_import_info)
+    
+    def get_import_statement(self):
+        if self.is_changed or self.main_statement is None:
+            return self.import_info.get_import_statement()
+        else:
+            return self.main_statement
+    
+    def empty_import(self):
+        self.import_info = ImportInfo.get_empty_import()
+    
+    def accept(self, visitor):
+        return visitor.dispatch(self)
+
+
+class ImportInfo(object):
+    
+    def get_imported_primaries(self):
+        pass
+    
+    def get_imported_names(self):
+        return [primary.split('.')[0]
+                for primary in self.get_imported_primaries()]
+    
+    def get_import_statement(self):
+        pass
+    
+    def is_empty(self):
+        pass
+    
+    def __hash__(self):
+        return hash(self.get_import_statement())
+    
+    def _are_name_and_alias_lists_equal(self, list1, list2):
+        if len(list1) != len(list2):
+            return False
+        for pair1, pair2 in zip(list1, list2):
+            if pair1 != pair2:
+                return False
+        return True
+    
+    def __eq__(self, obj):
+        return isinstance(obj, self.__class__) and \
+               self.get_import_statement() == obj.get_import_statement()
+
+    @staticmethod
+    def get_empty_import():
+        return EmptyImport()
+    
+
+class NormalImport(ImportInfo):
+    
+    def __init__(self, names_and_aliases):
+        self.names_and_aliases = names_and_aliases
+    
+    def get_imported_primaries(self):
+        result = []
+        for name, alias in self.names_and_aliases:
+            if alias:
+                result.append(alias)
+            else:
+                result.append(name)
+        return result
+    
+    def get_import_statement(self):
+        result = 'import '
+        for name, alias in self.names_and_aliases:
+            result += name
+            if alias:
+                result += ' as ' + alias
+            result += ', '
+        return result[:-2]
+    
+    def is_empty(self):
+        return len(self.names_and_aliases) == 0
+
+
+class FromImport(ImportInfo):
+    
+    def __init__(self, module_name, level, names_and_aliases, current_folder, pycore):
+        self.module_name = module_name
+        self.level = level
+        self.names_and_aliases = names_and_aliases
+        self.current_folder = current_folder
+        self.pycore = pycore
+
+    def get_imported_primaries(self):
+        if self.names_and_aliases[0][0] == '*':
+            module = self.get_imported_module()
+            return [name for name in module.get_attributes().keys()
+                    if not name.startswith('_')]
+        result = []
+        for name, alias in self.names_and_aliases:
+            if alias:
+                result.append(alias)
+            else:
+                result.append(name)
+        return result
+    
+    def get_imported_module(self):
+        if self.level == 0:
+            return self.pycore.get_module(self.module_name,
+                                          self.current_folder)
+        else:
+            return self.pycore.get_relative_module(
+                self.module_name, self.current_folder, self.level)
+    
+    def get_import_statement(self):
+        result = 'from ' + '.' * self.level + self.module_name + ' import '
+        for name, alias in self.names_and_aliases:
+            result += name
+            if alias:
+                result += ' as ' + alias
+            result += ', '
+        return result[:-2]
+
+    def is_empty(self):
+        return len(self.names_and_aliases) == 0
+    
+    def is_star_import(self):
+        return len(self.names_and_aliases) > 0 and self.names_and_aliases[0][0] == '*'
+    
+
+class EmptyImport(ImportInfo):
+    
+    names_and_aliases = []
+    
+    def is_empty(self):
+        return True
+    
+    def get_imported_primaries(self):
+        return []
+
+
+def get_module_name(pycore, resource):
+    if resource.is_folder():
+        module_name = resource.get_name()
+        source_folder = resource.get_parent()
+    elif resource.get_name() == '__init__.py':
+        module_name = resource.get_parent().get_name()
+        source_folder = resource.get_parent().get_parent()
+    else:
+        module_name = resource.get_name()[:-3]
+        source_folder = resource.get_parent()
+
+    source_folders = pycore.get_source_folders()
+    source_folders.extend(pycore.get_python_path_folders())
+    while source_folder != source_folder.get_parent() and \
+          source_folder not in source_folders:
+        module_name = source_folder.get_name() + '.' + module_name
+        source_folder = source_folder.get_parent()
+    return module_name

rope/refactor/importutils/module_imports.py

+import compiler
+
+import rope.base.pynames
+from rope.refactor.importutils import importinfo
+from rope.refactor.importutils import visitors
+
+
+class ModuleImports(object):
+    
+    def __init__(self, pycore, pymodule):
+        self.pycore = pycore
+        self.pymodule = pymodule
+        self.import_statements = None
+    
+    def get_import_statements(self):
+        if self.import_statements is None:
+            self.import_statements = _GlobalImportFinder(self.pymodule,
+                                                         self.pycore).\
+                                     find_import_statements()
+        return self.import_statements
+    
+    def _get_unbound_names(self, defined_pyobject):
+        visitor = _GlobalUnboundNameFinder(self.pymodule, defined_pyobject)
+        compiler.walk(self.pymodule._get_ast(), visitor)
+        return visitor.unbound
+    
+    def remove_unused_imports(self):
+        can_select = _OneTimeSelector(self._get_unbound_names(self.pymodule))
+        visitor = visitors.RemovingVisitor(self.pycore, can_select)
+        for import_statement in self.get_import_statements():
+            import_statement.accept(visitor)
+    
+    def get_used_imports(self, defined_pyobject):
+        all_import_statements = self.get_import_statements()
+        result = []
+        can_select = _OneTimeSelector(self._get_unbound_names(defined_pyobject))
+        visitor = visitors.FilteringVisitor(self.pycore, can_select)
+        for import_statement in all_import_statements:
+            new_import = import_statement.accept(visitor)
+            if new_import is not None and not new_import.is_empty():
+                result.append(new_import)
+        return result
+
+    def get_changed_source(self):
+        lines = self.pymodule.source_code.splitlines(True)
+        result = []
+        last_index = 0
+        for import_statement in self.get_import_statements():
+            start = import_statement.start_line - 1
+            result.extend(lines[last_index:start])
+            last_index = import_statement.end_line - 1
+            if not import_statement.import_info.is_empty():
+                result.append(import_statement.get_import_statement() + '\n')
+        result.extend(lines[last_index:])
+        return ''.join(result)
+    
+    def add_import(self, import_info):
+        visitor = visitors.AddingVisitor(self.pycore, import_info)
+        for import_statement in self.get_import_statements():
+            if import_statement.accept(visitor):
+                break
+        else:
+            all_imports = self.get_import_statements()
+            last_line = 1
+            if all_imports:
+                last_line = all_imports[-1].end_line
+            all_imports.append(importinfo.ImportStatement(import_info, last_line, last_line))
+    
+    def filter_names(self, can_select):
+        visitor = visitors.RemovingVisitor(self.pycore, can_select)
+        for import_statement in self.get_import_statements():
+            import_statement.accept(visitor)
+    
+    def expand_stars(self):
+        can_select = _OneTimeSelector(self._get_unbound_names(self.pymodule))
+        visitor = visitors.ExpandStarsVisitor(self.pycore, can_select)
+        for import_statement in self.get_import_statements():
+            import_statement.accept(visitor)
+    
+    def remove_duplicates(self):
+        imports = self.get_import_statements()
+        added_imports = []
+        for import_stmt in imports:
+            visitor = visitors.AddingVisitor(self.pycore, import_stmt.import_info)
+            for added_import in added_imports:
+                if added_import.accept(visitor):
+                    import_stmt.empty_import()
+            else:
+                added_imports.append(import_stmt)
+    
+    def get_relative_to_absolute_list(self):
+        visitor = rope.refactor.importutils.visitors.RelativeToAbsoluteVisitor(
+            self.pycore, self.pymodule.get_resource().get_parent())
+        for import_stmt in self.get_import_statements():
+            import_stmt.accept(visitor)
+        return visitor.to_be_absolute
+
+    def get_self_import_fix_and_rename_list(self):
+        visitor = rope.refactor.importutils.visitors.SelfImportVisitor(
+            self.pycore, self.pymodule.get_resource().get_parent(),
+            self.pymodule.get_resource())
+        for import_stmt in self.get_import_statements():
+            import_stmt.accept(visitor)
+        return visitor.to_be_fixed, visitor.to_be_renamed
+
+
+class _OneTimeSelector(object):
+    
+    def __init__(self, names):
+        self.names = names
+        self.selected_names = set()
+    
+    def __call__(self, imported_primary):
+        if self._can_name_be_added(imported_primary):
+            for name in self._get_dotted_tokens(imported_primary):
+                self.selected_names.add(name)
+            return True
+        return False
+    
+    def _get_dotted_tokens(self, imported_primary):
+        tokens = imported_primary.split('.')
+        for i in range(len(tokens)):
+            yield '.'.join(tokens[:i + 1])
+    
+    def _can_name_be_added(self, imported_primary):
+        for name in self._get_dotted_tokens(imported_primary):
+            if name in self.names and name not in self.selected_names:
+                return True
+        return False
+
+
+class _UnboundNameFinder(object):
+    
+    def __init__(self, pyobject):
+        self.pyobject = pyobject
+    
+    def _visit_child_scope(self, node):
+        pyobject = self.pyobject.get_module().get_scope().\
+                   get_inner_scope_for_line(node.lineno).pyobject
+        visitor = _LocalUnboundNameFinder(pyobject, self)
+        for child in node.getChildNodes():
+            compiler.walk(child, visitor)
+    
+    def visitFunction(self, node):
+        self._visit_child_scope(node)
+
+    def visitClass(self, node):
+        self._visit_child_scope(node)
+    
+    def visitName(self, node):
+        if self._get_root()._is_node_interesting(node) and not self.is_bound(node.name):
+            self.add_unbound(node.name)
+    
+    def visitGetattr(self, node):
+        result = []
+        while isinstance(node, compiler.ast.Getattr):
+            result.append(node.attrname)
+            node = node.expr
+        if isinstance(node, compiler.ast.Name):
+            result.append(node.name)
+            primary = '.'.join(reversed(result))
+            if self._get_root()._is_node_interesting(node) and not self.is_bound(primary):
+                self.add_unbound(primary)
+        else:
+            compiler.walk(node, self)
+
+    def _get_root(self):
+        pass
+    
+    def is_bound(self, name):
+        pass
+    
+    def add_unbound(self, name):
+        pass
+    
+
+class _GlobalUnboundNameFinder(_UnboundNameFinder):
+    
+    def __init__(self, pymodule, wanted_pyobject):
+        super(_GlobalUnboundNameFinder, self).__init__(pymodule)
+        self.unbound = set()
+        self.names = set()
+        for name, pyname in pymodule._get_structural_attributes().iteritems():
+            if not isinstance(pyname, (rope.base.pynames.ImportedName,
+                                       rope.base.pynames.ImportedModule)):
+                self.names.add(name)
+        wanted_scope = wanted_pyobject.get_scope()
+        self.start = wanted_scope.get_start()
+        self.end = wanted_scope.get_end() + 1
+    
+    def _get_root(self):
+        return self
+    
+    def is_bound(self, primary):
+        name = primary.split('.')[0]
+        if name in self.names:
+            return True
+        return False
+    
+    def add_unbound(self, name):
+        names = name.split('.')
+        for i in range(len(names)):
+            self.unbound.add('.'.join(names[:i + 1]))
+
+    def _is_node_interesting(self, node):
+        start = self.start
+        end = self.end
+        return start <= node.lineno < end
+
+
+class _LocalUnboundNameFinder(_UnboundNameFinder):
+    
+    def __init__(self, pyobject, parent):
+        super(_LocalUnboundNameFinder, self).__init__(pyobject)
+        self.parent = parent
+    
+    def _get_root(self):
+        return self.parent._get_root()
+    
+    def is_bound(self, primary):
+        name = primary.split('.')[0]
+        if name in self.pyobject.get_scope().get_names() or \
+           self.parent.is_bound(name):
+            return True
+        return False
+    
+    def add_unbound(self, name):
+        self.parent.add_unbound(name)
+
+
+class _GlobalImportFinder(object):
+    
+    def __init__(self, pymodule, pycore):
+        self.current_folder = None
+        if pymodule.get_resource():
+            self.current_folder = pymodule.get_resource().get_parent()
+            self.pymodule = pymodule
+        self.pycore = pycore
+        self.imports = []
+        self.lines = self.pymodule.lines
+    
+    def visit_import(self, node, end_line):
+        start_line = node.lineno
+        import_statement = importinfo.ImportStatement(
+            importinfo.NormalImport(node.names),start_line, end_line,
+            self._get_text(start_line, end_line))
+        self.imports.append(import_statement)
+    
+    def _get_text(self, start_line, end_line):
+        result = []
+        for index in range(start_line, end_line):
+            result.append(self.lines.get_line(index))
+        return '\n'.join(result)
+
+    def visit_from(self, node, end_line):
+        level = 0
+        if hasattr(node, 'level'):
+            level = node.level
+        import_info = importinfo.FromImport(node.modname, level, node.names,
+                                            self.current_folder, self.pycore)
+        start_line = node.lineno
+        self.imports.append(importinfo.ImportStatement(import_info, node.lineno, end_line,
+                                                       self._get_text(start_line, end_line)))
+    
+    def find_import_statements(self):
+        nodes = self.pymodule._get_ast().node.nodes
+        for index, node in enumerate(nodes):
+            if isinstance(node, (compiler.ast.Import, compiler.ast.From)):
+                end_line = self.lines.length() + 1
+                if index + 1 < len(nodes):
+                    end_line = nodes[index + 1].lineno
+                while self.lines.get_line(end_line - 1).strip() == '':
+                    end_line -= 1
+            if isinstance(node, compiler.ast.Import):
+                self.visit_import(node, end_line)
+            if isinstance(node, compiler.ast.From):
+                self.visit_from(node, end_line)
+        return self.imports

rope/refactor/importutils/visitors.py

+from rope.base import pyobjects
+from rope.base import exceptions
+from rope.refactor.importutils import importinfo
+
+
+class ImportInfoVisitor(object):
+    
+    def dispatch(self, import_):
+        method = getattr(self, 'visit' + import_.import_info.__class__.__name__)
+        return method(import_, import_.import_info)
+
+    def visitEmptyImport(self, import_stmt, import_info):
+        pass
+    
+    def visitNormalImport(self, import_stmt, import_info):
+        pass
+    
+    def visitFromImport(self, import_stmt, import_info):
+        pass
+    
+
+class RelativeToAbsoluteVisitor(ImportInfoVisitor):
+    
+    def __init__(self, pycore, current_folder):
+        self.to_be_absolute = []
+        self.pycore = pycore
+        self.current_folder = current_folder
+    
+    def visitNormalImport(self, import_stmt, import_info):
+        self.to_be_absolute.extend(self._get_relative_to_absolute_list(import_info))
+        new_pairs = []
+        for name, alias in import_info.names_and_aliases:
+            resource = self.pycore.find_module(name, current_folder=self.current_folder)
+            if resource is None:
+                new_pairs.append((name, alias))
+                continue
+            absolute_name = importinfo.get_module_name(self.pycore, resource)
+            new_pairs.append((absolute_name, alias))
+        if not import_info._are_name_and_alias_lists_equal(
+            new_pairs, import_info.names_and_aliases):
+            import_stmt.import_info = importinfo.NormalImport(new_pairs)
+    
+    def _get_relative_to_absolute_list(self, import_info):
+        result = []
+        for name, alias in import_info.names_and_aliases:
+            if alias is not None:
+                continue
+            resource = self.pycore.find_module(name, current_folder=self.current_folder)
+            if resource is None:
+                continue
+            absolute_name = importinfo.get_module_name(self.pycore, resource)
+            if absolute_name != name:
+                result.append((name, absolute_name))
+        return result
+    
+    def visitFromImport(self, import_stmt, import_info):
+        if import_info.level == 0:
+            resource = self.pycore.find_module(import_info.module_name,
+                                               current_folder=self.current_folder)
+        else:
+            resource = self.pycore.find_relative_module(
+                import_info.module_name, self.current_folder, import_info.level)
+        if resource is None:
+            return None
+        absolute_name = importinfo.get_module_name(self.pycore, resource)
+        if import_info.module_name != absolute_name:
+            import_stmt.import_info = importinfo.FromImport(
+                absolute_name, 0, import_info.names_and_aliases,
+                self.current_folder, self.pycore)
+    
+
+class FilteringVisitor(ImportInfoVisitor):
+    
+    def __init__(self, pycore, can_select):
+        self.to_be_absolute = []
+        self.pycore = pycore
+        self.can_select = self._transform_can_select(can_select)
+    
+    def _transform_can_select(self, can_select):
+        def can_select_name_and_alias(name, alias):
+            imported = name
+            if alias is not None:
+                imported = alias
+            return can_select(imported)
+        return can_select_name_and_alias
+    
+    def visitNormalImport(self, import_stmt, import_info):
+        new_pairs = []
+        for name, alias in import_info.names_and_aliases:
+            if self.can_select(name, alias):
+                new_pairs.append((name, alias))
+        return importinfo.NormalImport(new_pairs)
+
+    def visitFromImport(self, import_stmt, import_info):
+        new_pairs = []
+        if import_info.is_star_import():
+            for name in import_info.get_imported_names():
+                if self.can_select(name, None):
+                    new_pairs.append(import_info.names_and_aliases[0])
+                    break
+        else:
+            for name, alias in import_info.names_and_aliases:
+                if self.can_select(name, alias):
+                    new_pairs.append((name, alias))
+        return importinfo.FromImport(
+            import_info.module_name, import_info.level, new_pairs,
+            import_info.current_folder, self.pycore)
+
+
+class RemovingVisitor(ImportInfoVisitor):
+    
+    def __init__(self, pycore, can_select):
+        self.to_be_absolute = []
+        self.pycore = pycore
+        self.filtering = FilteringVisitor(pycore, can_select)
+    
+    def dispatch(self, import_):
+        result = self.filtering.dispatch(import_)
+        if result is not None:
+            import_.import_info = result
+
+
+class AddingVisitor(ImportInfoVisitor):
+    
+    def __init__(self, pycore, import_info):
+        self.pycore = pycore
+        self.import_info = import_info
+    
+    def visitNormalImport(self, import_stmt, import_info):
+        if not isinstance(self.import_info, import_info.__class__):
+            return False
+        # Adding ``import x`` and ``import x.y`` that results ``import x.y``
+        if len(import_info.names_and_aliases) == len(self.import_info.names_and_aliases) == 1:
+            imported1 = import_info.names_and_aliases[0]
+            imported2 = self.import_info.names_and_aliases[0]
+            if imported1[1] == imported2[1] == None:
+                if imported1[0].startswith(imported2[0] + '.'):
+                    return True
+                if imported2[0].startswith(imported1[0] + '.'):
+                    import_stmt.import_info = self.import_info
+                    return True
+        # Multiple imports using a single import statement is discouraged
+        # so we won't bother adding them.
+        if self.import_info._are_name_and_alias_lists_equal(
+            import_info.names_and_aliases, self.import_info.names_and_aliases):
+            return True
+    
+    def visitFromImport(self, import_stmt, import_info):
+        if isinstance(self.import_info, import_info.__class__) and \
+           import_info.module_name == self.import_info.module_name and \
+           import_info.level == self.import_info.level:
+            if import_info.is_star_import():
+                return True
+            if self.import_info.is_star_import():
+                import_stmt.import_info = self.import_info
+                return True
+            new_pairs = list(import_info.names_and_aliases)
+            for pair in self.import_info.names_and_aliases:
+                if pair not in new_pairs:
+                    new_pairs.append(pair)
+            import_stmt.import_info = importinfo.FromImport(
+                import_info.module_name, import_info.level, new_pairs,
+                import_info.current_folder, import_info.pycore)
+            return True
+
+
+class ExpandStarsVisitor(ImportInfoVisitor):
+    
+    def __init__(self, pycore, can_select):
+        self.pycore = pycore
+        self.filtering = FilteringVisitor(pycore, can_select)
+
+    def visitNormalImport(self, import_stmt, import_info):
+        self.filtering.dispatch(import_stmt)
+
+    def visitFromImport(self, import_stmt, import_info):
+        if import_info.is_star_import():
+            new_pairs = []
+            for name in import_info.get_imported_names():
+                new_pairs.append((name, None))
+            new_import = importinfo.FromImport(
+                import_info.module_name, import_info.level, new_pairs,
+                import_info.current_folder, self.pycore)
+            import_stmt.import_info = self.filtering.visitFromImport(None, new_import)
+        else:
+            self.filtering.dispatch(import_stmt)
+
+
+class SelfImportVisitor(ImportInfoVisitor):
+    
+    def __init__(self, pycore, current_folder, resource):
+        self.pycore = pycore
+        self.current_folder = current_folder
+        self.resource = resource
+        self.to_be_fixed = set()
+        self.to_be_renamed = set()
+    
+    def visitNormalImport(self, import_stmt, import_info):
+        new_pairs = []
+        for name, alias in import_info.names_and_aliases:
+            resource = self.pycore.find_module(name, current_folder=self.current_folder)
+            if resource is not None and resource == self.resource:
+                imported = name
+                if alias is not None:
+                    imported = alias
+                self.to_be_fixed.add(imported)
+            else:
+                new_pairs.append((name, alias))
+        if not import_info._are_name_and_alias_lists_equal(
+            new_pairs, import_info.names_and_aliases):
+            import_stmt.import_info = importinfo.NormalImport(new_pairs)
+    
+    def visitFromImport(self, import_stmt, import_info):
+        if import_info.level == 0:
+            resource = self.pycore.find_module(import_info.module_name,
+                                               current_folder=self.current_folder)
+        else:
+            resource = self.pycore.find_relative_module(
+                import_info.module_name, self.current_folder, import_info.level)
+        if resource is None:
+            return
+        if resource == self.resource:
+            self._importing_names_from_self(import_info, import_stmt)
+            return
+        pymodule = self.pycore.resource_to_pyobject(resource)
+        new_pairs = []
+        for name, alias in import_info.names_and_aliases:
+            try:
+                result = pymodule.get_attribute(name).get_object()
+                if isinstance(result, pyobjects.PyModule) and \
+                   result.get_resource() == self.resource:
+                    imported = name
+                    if alias is not None:
+                        imported = alias
+                    self.to_be_fixed.add(imported)
+                else:
+                    new_pairs.append((name, alias))
+            except exceptions.AttributeNotFoundException:
+                new_pairs.append((name, alias))
+        if not import_info._are_name_and_alias_lists_equal(
+            new_pairs, import_info.names_and_aliases):
+            import_stmt.import_info = importinfo.FromImport(
+                import_info.module_name, import_info.level, new_pairs,
+                import_info.current_folder, self.pycore)
+
+    def _importing_names_from_self(self, import_info, import_stmt):
+        if not import_info.is_star_import():
+            for name, alias in import_info.names_and_aliases:
+                if alias is not None:
+                    self.to_be_renamed.add((alias, name))
+        import_stmt.empty_import()

rope/refactor/move.py

         old_name = pyname.get_object()._get_ast().name
         pymodule = pyname.get_object().get_module()
         source = pymodule.get_resource()
-        new_name = rope.refactor.importutils.ImportTools.get_module_name(
+        new_name = rope.refactor.importutils.get_module_name(
             pycore, destination) + '.' + old_name
         if destination.is_folder() and destination.has_child('__init__.py'):
             destination = destination.get_child('__init__.py')
             start = -1
         result += moving + '\n' + source[start + 1:]
         
-        changes.add_change(ChangeContents(self.destination, result))
+        # Organizing imports
+        source = result
+        pymodule = self.pycore.get_string_module(source, self.destination)
+        source = self.import_tools.organize_imports(pymodule)
+        changes.add_change(ChangeContents(self.destination, source))
     
     def _get_moving_element_with_imports(self):
         moving = self._get_moving_element()
             old_name = source.get_name()
         else:
             old_name = source.get_name()[:-3]
-        package = rope.refactor.importutils.ImportTools.get_module_name(pycore, destination)
+        package = rope.refactor.importutils.get_module_name(pycore, destination)
         if package:
             new_name = package + '.' + old_name
         else:

rope/ui/editactions.py

     line_entry.bind('<Escape>', cancel)
     toplevel.grid()
     line_entry.focus_set()
+
+def goto_last_edit_location(context):
+    context.get_core().get_editor_manager().goto_last_edit_location()
     
 
 core = rope.ui.core.Core.get_core()
                             MenuAddress(['Edit', 'Emacs Paste'], 'p'), ['all']))
 actions.append(SimpleAction('Goto Line', goto_line, None,
                             MenuAddress(['Edit', 'Goto Line'], 'g'), ['all']))
+actions.append(SimpleAction('Goto Last Edit Location', goto_last_edit_location, 'C-q',
+                            MenuAddress(['Edit', 'Goto Last Edit Location'], 'e'), ['all']))
 actions.append(SimpleAction('Undo', undo, 'C-x u',
                             MenuAddress(['Edit', 'Undo'], 'u', 1), ['all']))
 actions.append(SimpleAction('Redo', redo, 'C-x r',

rope/ui/editor.py

         self._bind_keys()
         self.status_bar_manager = None
         self.modification_observers = []
+        self.change_observers = []
         self.modified_flag = False
         self.text.bind('<<Modified>>', self._editor_modified)
         self.text.edit_modified(False)
         start, end = self.change_inspector.get_changed_region()
         self._colorize(start, end)
         self.change_inspector.clear_changed()
+        if self.modified_flag:
+            for observer in self.change_observers:
+                observer(end)
 
     def _colorize(self, start, end):
-        start_offset, end_offset = self.highlighting.\
-                                   get_suspected_region_after_change(self.get_text(),
-                                                                     self.get_offset(start),
-                                                                     self.get_offset(end))
+        start_offset, end_offset = \