[gnome-builder/wip/elad/jedi-gir-docs] jedi: show documentation next to completion suggestions



commit e3ad3e6a443bd057e9264e0d761ff67770ff1f57
Author: Elad Alfassa <elad fedoraproject org>
Date:   Thu Oct 22 17:52:45 2015 +0300

    jedi: show documentation next to completion suggestions

 plugins/jedi/jedi_plugin.py |  101 ++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 100 insertions(+), 1 deletions(-)
---
diff --git a/plugins/jedi/jedi_plugin.py b/plugins/jedi/jedi_plugin.py
index db51a18..6cdcac5 100644
--- a/plugins/jedi/jedi_plugin.py
+++ b/plugins/jedi/jedi_plugin.py
@@ -19,8 +19,14 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
-
 import gi
+# lxml is faster than the Python standard library xml, and can ignore invalid
+# characters (which occur in some .gir files).
+# gnome-code-assistance also needs lxml, so I think it's okay to use it here.
+import lxml.etree
+import os
+import os.path
+import sqlite3
 gi.require_version('Gtk', '3.0')
 gi.require_version('GtkSource', '3.0')
 gi.require_version('Ide', '1.0')
@@ -32,6 +38,8 @@ from gi.repository import GObject
 from gi.repository import Gtk
 from gi.repository import GtkSource
 from gi.repository import Ide
+from gi.types import GObjectMeta
+from gi.types import StructMeta
 gi_importer = DynamicImporter('gi.repository')
 
 try:
@@ -132,6 +140,55 @@ except ImportError:
 #        relatively fast enough for interactivity.
 import threading
 
+# Build documentation DB and ensure it's up to date
+# This sits here and not in a special class because it needs to run at app startup.
+# Building the DB can slow down the startup process in a noticable way,
+# we could thread it, but that's a problem because sqlite is not thread safe,
+# and if someone starts typing while the DB is still building, they'd get no docs
+gir_path = '/usr/share/gir-1.0'
+ns = {'core': 'http://www.gtk.org/introspection/core/1.0',
+      'c': 'http://www.gtk.org/introspection/c/1.0'}
+doc_db_path = os.path.join(GLib.get_user_data_dir(), 'gnome-builder', 'doc.db')
+db = sqlite3.connect(doc_db_path)
+cursor = db.cursor()
+cursor.execute('CREATE TABLE IF NOT EXISTS doc (symbol text, library_version text, doc text, gir_file text)')
+cursor.execute('CREATE TABLE IF NOT EXISTS girfiles (file text, last_modified integer)')
+
+# I would use scandir for better performance, but it requires newer Python
+for gir_file in os.listdir(gir_path):
+    filename = os.path.join(gir_path, gir_file)
+    mtime = os.stat(filename).st_mtime
+    cursor.execute('SELECT * from girfiles WHERE file=?', (filename,))
+    result = cursor.fetchone()
+    if result is None:
+        cursor.execute('INSERT INTO girfiles VALUES (?, ?)', (filename, mtime))
+    else:
+        if result[1] >= mtime:
+            continue
+        else:
+            # updated
+            cursor.execute('DELETE FROM doc WHERE gir_file=?', (filename,))
+            cursor.execute('UPDATE girfiles SET last_modified=? WHERE file=?', (mtime, filename))
+    parser = lxml.etree.XMLParser(recover=True)
+    tree = lxml.etree.parse(filename, parser=parser)
+    namespace = tree.find('core:namespace', namespaces=ns)
+    library_version = namespace.attrib['version']
+    for node in namespace.findall('core:class', namespaces=ns):
+        doc = node.find('core:doc', namespaces=ns)
+        if doc is not None:
+            db.execute("INSERT INTO doc VALUES (?, ?, ?, ?)",
+                       (node.attrib['{http://www.gtk.org/introspection/glib/1.0}type-name'], 
library_version, doc.text, filename))
+    for method in namespace.findall('core:method', namespaces=ns) + \
+                  namespace.findall('core:constructor', namespaces=ns) + \
+                  namespace.findall('core:function', namespaces=ns):
+        doc = method.find('core:doc', namespaces=ns)
+        if doc is not None:
+            db.execute("INSERT INTO doc VALUES (?, ?, ?, ?)",
+                       (method.attrib['{http://www.gtk.org/introspection/c/1.0}identifier'], 
library_version, doc.text, filename))
+cursor.close()
+db.commit()
+db.close()
+
 
 class GIParam(object):
     "A pygobject ArgInfo wrapper to make it similar to Jedi's Param class"
@@ -447,9 +504,51 @@ class JediCompletionProposal(Ide.CompletionItem, GtkSource.CompletionProposal):
     def do_changed(self):
         pass
 
+    def do_get_info(self):
+        if hasattr(self.completion._definition, 'obj'):
+            obj = self.completion._definition.obj
+        else:
+            return self.completion.docstring()
+        symbol = None
+        namespace = None
+
+        if type(obj) == GObjectMeta or type(obj) == StructMeta:
+            if hasattr(obj, '__info__'):
+                symbol = obj.__info__.get_type_name()
+                namespace = obj.__info__.get_namespace()
+        elif type(obj) == FunctionInfo:
+            symbol = obj.get_symbol()
+            namespace = obj.get_namespace()
+
+        if symbol is not None:
+            # we need to walk down the path to find the module so we can get the version
+            # TODO find a more efficient way to do this.
+            parent = self.completion._definition.parent
+            found = False
+            while not found:
+                new_parent = parent.parent
+                if new_parent is None:
+                    found = True
+                else:
+                    parent = new_parent
+            version = parent.obj._version
+            print(version)
+            db = sqlite3.connect(doc_db_path)
+            cursor = db.cursor()
+            cursor.execute('SELECT doc FROM doc WHERE symbol=? AND library_version=?', (symbol, version))
+            result = cursor.fetchone()
+            cursor.close()
+            db.close()
+            if result is not None:
+                return result[0]
+
+        return self.completion.docstring()
+
+
 def is_completable_char(ch):
     return ch in ('_', '.') or ch.isalnum()
 
+
 def get_param_description(param):
     if hasattr(param, 'description'):
         return param.description.replace('\n', '')


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]