I took a stab at replacing rope with the jedi library, because it looks to 
be more promising based on the feature list at 
https://github.com/davidhalter/jedi.
The replacement is fully functional with support for completions, call 
tips, docstrings and goto definition.
However, it lacks two things found in the rope version: fast initial 
loading of large libraries like numpy and PyQt, and docstring support for 
functions with dynamic docstrings like functools.partial.  
Fixing these would take some similar patching as was done to the rope 
library, perhaps pushing back improvements to the jedi library.

What do you guys think, is this worth pursuing further?

I have attached the diff for the files affected:
spyderlib/widgets/sourcecode/codeeditor.py 
spyderlib/widgets/editor.py    
spyderlib/plugins/inspector.py


Regards,
Steve Silvester

-- 
You received this message because you are subscribed to the Google Groups 
"spyder" group.
To view this discussion on the web visit 
https://groups.google.com/d/msg/spyderlib/-/V5TQxcxhG4AJ.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/spyderlib?hl=en.

diff -r 266ea5795f3a spyderlib/plugins/inspector.py
--- a/spyderlib/plugins/inspector.py	Fri Dec 28 23:28:38 2012 -0600
+++ b/spyderlib/plugins/inspector.py	Sat Dec 29 14:25:51 2012 -0600
@@ -256,7 +256,7 @@
         # locked = disable link with Console
         self.locked = False
         self._last_texts = [None, None]
-        self._last_rope_data = None
+        self._last_jedi_data = None
         
         # Object name
         layout_edit = QHBoxLayout()
@@ -568,9 +568,9 @@
     def force_refresh(self):
         if self.source_is_console():
             self.set_object_text(None, force_refresh=True)
-        elif self._last_rope_data is not None:
-            text = self._last_rope_data
-            self.set_rope_doc(text, force_refresh=True)
+        elif self._last_jedi_data is not None:
+            text = self._last_jedi_data
+            self.set_jedi_doc(text, force_refresh=True)
     
     def set_object_text(self, text, force_refresh=False, ignore_unknown=False):
         """Set object analyzed by Object Inspector"""
@@ -598,14 +598,14 @@
         if self.dockwidget is not None:
             self.dockwidget.blockSignals(False)
         
-    def set_rope_doc(self, text, force_refresh=False):
+    def set_jedi_doc(self, text, force_refresh=False):
         """Use the object inspector to show text computed with rope
         from the editor plugin"""
         if (self.locked and not force_refresh):
             return
         self.switch_to_editor_source()
         
-        self._last_rope_data = text
+        self._last_jedi_data = text
         
         self.object_edit.setText(text['obj_text'])
         
diff -r 266ea5795f3a spyderlib/widgets/editor.py
--- a/spyderlib/widgets/editor.py	Fri Dec 28 23:28:38 2012 -0600
+++ b/spyderlib/widgets/editor.py	Sat Dec 29 14:25:51 2012 -0600
@@ -269,7 +269,7 @@
         self.newly_created = new
         self.encoding = encoding
         self.editor = editor
-        self.rope_project = codeeditor.get_rope_project()
+        self.jedi = codeeditor.Jedi()
         self.classes = (filename, None, None)
         self.analysis_results = []
         self.todo_results = []
@@ -332,9 +332,10 @@
                                              automatic=automatic)
             return
         else:
