2026-01-04 11:42:27 -06:00
|
|
|
# Python imports
|
|
|
|
|
|
|
|
|
|
# Lib imports
|
|
|
|
|
import gi
|
|
|
|
|
gi.require_version('Gtk', '3.0')
|
|
|
|
|
gi.require_version('GtkSource', '4')
|
|
|
|
|
|
|
|
|
|
from gi.repository import Gtk
|
|
|
|
|
from gi.repository import GtkSource
|
|
|
|
|
from gi.repository import GObject
|
|
|
|
|
|
|
|
|
|
import jedi
|
|
|
|
|
from jedi.api import Script
|
|
|
|
|
|
|
|
|
|
# Application imports
|
2026-02-14 21:36:06 -06:00
|
|
|
from .provider_response_cache import ProviderResponseCache
|
2026-01-04 11:42:27 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# FIXME: Find real icon names...
|
|
|
|
|
icon_names = {
|
|
|
|
|
'import': '',
|
|
|
|
|
'module': '',
|
|
|
|
|
'class': '',
|
|
|
|
|
'function': '',
|
|
|
|
|
'statement': '',
|
|
|
|
|
'param': ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Jedi:
|
|
|
|
|
def get_script(file, doc_text):
|
|
|
|
|
return Script(code = doc_text, path = file)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PythonCompletionProvider(GObject.Object, GtkSource.CompletionProvider):
|
|
|
|
|
"""
|
|
|
|
|
This code is A python code completion plugin for Newton.
|
|
|
|
|
# NOTE: Some code pulled/referenced from here --> https://github.com/isamert/gedi
|
|
|
|
|
"""
|
|
|
|
|
__gtype_name__ = 'PythonProvider'
|
|
|
|
|
|
2026-02-14 21:36:06 -06:00
|
|
|
def __init__(self):
|
2026-01-04 11:42:27 -06:00
|
|
|
GObject.Object.__init__(self)
|
2026-02-14 21:36:06 -06:00
|
|
|
|
|
|
|
|
self.response_cache: ProviderResponseCache = ProviderResponseCache()
|
2026-01-04 11:42:27 -06:00
|
|
|
self._theme = Gtk.IconTheme.get_default()
|
2026-02-14 21:36:06 -06:00
|
|
|
self._file = None
|
2026-01-04 11:42:27 -06:00
|
|
|
|
|
|
|
|
def do_get_name(self):
|
|
|
|
|
return "Python Code Completion"
|
|
|
|
|
|
|
|
|
|
def get_iter_correctly(self, context):
|
|
|
|
|
return context.get_iter()[1] if isinstance(context.get_iter(), tuple) else context.get_iter()
|
|
|
|
|
|
|
|
|
|
def do_match(self, context):
|
2026-02-14 21:36:06 -06:00
|
|
|
word = self.response_cache.get_word(context)
|
|
|
|
|
if not word or len(word) < 2: return False
|
|
|
|
|
|
2026-01-04 11:42:27 -06:00
|
|
|
iter = self.get_iter_correctly(context)
|
|
|
|
|
iter.backward_char()
|
|
|
|
|
|
|
|
|
|
ch = iter.get_char()
|
|
|
|
|
# NOTE: Look to re-add or apply supprting logic to use spaces
|
|
|
|
|
# As is it slows down the editor in certain contexts...
|
|
|
|
|
# if not (ch in ('_', '.', ' ') or ch.isalnum()):
|
|
|
|
|
if not (ch in ('_', '.') or ch.isalnum()):
|
|
|
|
|
return False
|
|
|
|
|
|
2026-02-14 21:36:06 -06:00
|
|
|
buffer = iter.get_buffer()
|
|
|
|
|
if buffer.get_context_classes_at_iter(iter) != ['no-spell-check']:
|
|
|
|
|
return False
|
|
|
|
|
|
2026-01-04 11:42:27 -06:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def do_get_priority(self):
|
2026-02-14 21:36:06 -06:00
|
|
|
return 5
|
2026-01-04 11:42:27 -06:00
|
|
|
|
|
|
|
|
def do_get_activation(self):
|
2026-02-14 21:36:06 -06:00
|
|
|
""" The context for when a provider will show results """
|
2026-01-04 11:42:27 -06:00
|
|
|
return GtkSource.CompletionActivation.INTERACTIVE
|
|
|
|
|
|
|
|
|
|
def do_populate(self, context):
|
2026-02-14 21:36:06 -06:00
|
|
|
# Note: Filtering needs to happen before getting here in some type of symbol cache
|
2026-01-04 11:42:27 -06:00
|
|
|
# TODO: Maybe convert async?
|
2026-02-14 21:36:06 -06:00
|
|
|
if not self._file: return
|
|
|
|
|
|
2026-01-04 11:42:27 -06:00
|
|
|
it = self.get_iter_correctly(context)
|
|
|
|
|
buffer = it.get_buffer()
|
|
|
|
|
proposals = []
|
|
|
|
|
|
2026-02-14 21:36:06 -06:00
|
|
|
doc_text = buffer.get_text(
|
|
|
|
|
buffer.get_start_iter(),
|
|
|
|
|
buffer.get_end_iter(),
|
|
|
|
|
False
|
|
|
|
|
)
|
|
|
|
|
iter_cursor = buffer.get_iter_at_mark( buffer.get_insert() )
|
2026-01-04 11:42:27 -06:00
|
|
|
linenum = iter_cursor.get_line() + 1
|
|
|
|
|
charnum = iter_cursor.get_line_index()
|
|
|
|
|
|
|
|
|
|
def create_generator():
|
2026-02-14 21:36:06 -06:00
|
|
|
if not self._file: return
|
|
|
|
|
|
|
|
|
|
for completion in Jedi.get_script(self._file, doc_text).complete(
|
|
|
|
|
line = linenum,
|
|
|
|
|
column = None,
|
|
|
|
|
fuzzy = False
|
|
|
|
|
):
|
2026-01-04 11:42:27 -06:00
|
|
|
comp_item = GtkSource.CompletionItem.new()
|
|
|
|
|
comp_item.set_label(completion.name)
|
|
|
|
|
comp_item.set_text(completion.name)
|
|
|
|
|
comp_item.set_icon(self.get_icon_for_type(completion.type))
|
|
|
|
|
comp_item.set_info(completion.docstring())
|
2026-02-14 21:36:06 -06:00
|
|
|
|
2026-01-04 11:42:27 -06:00
|
|
|
yield comp_item
|
|
|
|
|
|
|
|
|
|
for item in create_generator():
|
|
|
|
|
proposals.append(item)
|
|
|
|
|
|
|
|
|
|
context.add_proposals(self, proposals, True)
|
|
|
|
|
|
|
|
|
|
def get_icon_for_type(self, _type):
|
|
|
|
|
try:
|
|
|
|
|
return self._theme.load_icon(icon_names[_type.lower()], 16, 0)
|
|
|
|
|
except (KeyError, AttributeError, GObject.GError) as e:
|
|
|
|
|
return self._theme.load_icon(Gtk.STOCK_ADD, 16, 0)
|
|
|
|
|
except (GObject.GError, AttributeError) as e:
|
|
|
|
|
return None
|