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]
#===============================================================================