-            textlist = self.rope_project.get_completion_list(source_code,
+            textlist = self.jedi.get_completion_list(source_code,
                                                              offset,
                                                              self.filename)
+            import sys; print >> sys.stderr, textlist                                                
             if textlist:
                 completion_text = re.split(r"[^a-zA-Z0-9_]", text)[-1]
                 if text.lstrip().startswith('#') and text.endswith('.'):
@@ -356,7 +357,7 @@
         source_code = unicode(self.editor.toPlainText())
         offset = position
         
-        textlist = self.rope_project.get_calltip_text(source_code, offset,
+        textlist = self.jedi.get_calltip_text(source_code, offset,
                                                       self.filename)
         if not textlist:
             return
@@ -401,7 +402,7 @@
         """Go to definition"""
         source_code = unicode(self.editor.toPlainText())
         offset = position
-        fname, lineno = self.rope_project.get_definition_location(source_code,
+        fname, lineno = self.jedi.get_definition_location(source_code,
                                                     offset, self.filename)
         if fname is not None and lineno is not None:
             self.emit(SIGNAL("edit_goto(QString,int,QString)"),
@@ -735,7 +736,7 @@
             
     def inspect_current_object(self):
         """Inspect current object in Object Inspector"""
-        if programs.is_module_installed('rope'):
+        if programs.is_module_installed('jedi'):
             editor = self.get_current_editor()
             position = editor.get_position('cursor')
             finfo = self.get_current_finfo()
@@ -1347,7 +1348,6 @@
             finfo.editor.document().setModified(False)
             self.modification_changed(index=index)
             self.analyze_script(index)
-            codeeditor.validate_rope_project()
             
             #XXX CodeEditor-only: re-scan the whole text to rebuild outline 
             # explorer data from scratch (could be optimized because 
@@ -1685,7 +1685,6 @@
         finfo.editor.set_text(txt)
         finfo.editor.document().setModified(False)
         finfo.editor.set_cursor_position(position)
-        codeeditor.validate_rope_project()
         self._refresh_outlineexplorer(index, update=True, clear=True)
         
     def revert(self):
@@ -1803,7 +1802,7 @@
                 doc = unicode(qstr4)
                 text = {'obj_text': objtxt, 'title': title, 'argspec': argspec,
                         'note': note, 'doc': doc}
-                self.inspector.set_rope_doc(text, force_refresh=force)
+                self.inspector.set_jedi_doc(text, force_refresh=force)
             editor = self.get_current_editor()
             editor.setFocus()
     
diff -r 266ea5795f3a spyderlib/widgets/sourcecode/codeeditor.py
--- a/spyderlib/widgets/sourcecode/codeeditor.py	Fri Dec 28 23:28:38 2012 -0600
+++ b/spyderlib/widgets/sourcecode/codeeditor.py	Sat Dec 29 14:25:51 2012 -0600
@@ -56,165 +56,57 @@
 
 
 #===============================================================================
-# Code introspection features: rope integration
+# Code introspection features: jedi integration
 #===============================================================================
 try:
-    try:
-        from spyderlib import rope_patch
-        rope_patch.apply()
-    except ImportError:
-        # rope 0.9.2/0.9.3 is not installed
-        pass
-    import rope.base.libutils
-    import rope.contrib.codeassist
+    import jedi
 except ImportError:
     pass
+import sys
 
-#TODO: The following preferences should be customizable in the future
-ROPE_PREFS = {'ignore_syntax_errors': True,
-              'ignore_bad_imports': True,
-              'soa_followed_calls': 2,
-              'extension_modules': [
-        "PyQt4", "PyQt4.QtGui", "QtGui", "PyQt4.QtCore", "QtCore",
-        "PyQt4.QtScript", "QtScript", "os.path", "numpy", "scipy", "PIL",
-        "OpenGL", "array", "audioop", "binascii", "cPickle", "cStringIO",
-        "cmath", "collections", "datetime", "errno", "exceptions", "gc",
-        "imageop", "imp", "itertools", "marshal", "math", "mmap", "msvcrt",
-        "nt", "operator", "os", "parser", "rgbimg", "signal", "strop", "sys",
-        "thread", "time", "wx", "wxPython", "xxsubtype", "zipimport", "zlib"],
-              }
+class Jedi(object):
 
-class RopeProject(object):
-    def __init__(self):
-        self.project = None
-        self.create_rope_project(root_path=get_conf_path())
-
-    #------rope integration
-    def create_rope_project(self, root_path):
-        try:
-            import rope.base.project
-            self.project = rope.base.project.Project(
-                encoding.to_fs_from_unicode(root_path), **ROPE_PREFS)
-        except ImportError:
-            self.project = None
-            if DEBUG:
-                log_last_error(LOG_FILENAME,
-                               "create_rope_project: %r" % root_path)
-        except TypeError:
-            # Compatibility with new Mercurial API (>= 1.3).
-            # New versions of rope (> 0.9.2) already handle this issue
-            self.project = None
-            if DEBUG:
-                log_last_error(LOG_FILENAME,
-                               "create_rope_project: %r" % root_path)
-        self.validate_rope_project()
-
-    def close_rope_project(self):
-        if self.project is not None:
-            self.project.close()
-
-    def validate_rope_project(self):
-        if self.project is not None:
-            self.project.validate(self.project.root)
+    def get_script(self, source, offset, path=None):
+        lines = source[:offset].split('\n')
+        row = len(lines)
+        col = len(lines[-1])
+        return jedi.Script(source, row, col, path)
 
     def get_completion_list(self, source_code, offset, filename):
-        if self.project is None:
-            return []
-        try:
-            resource = rope.base.libutils.path_to_resource(self.project,
-                                                   filename.encode('utf-8'))
-        except Exception, _error:
-            if DEBUG:
-                log_last_error(LOG_FILENAME, "path_to_resource: %r" % filename)
-            resource = None
-        try:
-            if DEBUG:
-                t0 = time.time()
-            proposals = rope.contrib.codeassist.code_assist(self.project,
-                                    source_code, offset, resource, maxfixes=3)
-            proposals = rope.contrib.codeassist.sorted_proposals(proposals)
-            if DEBUG:
-                log_dt(LOG_FILENAME, "code_assist/sorted_proposals", t0)
-            return [proposal.name for proposal in proposals]
-        except Exception, _error:  #analysis:ignore
-            if DEBUG:
-                log_last_error(LOG_FILENAME, "get_completion_list")
-            return []
+        script = self.get_script(source_code, offset, filename)
+        completions = script.complete()
+        return [c.word for c in completions]
 
     def get_calltip_text(self, source_code, offset, filename):
-        if self.project is None:
-            return []
-        try:
-            resource = rope.base.libutils.path_to_resource(self.project,
-                                                   filename.encode('utf-8'))
-        except Exception, _error:
-            if DEBUG:
-                log_last_error(LOG_FILENAME, "path_to_resource: %r" % filename)
-            resource = None
-        try:
-            if DEBUG:
-                t0 = time.time()
-            cts = rope.contrib.codeassist.get_calltip(
-                            self.project, source_code, offset, resource,
-                            ignore_unknown=False, remove_self=True, maxfixes=3)
-            if DEBUG:
-                log_dt(LOG_FILENAME, "get_calltip", t0)
-            if cts is not None:
-                while '..' in cts:
-                    cts = cts.replace('..', '.')
+        script = self.get_script(source_code, offset + 1, filename)
+        call_def = script.get_in_function_call()
+        if call_def:
+            if hasattr(call_def.executable, 'base_func'):
+                base_func = call_def.executable.base_func
+                calltip = base_func.get_call_signature()
+                doc_text = base_func.doc
+            else:
                 try:
-                    doc_text = rope.contrib.codeassist.get_doc(self.project,
-                                    source_code, offset, resource, maxfixes=3)
-                    if DEBUG:
-                        log_dt(LOG_FILENAME, "get_doc", t0)
-                except Exception, _error:
+                    params = [p.get_code().replace('\n', '') for p in call_def.params]
+                except AttributeError:
+                    return []
+                try:
+                    params[call_def.index] = '*%s*' % params[call_def.index]
+                except (IndexError, TypeError):
+                    pass
+                calltip = " (%s) " % ', '.join(params)
+                if hasattr(call_def.executable, 'base'):
+                    doc_text = call_def.executable.base.docstr
+                else:
                     doc_text = ''
-                    if DEBUG:
-                        log_last_error(LOG_FILENAME, "get_doc")
-                return [cts, doc_text]
-            else:
-                return []
-        except Exception, _error:  #analysis:ignore
-            if DEBUG:
-                log_last_error(LOG_FILENAME, "get_calltip_text")
-            return []
+            return [calltip, doc_text]
 
     def get_definition_location(self, source_code, offset, filename):
-        if self.project is None:
-            return (None, None)
-        try:
-            resource = rope.base.libutils.path_to_resource(self.project,
-                                                   filename.encode('utf-8'))
-        except Exception, _error:
-            if DEBUG:
-                log_last_error(LOG_FILENAME, "path_to_resource: %r" % filename)
-            resource = None
-        try:
-            if DEBUG:
-                t0 = time.time()
-            resource, lineno = rope.contrib.codeassist.get_definition_location(
-                    self.project, source_code, offset, resource, maxfixes=3)
-            if DEBUG:
-                log_dt(LOG_FILENAME, "get_definition_location", t0)
-            if resource is not None:
-                filename = resource.real_path
-            return filename, lineno
-        except Exception, _error:  #analysis:ignore
-            if DEBUG:
-                log_last_error(LOG_FILENAME, "get_definition_location")
-            return (None, None)
-
-ROPE_PROJECT = None
-def get_rope_project():
-    """Create a single rope project"""
-    global ROPE_PROJECT
-    if ROPE_PROJECT is None:
-        ROPE_PROJECT = RopeProject()
-    return ROPE_PROJECT
-
-def validate_rope_project():
-    """Validate rope project"""
-    get_rope_project().validate_rope_project()
+        script = self.get_script(source_code, offset, filename)
+        goto = script.goto()
+        if goto:
+            defn = goto[0]
+            return [defn.module_path, defn.line_nr]
 
 
 #===============================================================================

Reply via email to