From 37bff1eb3dba0a9ab8082ae68f563f6805f893fe Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Wed, 8 Nov 2023 23:52:23 -0600 Subject: [PATCH] Completion providfer efforts; Snippits stubbing message --- plugins/lsp_client/lsp_controller.py | 36 +++++++ plugins/lsp_client/plugin.py | 101 ++++++++++-------- .../lsp_client/pylspclient/lsp_endpoint.py | 2 +- plugins/lsp_client/pylspclient/lsp_structs.py | 8 +- plugins/snippets/plugin.py | 3 +- .../lsp_completion_provider.py | 73 +++++++++++++ .../mixins/source_file_events_mixin.py | 18 +--- .../widgets/base/sourceview/source_view.py | 4 +- .../base/sourceview/source_view_events.py | 4 +- src/utils/ipc_server.py | 2 +- 10 files changed, 185 insertions(+), 66 deletions(-) create mode 100644 src/core/widgets/base/sourceview/custom_completion_providers/lsp_completion_provider.py diff --git a/plugins/lsp_client/lsp_controller.py b/plugins/lsp_client/lsp_controller.py index 2c19634..720f15b 100644 --- a/plugins/lsp_client/lsp_controller.py +++ b/plugins/lsp_client/lsp_controller.py @@ -48,6 +48,7 @@ class LSPController: json_rpc_endpoint = pylspclient.JsonRpcEndpoint(server_proc.stdin, server_proc.stdout) callbacks = { + "window/showMessage": print, "textDocument/symbolStatus": print, "textDocument/publishDiagnostics": self._blame, } @@ -117,6 +118,41 @@ class LSPController: pylspclient.lsp_structs.TextDocumentIdentifier(uri), pylspclient.lsp_structs.Position(line, offset) ) + + return [] + + def do_change(self, language_id, line, start, end, text): + if language_id in self.lsp_clients.keys(): + + start_pos = pylspclient.lsp_structs.Position(line, start.get_line_offset()) + end_pos = pylspclient.lsp_structs.Position(line, end.get_line_offset()) + range_info = pylspclient.lsp_structs.Range(start_pos, end_pos) + text_length = len(text) + change_event = pylspclient.lsp_structs.TextDocumentContentChangeEvent(range_info, text_length, text) + + return self.lsp_clients[language_id].didChange( None, change_event ) + + return [] + + def do_completion(self, language_id, uri, line, offset, _char, is_invoked = False): + if language_id in self.lsp_clients.keys(): + trigger = pylspclient.lsp_structs.CompletionTriggerKind.TriggerCharacter + + if _char in [".", " "]: + trigger = pylspclient.lsp_structs.CompletionTriggerKind.TriggerCharacter + elif is_invoked: + trigger = pylspclient.lsp_structs.CompletionTriggerKind.Invoked + else: + trigger = pylspclient.lsp_structs.CompletionTriggerKind.TriggerForIncompleteCompletions + + return self.lsp_clients[language_id].completion( + pylspclient.lsp_structs.TextDocumentIdentifier(uri), + pylspclient.lsp_structs.Position(line, offset), + pylspclient.lsp_structs.CompletionContext(trigger, _char) + ) + + return [] + def load_lsp_server(self, language_id): if not language_id in self.lsp_servers_config.keys(): diff --git a/plugins/lsp_client/plugin.py b/plugins/lsp_client/plugin.py index c249e90..29c87ff 100644 --- a/plugins/lsp_client/plugin.py +++ b/plugins/lsp_client/plugin.py @@ -1,8 +1,10 @@ # Python imports import os import json +import threading # Lib imports +from gi.repository import GLib # Application imports @@ -12,7 +14,6 @@ from .lsp_controller import LSPController - class LSPPliginException(Exception): ... @@ -27,6 +28,8 @@ class Plugin(PluginBase): self.lsp_config_path: str = os.path.dirname(os.path.realpath(__file__)) + "/../../lsp_servers_config.json" self.lsp_servers_config: dict = {} self.lsp_controller = None + self.timer = None + def generate_reference_ui_element(self): ... @@ -39,63 +42,76 @@ class Plugin(PluginBase): text = f"LSP NOT Enabled.\nFile:\n\t{self.lsp_config_path}\ndoes no exsist..." self._event_system.emit("bubble_message", ("warning", self.name, text,)) return - - self.lsp_controller = LSPController(self.lsp_servers_config) - self.inner_subscribe_to_events() + + if len(self.lsp_servers_config.keys()) > 0: + self.lsp_controller = LSPController(self.lsp_servers_config) + self.inner_subscribe_to_events() def subscribe_to_events(self): ... def inner_subscribe_to_events(self): self._event_system.subscribe("shutting_down", self._shutting_down) - self._event_system.subscribe("buffer_changed", self._buffer_changed) + self._event_system.subscribe("textDocument/didChange", self._buffer_changed) self._event_system.subscribe("textDocument/didOpen", self.lsp_controller.do_open) self._event_system.subscribe("textDocument/didSave", self.lsp_controller.do_save) self._event_system.subscribe("textDocument/didClose", self.lsp_controller.do_close) self._event_system.subscribe("textDocument/definition", self._do_goto) + self._event_system.subscribe("textDocument/completion", self.completion) def _shutting_down(self): - if self.lsp_controller: - self.lsp_controller._shutting_down() + self.lsp_controller._shutting_down() + def cancel_timer(self): + if self.timer: + self.timer.cancel() + GLib.idle_remove_by_data(None) - def _buffer_changed(self, buffer): - # self._do_completion() - ... + def delay_completion_glib(self, source_view, context, callback): + GLib.idle_add(self._do_completion, source_view, context, callback) - - def _do_completion(self, is_invoked = False): - fpath = self._active_src_view.get_current_filepath() - - if not fpath: return - - uri = fpath.get_uri() - iter = self._buffer.get_iter_at_mark( self._buffer.get_insert() ) - line = iter.get_line() - offset = iter.get_line_offset() - trigger = pylspclient.lsp_structs.CompletionTriggerKind.TriggerCharacter - _char = iter.get_char() - trigger = None - - if _char in [".", " "]: - trigger = pylspclient.lsp_structs.CompletionTriggerKind.TriggerCharacter - elif is_invoked: - trigger = pylspclient.lsp_structs.CompletionTriggerKind.Invoked - else: - trigger = pylspclient.lsp_structs.CompletionTriggerKind.TriggerForIncompleteCompletions - - result = self.lsp_controller.completion( - pylspclient.lsp_structs.TextDocumentIdentifier(uri), - pylspclient.lsp_structs.Position(line, offset), - pylspclient.lsp_structs.CompletionContext(trigger, _char) - ) + def delay_completion(self, source_view, context, callback): + self.timer = threading.Timer(0.8, self.delay_completion_glib, (source_view, context, callback,)) + self.timer.daemon = True + self.timer.start() - if result.items: - for item in result.items: - print(item.label) - else: - print(result.label) + def _buffer_changed(self, language_id, buffer): + iter = buffer.get_iter_at_mark( buffer.get_insert() ) + line = iter.get_line() + start = iter.copy() + end = iter.copy() + + start.backward_line() + start.forward_line() + end.forward_to_line_end() + + text = buffer.get_text(start, end, include_hidden_chars = False) + result = self.lsp_controller.do_change(language_id, line, start, end, text) + # print(result) + + + def completion(self, source_view, context, callback): + self.cancel_timer() + self.delay_completion(source_view, context, callback) + + def _do_completion(self, source_view, context, callback): + filepath = source_view.get_current_filepath() + + if not filepath: return + + uri = filepath.get_uri() + buffer = source_view.get_buffer() + iter = buffer.get_iter_at_mark( buffer.get_insert() ) + line = iter.get_line() + 1 + + _char = iter.get_char() + if iter.backward_char(): + _char = iter.get_char() + + offset = iter.get_line_offset() + result = self.lsp_controller.do_completion(source_view.get_filetype(), uri, line, offset, _char) + callback(context, result) def _do_goto(self, language_id, uri, line, offset): results = self.lsp_controller.do_goto(language_id, uri, line, offset) @@ -105,5 +121,4 @@ class Plugin(PluginBase): file = result.uri[7:] line = result.range.end.line message = f"FILE|{file}:{line}" - self._event_system.emit("post_file_to_ipc", message) - + self._event_system.emit("post_file_to_ipc", message) \ No newline at end of file diff --git a/plugins/lsp_client/pylspclient/lsp_endpoint.py b/plugins/lsp_client/pylspclient/lsp_endpoint.py index c442db9..655833e 100644 --- a/plugins/lsp_client/pylspclient/lsp_endpoint.py +++ b/plugins/lsp_client/pylspclient/lsp_endpoint.py @@ -41,7 +41,7 @@ class LspEndpoint(threading.Thread): params = jsonrpc_message.get("params") if method: - if rpc_id: + if rpc_id is not None: if method not in self.method_callbacks: raise lsp_structs.ResponseError( lsp_structs.ErrorCodes.MethodNotFound, diff --git a/plugins/lsp_client/pylspclient/lsp_structs.py b/plugins/lsp_client/pylspclient/lsp_structs.py index 7a61581..70804a5 100644 --- a/plugins/lsp_client/pylspclient/lsp_structs.py +++ b/plugins/lsp_client/pylspclient/lsp_structs.py @@ -436,7 +436,7 @@ class CompletionItem(object): detail = None, \ documentation = None, \ deprecated = None, \ - presented = None, \ + preselect = None, \ sortText = None, \ filterText = None, \ insertText = None, \ @@ -455,7 +455,7 @@ class CompletionItem(object): :param str detail: A human-readable string with additional information about this item, like type or symbol information. :param tr ocumentation: A human-readable string that represents a doc-comment. :param bool deprecated: Indicates if this item is deprecated. - :param bool presented: Select this item when showing. Note: that only one completion item can be selected and that the + :param bool preselect: Select this item when showing. Note: that only one completion item can be selected and that the tool / client decides which item that is. The rule is that the first item of those that match best is selected. :param str sortText: A string that should be used when comparing this item with other items. When `falsy` the label is used. :param str filterText: A string that should be used when filtering a set of completion items. When `falsy` the label is used. @@ -487,7 +487,7 @@ class CompletionItem(object): self.detail = detail self.documentation = documentation self.deprecated = deprecated - self.presented = presented + self.preselect = preselect self.sortText = sortText self.filterText = filterText self.insertText = insertText @@ -530,7 +530,7 @@ class CompletionItemKind(enum.Enum): class CompletionList(object): """ - Represents a collection of [completion items](#CompletionItem) to be presented in the editor. + Represents a collection of [completion items](#CompletionItem) to be preselect in the editor. """ def __init__(self, isIncomplete, items): """ diff --git a/plugins/snippets/plugin.py b/plugins/snippets/plugin.py index 5794b7e..9a5877b 100644 --- a/plugins/snippets/plugin.py +++ b/plugins/snippets/plugin.py @@ -99,4 +99,5 @@ class Plugin(PluginBase): body = self.snippet_data[self.active_snippit_group][key]["body"] snippits.append(body) - print(snippits) \ No newline at end of file + # print(snippits) + print("Snippits Plugin: _handle_update > results > stub...") \ No newline at end of file diff --git a/src/core/widgets/base/sourceview/custom_completion_providers/lsp_completion_provider.py b/src/core/widgets/base/sourceview/custom_completion_providers/lsp_completion_provider.py new file mode 100644 index 0000000..d6a6d4a --- /dev/null +++ b/src/core/widgets/base/sourceview/custom_completion_providers/lsp_completion_provider.py @@ -0,0 +1,73 @@ +# 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 + +# Application imports + + + +class LSPCompletionProvider(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' + + def __init__(self, source_view): + GObject.Object.__init__(self) + + self._theme = Gtk.IconTheme.get_default() + self._source_view = source_view + + def do_get_name(self): + return "LSP 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): + event_system.emit("textDocument/completion", (self._source_view, context, self.do_populate)) + return True + + def do_get_priority(self): + return 1 + + def do_get_activation(self): + return GtkSource.CompletionActivation.INTERACTIVE + + def do_populate(self, context, result = None): + proposals = [] + if result: + if result.items: + for item in result.items: + comp_item = GtkSource.CompletionItem.new() + comp_item.set_label(item.label) + comp_item.set_text(item.textEdit) + comp_item.set_icon( self.get_icon_for_type(item.kind) ) + comp_item.set_info(item.documentation) + proposals.append(comp_item) + else: + comp_item = GtkSource.CompletionItem.new() + comp_item.set_label(item.label) + comp_item.set_text(item.textEdit) + comp_item.set_icon( self.get_icon_for_type(item.kind) ) + comp_item.set_info(item.documentation) + proposals.append(comp_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: + try: + return self._theme.load_icon(Gtk.STOCK_ADD, 16, 0) + except: + return None \ No newline at end of file diff --git a/src/core/widgets/base/sourceview/mixins/source_file_events_mixin.py b/src/core/widgets/base/sourceview/mixins/source_file_events_mixin.py index ec942d0..debdc48 100644 --- a/src/core/widgets/base/sourceview/mixins/source_file_events_mixin.py +++ b/src/core/widgets/base/sourceview/mixins/source_file_events_mixin.py @@ -10,6 +10,7 @@ from gi.repository import Gio from gi.repository import GtkSource # Application imports +from ..custom_completion_providers.lsp_completion_provider import LSPCompletionProvider @@ -135,8 +136,7 @@ class FileEventsMixin: def _document_loaded(self, line: int = 0): for provider in self._completion.get_providers(): self._completion.remove_provider(provider) - - file = self._current_file.get_path() + uri = self._current_file.get_uri() buffer = self.get_buffer() @@ -145,19 +145,11 @@ class FileEventsMixin: word_completion = GtkSource.CompletionWords.new("word_completion") word_completion.register(buffer) self._completion.add_provider(word_completion) + + lsp_completion_provider = LSPCompletionProvider(self) + self._completion.add_provider(lsp_completion_provider) - # TODO: actually load a meaningful provider based on file type... - # example_completion_provider = ExampleCompletionProvider() - # self._completion.add_provider(example_completion_provider) - - # py_completion_provider = PythonCompletionProvider(file) - # self._completion.add_provider(py_completion_provider) self.got_to_line(buffer, line) - - - - - diff --git a/src/core/widgets/base/sourceview/source_view.py b/src/core/widgets/base/sourceview/source_view.py index 447b4ad..f089bff 100644 --- a/src/core/widgets/base/sourceview/source_view.py +++ b/src/core/widgets/base/sourceview/source_view.py @@ -14,8 +14,8 @@ from gi.repository import GtkSource # Application imports from .source_view_controller import SourceViewControllerMixin -from .custom_completion_providers.example_completion_provider import ExampleCompletionProvider -from .custom_completion_providers.python_completion_provider import PythonCompletionProvider +# from .custom_completion_providers.example_completion_provider import ExampleCompletionProvider +# from .custom_completion_providers.python_completion_provider import PythonCompletionProvider diff --git a/src/core/widgets/base/sourceview/source_view_events.py b/src/core/widgets/base/sourceview/source_view_events.py index 6ea87bd..acb7ace 100644 --- a/src/core/widgets/base/sourceview/source_view_events.py +++ b/src/core/widgets/base/sourceview/source_view_events.py @@ -21,10 +21,12 @@ class SourceViewEvents(SourceViewDnDMixin, MarkEventsMixin, FileEventsMixin): general_style_tag.set_property('scale', 100) def _is_modified(self, *args): - buffer = self.get_buffer() + buffer = self.get_buffer() + file_type = self.get_filetype() if not self._loading_file: event_system.emit("buffer_changed", (buffer, )) + event_system.emit("textDocument/didChange", (file_type, buffer, )) else: event_system.emit("buffer_changed_first_load", (buffer, )) diff --git a/src/utils/ipc_server.py b/src/utils/ipc_server.py index 4931de6..848044e 100644 --- a/src/utils/ipc_server.py +++ b/src/utils/ipc_server.py @@ -123,4 +123,4 @@ class IPCServer: logger.error("IPC Socket no longer valid.... Removing.") os.unlink(self._ipc_address) except Exception as e: - logger.error( repr(e) ) + logger.error( repr(e) ) \ No newline at end of file