[gnome-builder/wip/chergert/completion] jedi: port to new completion engine
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/chergert/completion] jedi: port to new completion engine
- Date: Thu, 7 Jun 2018 03:29:14 +0000 (UTC)
commit f28f8a71e3c448919c8e1c93e8d9c809bea93883
Author: Christian Hergert <chergert redhat com>
Date: Wed Jun 6 20:11:11 2018 -0700
jedi: port to new completion engine
src/plugins/jedi/jedi_plugin.py | 436 ++++++++++++++++++++--------------------
1 file changed, 216 insertions(+), 220 deletions(-)
---
diff --git a/src/plugins/jedi/jedi_plugin.py b/src/plugins/jedi/jedi_plugin.py
index cd25abea1..540ab2300 100644
--- a/src/plugins/jedi/jedi_plugin.py
+++ b/src/plugins/jedi/jedi_plugin.py
@@ -55,6 +55,7 @@ from gi.repository import GtkSource
from gi.repository import Ide
from gi.types import GObjectMeta
from gi.types import StructMeta
+
_ = Ide.gettext
gi_importer = DynamicImporter('gi.repository')
@@ -87,6 +88,12 @@ _ICONS = {
_TYPE_MODULE: 'lang-namespace-symbolic',
}
+_PROXY_TASK = None
+
+def return_error(task, error):
+ #print(repr(error))
+ task.return_error(GLib.Error(message=repr(error)))
+
try:
import jedi
from jedi.evaluate.compiled import CompiledObject
@@ -362,243 +369,154 @@ def update_doc_db_on_startup():
update_doc_db_on_startup()
-class JediCompletionProvider(Ide.Object, GtkSource.CompletionProvider, Ide.CompletionProvider):
+def get_proxy_cb(app, result, task):
+ try:
+ worker = app.get_worker_finish(result)
+ task.return_object(worker)
+ except Exception as ex:
+ return_error(task, ex)
+
+def get_proxy_async(cancellable, callback, data):
+ global _PROXY_TASK
+
+ task = Ide.Task.new(None, None, callback, data)
+ task.set_name('get-jedi-proxy')
+
+ if _PROXY_TASK:
+ _PROXY_TASK.chain(task)
+ return
+
+ task.set_release_on_propagate(False)
+ _PROXY_TASK = task
+
+ app = Gio.Application.get_default()
+ app.get_worker_async('jedi_plugin', None, get_proxy_cb, task)
+
+def get_proxy_finish(result):
+ return result.propagate_object()
+
+def code_complete_cb(proxy, result, task):
+ try:
+ variant = proxy.call_finish(result)
+ task.variant = variant.get_child_value(0)
+ task.return_boolean(True)
+ except Exception as ex:
+ return_error(task, ex)
+
+def code_complete_async(proxy, filename, line, line_offset, text, cancellable, callback, data=None):
+ task = Ide.Task.new(proxy, cancellable, callback, data)
+ task.set_name('jedi-code-complete')
+
+ proxy.call('CodeComplete',
+ GLib.Variant('(siis)', (filename, line, line_offset, text)),
+ 0, 10000, cancellable, code_complete_cb, task)
+
+def code_complete_finish(proxy, task):
+ task.propagate_boolean()
+ return task.variant
+
+class JediCompletionProvider(Ide.Object, Ide.CompletionProvider):
context = None
current_word = None
results = None
thread = None
- line_str = None
line = -1
line_offset = -1
loading_proxy = False
proxy = None
- def do_get_name(self):
- return 'Jedi Provider'
+ def do_get_title(self):
+ return 'Jedi'
def do_get_icon(self):
return None
- def invalidates(self, line_str):
- if not line_str.startswith(self.line_str):
- return True
- suffix = line_str[len(self.line_str):]
- for ch in suffix:
- if ch in (')', '.', ']'):
- return True
- return False
-
- def do_populate(self, context):
- self.current_word = Ide.CompletionProvider.context_current_word(context) or ''
- self.current_word_lower = self.current_word.lower()
-
- _, iter = context.get_iter()
-
- begin = iter.copy()
- begin.set_line_offset(0)
- line_str = begin.get_slice(iter)
-
- # If we have no results yet, but a thread is active and mostly matches
- # our line prefix, then we should just let that one continue but tell
- # it to deliver to our new context.
- if self.context is not None:
- if not line_str.startswith(self.line_str):
- self.cancellable.cancel()
- self.context = context
+ def do_get_priority(self):
+ return 200
- if iter.get_line() == self.line and not self.invalidates(line_str):
- if self.results and self.results.replay(self.current_word):
- self.results.present(self, context)
- return
+ def do_load(self, context):
+ pass
- self.line_str = line_str
+ def do_get_comment(self, proposal):
+ return proposal.get_comment()
- buffer = iter.get_buffer()
+ def do_activate_proposal(self, context, proposal, key):
+ buffer = context.get_buffer()
+ view = context.get_view()
- begin, end = buffer.get_bounds()
- filename = (iter.get_buffer()
- .get_file()
- .get_file()
- .get_path())
+ _, begin, end = context.get_bounds()
- text = buffer.get_text(begin, end, True)
- line = iter.get_line() + 1
- column = iter.get_line_offset()
+ snippet = proposal.get_snippet()
- self.line = iter.get_line()
- self.line_offset = iter.get_line_offset()
+ buffer.begin_user_action()
+ buffer.delete(begin, end)
+ view.push_snippet(snippet, begin)
+ buffer.end_user_action()
- results = Ide.CompletionResults(query=self.current_word)
+ def do_display_proposal(self, row, context, typed_text, proposal):
+ casefold = typed_text.lower()
- self.cancellable = cancellable = Gio.Cancellable()
- context.connect('cancelled', lambda *_: cancellable.cancel())
+ row.set_icon_name(proposal.get_icon_name())
+ row.set_left(None)
+ row.set_center_markup(proposal.get_markup(casefold))
+ row.set_right(None)
- def async_handler(proxy, result, user_data):
- (self, results, context) = user_data
-
- try:
- variant = proxy.call_finish(result)
- # unwrap outer tuple
- variant = variant.get_child_value(0)
- for i in range(variant.n_children()):
- proposal = JediCompletionProposal(self, context, variant, i)
- results.take_proposal(proposal)
- self.complete(context, results)
- except Exception as ex:
- if isinstance(ex, GLib.Error) and \
- ex.matches(Gio.io_error_quark(), Gio.IOErrorEnum.CANCELLED):
- return
- print(repr(ex))
- context.add_proposals(self, [], True)
-
- self.proxy.call('CodeComplete',
- GLib.Variant('(siis)', (filename, self.line, self.line_offset, text)),
- 0, 10000, cancellable, async_handler, (self, results, context))
-
- def do_match(self, context):
- if not HAS_JEDI:
- return False
-
- if not self.proxy and not self.loading_proxy:
- def get_worker_cb(app, result):
- self.loading_proxy = False
- self.proxy = app.get_worker_finish(result)
- self.loading_proxy = True
- app = Gio.Application.get_default()
- app.get_worker_async('jedi_plugin', None, get_worker_cb)
-
- if not self.proxy:
- return False
-
- if context.get_activation() == GtkSource.CompletionActivation.INTERACTIVE:
- _, iter = context.get_iter()
- iter.backward_char()
- ch = iter.get_char()
- if not (ch in ('_', '.') or ch.isalnum()):
- return False
+ def do_is_trigger(self, iter, ch):
+ if ch == '.':
buffer = iter.get_buffer()
- if Ide.CompletionProvider.context_in_comment_or_string(context):
+ if buffer.iter_has_context_class(iter, 'string') or \
+ buffer.iter_has_context_class(iter, 'comment'):
return False
+ return True
+ return False
- return True
-
- def do_get_start_iter(self, context, proposal):
- _, iter = context.get_iter()
- if self.line != -1 and self.line_offset != -1:
- iter.set_line(self.line)
- iter.set_line_offset(0)
- line_offset = self.line_offset
- while not iter.ends_line() and line_offset > 0:
- if not iter.forward_char():
- break
- line_offset -= 1
- return True, iter
-
- def do_activate_proposal(self, proposal, iter):
- # We may have generated completions a few characters before
- # our current insertion mark. So let's delete any of that
- # transient text.
- if iter.get_line() == self.line:
- begin = iter.copy()
- begin.set_line_offset(0)
- line_offset = self.line_offset
- while not begin.ends_line() and line_offset > 0:
- if not begin.forward_char():
- break
- line_offset -= 1
- buffer = iter.get_buffer()
- buffer.begin_user_action()
- buffer.delete(begin, iter)
- buffer.end_user_action()
-
- snippet = JediSnippet(proposal)
- proposal.context.props.completion.props.view.push_snippet(snippet, None)
-
- self.results = None
- self.line = -1
- self.line_offset = -1
-
- return True, None
-
- def do_get_interactive_delay(self):
- return -1
-
- def do_get_priority(self):
- return 200
-
- def complete(self, context, results):
- # If context and self.context are not the same, that means
- # we stole the results of this task for a later completion.
- self.results = results
- self.results.present(self, self.context)
- self.context = None
-
+ def do_populate_async(self, context, cancellable, callback, data):
+ task = Ide.Task.new(self, None, callback)
+ task.set_name('jedi-completion')
-class JediCompletionProposal(GObject.Object, GtkSource.CompletionProposal):
- def __init__(self, provider, context, variant, index, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.provider = provider
- self.context = context
- self._variant = variant
- self._index = index
+ self.current_word = context.get_word()
+ self.current_word_lower = self.current_word.lower()
- @property
- def variant(self):
- return self._variant.get_child_value(self._index)
+ _, iter, _ = context.get_bounds()
- @property
- def completion_type(self):
- return self.variant.get_child_value(0).get_int32()
+ buffer = context.get_buffer()
- @property
- def completion_label(self):
- return self.variant.get_child_value(1).get_string()
+ begin, end = buffer.get_bounds()
- @property
- def completion_text(self):
- return self.variant.get_child_value(2).get_string()
+ task.filename = buffer.get_file().get_file().get_path()
+ task.line = iter.get_line()
+ task.line_offset = iter.get_line_offset()
+ #if task.line_offset > 0:
+ # task.line_offset -= 1
+ task.text = buffer.get_text(begin, end, True)
- @property
- def completion_params(self):
- return self.variant.get_child_value(3).unpack()
+ def code_complete_cb(obj, result, task):
+ try:
+ variant = code_complete_finish(obj, result)
+ task.return_object(JediResults(variant))
+ except Exception as ex:
+ return_error(task, ex)
- @property
- def completion_doc(self):
- return self.variant.get_child_value(4).get_string()
-
- def do_get_label(self):
- return self.completion_label
-
- def do_match(self, query, casefold):
- label = self.completion_label
- ret, priority = Ide.Completion.fuzzy_match(label, self.provider.current_word_lower)
- # Penalize words that start with __ like __eq__.
- if label.startswith('__'):
- priority += 1000
- self.set_priority(priority)
- return ret
-
- def do_get_markup(self):
- label = self.completion_label
- name = Ide.Completion.fuzzy_highlight(label, self.provider.current_word_lower)
- if self.completion_type == _TYPE_FUNCTION:
- params = self.completion_params
- if params is not None:
- return ''.join([name, '(', ', '.join(self.completion_params), ')'])
- else:
- return name + '()'
- return name
+ def get_proxy_cb(obj, result, task):
+ try:
+ proxy = get_proxy_finish(result)
+ code_complete_async(proxy, task.filename, task.line, task.line_offset, task.text,
task.get_cancellable(), code_complete_cb, task)
+ except Exception as ex:
+ return_error(task, ex)
- def do_get_text(self):
- return self.completion_text
+ get_proxy_async(None, get_proxy_cb, task)
- def do_get_icon_name(self):
- return _ICONS.get(self.completion_type, None)
+ def do_populate_finish(self, result):
+ return result.propagate_object()
- def do_get_info(self):
- return self.completion_doc
+ def do_get_priority(self):
+ return 200
+ def do_refilter(self, context, model):
+ word = context.get_word().lower()
+ model.refilter(word)
+ return True
class JediCompletionRequest:
did_run = False
@@ -736,26 +654,6 @@ class JediWorker(GObject.Object, Ide.Worker):
'org.gnome.builder.plugins.jedi',
None)
-def JediSnippet(proposal):
- snippet = Ide.Snippet()
- snippet.add_chunk(Ide.SnippetChunk(text=proposal.completion_text, text_set=True))
-
- # Add parameter completion for functions.
- if proposal.completion_type == _TYPE_FUNCTION:
- snippet.add_chunk(Ide.SnippetChunk(text='(', text_set=True))
- params = proposal.completion_params
- if params:
- tab_stop = 0
- for param in params[:-1]:
- tab_stop += 1
- snippet.add_chunk(Ide.SnippetChunk(text=param, text_set=True, tab_stop=tab_stop))
- snippet.add_chunk(Ide.SnippetChunk(text=', ', text_set=True))
- tab_stop += 1
- snippet.add_chunk(Ide.SnippetChunk(text=params[-1], text_set=True, tab_stop=tab_stop))
- snippet.add_chunk(Ide.SnippetChunk(text=')', text_set=True))
-
- return snippet
-
class JediPreferences(GObject.Object, Ide.PreferencesAddin):
def do_load(self, prefs):
@@ -769,3 +667,101 @@ class JediPreferences(GObject.Object, Ide.PreferencesAddin):
def do_unload(self, prefs):
prefs.remove_id(self.completion_id)
+
+class JediResults(GObject.Object, Gio.ListModel):
+ variant = None
+ items = None
+
+ def __init__(self, variant, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.variant = variant
+ self.items = []
+ for i in range(variant.n_children()):
+ self.items.append((i,0))
+
+ def do_get_item_type(self):
+ return type(Ide.CompletionProposal)
+
+ def do_get_n_items(self):
+ return len(self.items)
+
+ def do_get_item(self, position):
+ idx,_ = self.items[position]
+ return JediResult(self.variant.get_child_value(idx))
+
+ def refilter(self, casefold):
+ old_len = len(self.items)
+ self.items = []
+ for i in range(self.variant.n_children()):
+ child = self.variant.get_child_value(i)
+ label = child.get_child_value(1).get_string()
+ match, priority = Ide.Completion.fuzzy_match(label, casefold)
+ if match:
+ self.items.append((i, priority))
+ self.items.sort(key=lambda x: x[1])
+ self.items_changed(0, old_len, len(self.items))
+
+class JediResult(GObject.Object, Ide.CompletionProposal):
+ def __init__(self, variant):
+ super().__init__()
+ self.variant = variant
+
+ def get_kind(self):
+ return self.variant.get_child_value(0).get_int32()
+
+ def get_icon_name(self):
+ kind = self.get_kind()
+ return _ICONS.get(kind, None)
+
+ def get_label(self):
+ return self.variant.get_child_value(1).get_string()
+
+ def get_markup(self, casefold):
+ markup = Ide.Completion.fuzzy_highlight(self.get_label(), casefold)
+
+ kind = self.get_kind()
+ if kind == _TYPE_FUNCTION:
+ parts = []
+ params = self.get_params()
+ for p in params:
+ if p.startswith('param '):
+ parts.append(p[6:])
+ markup += ''.join(['<span fgalpha="32762">(', ', '.join(parts), ')</span>'])
+
+ return markup
+
+ def get_text(self):
+ return self.variant.get_child_value(2).get_string()
+
+ def get_comment(self):
+ comment = self.variant.get_child_value(4).get_string()
+ if comment and '\n' in comment:
+ return comment[:comment.index('\n')]
+ return comment
+
+ def get_params(self):
+ return self.variant.get_child_value(3).unpack()
+
+ def get_snippet(self):
+ snippet = Ide.Snippet.new(None, None)
+ snippet.add_chunk(Ide.SnippetChunk(spec=self.get_text()))
+
+ # Add parameter completion for functions.
+ kind = self.get_kind()
+ if kind == _TYPE_FUNCTION:
+ snippet.add_chunk(Ide.SnippetChunk(text='(', text_set=True))
+ params = self.get_params()
+ if params:
+ tab_stop = 0
+ for param in params[:-1]:
+ tab_stop += 1
+ if param.startswith('param '):
+ param = param[6:]
+ snippet.add_chunk(Ide.SnippetChunk(text=param, text_set=True, tab_stop=tab_stop))
+ snippet.add_chunk(Ide.SnippetChunk(text=', ', text_set=True))
+ tab_stop += 1
+ snippet.add_chunk(Ide.SnippetChunk(text=params[-1], text_set=True, tab_stop=tab_stop))
+ snippet.add_chunk(Ide.SnippetChunk(text=')', text_set=True))
+
+ return snippet
+
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]