[gnome-builder] python: add very basic auto-completion for python using jedi
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] python: add very basic auto-completion for python using jedi
- Date: Wed, 15 Jul 2015 21:57:45 +0000 (UTC)
commit 1429e043ba9df3ec817d0bc20b6d4857f9878cd3
Author: Christian Hergert <christian hergert me>
Date: Wed Jul 15 12:52:39 2015 -0700
python: add very basic auto-completion for python using jedi
Long term, this really should be put in gnome-code-assistance, but we
still need to figure out the right abstraction there. Creating
abstractions before we understand what is needed is generally a bad idea,
so lets prototype this in process.
Igor Gnatenko mentioned that Jedi is a pretty good completion library for
Python. Thankfully, it seems to be packaged on a few systems including
Fedora.
If jedi is not found, this plugin will simply do nothing. On Fedora
systems, "dnf install python3-jedi" should get the job done.
Currently, this performs the lookup using Python's big Thread. I don't
like this because it means we get all the python memory fragmentation
with it. This is another great reason for this to belong in
gnome-code-assistance. Multiprocessing is another short term stop gap
that could be used. The threading/GIL shouldn't affect us much here,
but with CPython, always famous last words.
If you know python, I'm sure you can improve this module.
Patches wanted, accepted.
configure.ac | 1 +
plugins/Makefile.am | 1 +
plugins/jedi/Makefile.am | 10 +++
plugins/jedi/jedi.plugin | 10 +++
plugins/jedi/jedi_plugin.py | 186 +++++++++++++++++++++++++++++++++++++++++++
5 files changed, 208 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 176c124..12c9042 100644
--- a/configure.ac
+++ b/configure.ac
@@ -392,6 +392,7 @@ AC_CONFIG_FILES([
plugins/file-search/Makefile
plugins/gnome-code-assistance/Makefile
plugins/html-completion/Makefile
+ plugins/jedi/Makefile
plugins/mingw/Makefile
plugins/python-pack/Makefile
plugins/symbol-tree/Makefile
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 276090f..bf85fba 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -8,6 +8,7 @@ SUBDIRS = \
file-search \
gnome-code-assistance \
html-completion \
+ jedi \
mingw \
python-pack \
symbol-tree \
diff --git a/plugins/jedi/Makefile.am b/plugins/jedi/Makefile.am
new file mode 100644
index 0000000..1b71bfa
--- /dev/null
+++ b/plugins/jedi/Makefile.am
@@ -0,0 +1,10 @@
+EXTRA_DIST = $(plugin_DATA)
+
+plugindir = $(libdir)/gnome-builder/plugins
+plugin_DATA = \
+ jedi.plugin \
+ jedi_plugin.py
+
+GITIGNOREFILES = __pycache__
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/jedi/jedi.plugin b/plugins/jedi/jedi.plugin
new file mode 100644
index 0000000..8a0b7d9
--- /dev/null
+++ b/plugins/jedi/jedi.plugin
@@ -0,0 +1,10 @@
+[Plugin]
+Module=jedi_plugin
+Loader=python3
+Name=Jedi Auto-Completion
+Description=Provides autocompletion features for the Python programming language.
+Authors=Christian Hergert <christian hergert me>
+Copyright=Copyright © 2015 Christian Hergert
+Builtin=true
+Hidden=true
+X-Completion-Provider-Languages=python,python3
diff --git a/plugins/jedi/jedi_plugin.py b/plugins/jedi/jedi_plugin.py
new file mode 100644
index 0000000..a047bba
--- /dev/null
+++ b/plugins/jedi/jedi_plugin.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python3
+
+#
+# jedi_plugin.py
+#
+# Copyright (C) 2015 Christian Hergert <chris dronelabs com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+from gi.repository import GLib
+from gi.repository import GObject
+from gi.repository import Gtk
+from gi.repository import GtkSource
+from gi.repository import Ide
+
+try:
+ import jedi
+ HAS_JEDI=True
+except ImportError:
+ HAS_JEDI=False
+
+# FIXME: Should we be using multiprocessing or something?
+# Alternatively, this can go in gnome-code-assistance
+# once we have an API that can transfer completions
+# relatively fast enough for interactivity.
+import threading
+
+class CompletionThread(threading.Thread):
+ def __init__(self, provider, context, text, line, column, filename):
+ super().__init__()
+ self._provider = provider
+ self._context = context
+ self._text = text
+ self._line = line
+ self._column = column
+ self._filename = filename
+ self._completions = []
+
+ def run(self):
+ try:
+ script = jedi.Script(self._text, self._line, self._column, self._filename)
+ self._completions = [JediCompletionProposal(self._provider, self._context, info)
+ for info in script.completions()]
+ finally:
+ self.complete_in_idle()
+
+ def _complete(self):
+ self._context.add_proposals(self._provider, self._completions, True)
+
+ def complete_in_idle(self):
+ GLib.timeout_add(0, lambda *_: self._complete())
+
+class JediCompletionProvider(Ide.Object,
+ GtkSource.CompletionProvider,
+ Ide.CompletionProvider):
+ def do_get_name(self):
+ return 'Jedi Provider'
+
+ def do_get_icon(self):
+ return None
+
+ def do_populate(self, context):
+ if not HAS_JEDI:
+ context.add_proposals(self, [], True)
+ return
+
+ _, iter = context.get_iter()
+ buffer = iter.get_buffer()
+ begin, end = buffer.get_bounds()
+
+ filename = (iter.get_buffer()
+ .get_file()
+ .get_file()
+ .get_basename())
+
+ text = buffer.get_text(begin, end, True)
+ line = iter.get_line() + 1
+ column = iter.get_line_offset()
+
+ CompletionThread(self, context, text, line, column, filename).start()
+
+ def do_get_activiation(self):
+ return GtkSource.CompletionActivation.INTERACTIVE
+
+ def do_match(self, context):
+ self.do_activate_proposal
+ return HAS_JEDI
+
+ def do_get_info_widget(self, proposal):
+ return None
+
+ def do_update_info(self, proposal, info):
+ pass
+
+ def do_get_start_iter(self, context, proposal):
+ _, iter = context.get_iter()
+ return True, iter
+
+ def do_activate_proposal(self, provider, proposal):
+ return False, None
+
+ def do_get_interactive_delay(self):
+ return -1
+
+ def do_get_priority(self):
+ return 200
+
+class JediCompletionProposal(GObject.Object, GtkSource.CompletionProposal):
+ def __init__(self, provider, context, completion, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.provider = provider
+ self.context = context
+ self.completion = completion
+
+ def do_get_label(self):
+ return self.completion.name
+
+ def do_get_markup(self):
+ return self.completion.name
+
+ def do_get_text(self):
+ return self.completion.complete
+
+ def do_get_icon(self):
+ if self.completion.type == 'class':
+ return load_icon(self.context, 'lang-class-symbolic')
+ elif self.completion.type == 'instance':
+ return load_icon(self.context, 'lang-variable-symbolic')
+ elif self.completion.type in ('import', 'module'):
+ # FIXME: Would be nice to do something better here.
+ return load_icon(self.context, 'lang-include-symbolic')
+ elif self.completion.type == 'function':
+ return load_icon(self.context, 'lang-function-symbolic')
+ elif self.completion.type == 'keyword':
+ # FIXME: And here
+ return None
+ return None
+
+ def do_hash(self):
+ return hash(self.completion.full_name)
+
+ def do_equal(self, other):
+ return False
+
+ def do_changed(self):
+ pass
+
+_icon_cache = {}
+
+def purge_cache():
+ _icon_cache.clear()
+
+settings = Gtk.Settings.get_default()
+settings.connect('notify::gtk-theme-name', lambda *_: purge_cache())
+settings.connect('notify::gtk-application-prefer-dark-theme', lambda *_: purge_cache())
+
+def load_icon(context, name):
+ if name in _icon_cache:
+ return _icon_cache[name]
+
+ window = context.props.completion.get_info_window()
+ size = 16 * window.get_scale_factor()
+ style_context = window.get_style_context()
+ icon_theme = Gtk.IconTheme.get_default()
+ icon_info = icon_theme.lookup_icon(name, size, 0)
+ if not icon_info:
+ icon = None
+ else:
+ icon = icon_info.load_symbolic_for_context(style_context)
+
+ _icon_cache[name] = icon
+
+ return icon
+
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]