Compare commits

...

22 Commits

Author SHA1 Message Date
c06df187a6 removing completion items without text insertions 2024-10-04 00:08:20 -05:00
742ec1baad fixing additional completion insertion edge cases 2024-10-03 21:31:59 -05:00
8eb8c4d543 text insertion fixes 2024-10-02 21:54:08 -05:00
84b24d6767 Base handling of java source dir paths 2024-10-02 00:58:16 -05:00
e3774ee5f3 Extending response type 2024-09-30 18:40:23 -05:00
73437ce5e9 Handling completion code based scrolling 2024-09-28 20:07:41 -05:00
52fa52864b Handling completion insertion 2024-09-26 20:44:22 -05:00
ed63bcc093 Handling completion keyboard movement 2024-09-26 01:20:06 -05:00
3d37a2335a Implementing own completion UI logic 2024-09-25 02:11:39 -05:00
dc387d1d4c Improved ctrl+d functionality; improved undo/redo 2024-09-22 21:10:50 -05:00
30634d9af7 Moved ctrl + line mover to key held 2024-09-20 21:27:42 -05:00
765a60d154 Adding button release event; fixing multi markers 2024-09-20 21:09:49 -05:00
3fd4750b2d added goto/definition logic; fixed auto complete 2024-09-20 01:42:23 -05:00
ca923943c9 update readme 2024-09-20 01:01:09 -05:00
d972b93b64 fixed id updates; improved result checking 2024-09-20 00:59:35 -05:00
28b618ea0b Populating completion items list with manual trigger 2024-09-16 23:00:39 -05:00
bc47275e57 Initial display logic of completion request 2024-09-16 02:03:16 -05:00
8e615349b7 Removing no longer needed logging 2024-09-15 01:51:30 -05:00
1ed502f799 fixing lsp manager calls and responses 2024-09-15 01:17:45 -05:00
ab74fdd811 More wiring of lsp manager calls and responses 2024-09-15 00:24:26 -05:00
07a0316703 Initial wiring of lsp manager calls and responses 2024-09-14 01:18:26 -05:00
9836285f86 Re-added events; adeed get_text method 2024-09-12 00:22:40 -05:00
14 changed files with 614 additions and 79 deletions

View File

@ -1,3 +1,5 @@
PyGObject PyGObject
pyxdg pyxdg
setproctitle setproctitle
python-lsp-server[all]
python-lsp-server[websockets]

View File

@ -1,10 +1,19 @@
# Python imports # Python imports
import urllib.parse as url_parse
# Lib 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 GLib
from gi.repository import GtkSource
# Application imports # Application imports
from libs.dto.lsp_message_structs import LSPResponseTypes, LSPResponseRequest, LSPResponseNotification, LSPIDResponseNotification
from .key_input_controller import KeyInputController from .key_input_controller import KeyInputController
from .editor_events import EditorEventsMixin from .editor_events import EditorEventsMixin
from ...completion_item import CompletionItem
@ -31,3 +40,116 @@ class EditorControllerMixin(KeyInputController, EditorEventsMixin):
self.set_buffer_language(source_view, query) self.set_buffer_language(source_view, query)
if action == "set_buffer_style": if action == "set_buffer_style":
self.set_buffer_style(source_view, query) self.set_buffer_style(source_view, query)
def _handle_lsp_message(self, message: dict or LSPResponseType):
if not self.is_editor_focused: return # TODO: Find way to converge this
page_num, container, source_view = self.get_active_view()
page_num = None
container = None
logger.debug( f"\n\n{repr(message)}\n\n" )
if isinstance(message, dict):
...
if hasattr(message, "result"):
if type(message.result) is dict:
keys = message.result.keys()
if "items" in keys: # completion
if source_view.completion_view.get_parent():
source_view.remove(source_view.completion_view)
if len( message.result["items"] ) == 0:
return
source_view.completion_view.clear_items()
x, y = self._get_insert_line_xy(source_view)
source_view.add_child_in_window(source_view.completion_view, Gtk.TextWindowType.WIDGET, x, y)
completion_list = self.filter_completion_list( message.result["items"] )
for item in completion_list:
ci = CompletionItem()
ci.populate_completion_item(item)
source_view.completion_view.add_completion_item(ci)
source_view.completion_view.show_all()
GLib.idle_add( source_view.completion_view.select_first_row )
# completion = source_view.get_completion()
# providers = completion.get_providers()
# for provider in providers:
# if provider.__class__.__name__ == 'LSPCompletionProvider':
# source_view.completion_items = message.result["items"]
# source_view.emit("show-completion")
if "result" in keys:
...
if type(message.result) is list:
if len(message.result) == 1: # goto/aka definition
result = message.result[0]
line = result["range"]["start"]["line"]
uri = result["uri"].replace("file://", "")
if "jdt:" in uri:
uri = self.parse_java_jdt_to_uri(uri)
file = f"{uri}:{line}"
event_system.emit("handle_file_from_ipc", file)
if hasattr(message, "method"):
if message.method == "textDocument/publshDiagnostics":
...
source_view = None
def parse_java_jdt_to_uri(self, uri):
parse_str = url_parse.unquote(uri)
post_stub, \
pre_stub = parse_str.split("?=")
post_stub = post_stub.replace("jdt://contents/", "")
replace_stub = post_stub[
post_stub.index(".jar") + 4 : post_stub.index(".class")
]
post_stub = post_stub.replace(replace_stub, replace_stub.replace(".", "/") ) \
.replace(".jar", "-sources.jar:")
post_stub = post_stub.replace(".class", ".java")
pre_stub = pre_stub[
pre_stub.index("/\\/") + 2 : pre_stub.index(".jar")
]
pre_stub = pre_stub[: pre_stub.rfind("/") + 1 ].replace("\\", "")
return f"file://{pre_stub}{post_stub}"
# Gotten logic from:
# https://stackoverflow.com/questions/7139645/find-the-cursor-position-on-a-gtksourceview-window
def _get_insert_line_xy(self, source_view):
buffer = source_view.get_buffer()
iter = buffer.get_iter_at_mark( buffer.get_insert() )
iter_loc = source_view.get_iter_location(iter)
win_loc = source_view.buffer_to_window_coords(Gtk.TextWindowType.WIDGET, iter_loc.x, iter_loc.y)
win = source_view.get_window( Gtk.TextWindowType.WIDGET )
view_pos = win.get_position()
xx = win_loc[0] + view_pos[0]
yy = win_loc[1] + view_pos[1] + iter_loc.height
return xx, yy
# Note: What I really need to do (long term) is keep all results and then just
# toggle show/hide of the CompletionItems relevent to the request context.
def filter_completion_list(self, completion_list: list) -> list:
filtered_list = []
for item in completion_list:
keys = item.keys()
if "insertText" in keys or "textEdit" in keys:
filtered_list.append(item)
return filtered_list

View File

@ -43,7 +43,7 @@ class EditorEventsMixin:
file_type = source_view.get_filetype() file_type = source_view.get_filetype()
if not file_type == "buffer": if not file_type == "buffer":
uri = source_view.get_current_file().get_uri() uri = source_view.get_current_file().get_uri()
event_system.emit("textDocument/didClose", (file_type, uri,)) event_system.emit("textDocument/didClose", (uri,))
page_num = notebook.page_num(container) page_num = notebook.page_num(container)
source_view._cancel_current_file_watchers() source_view._cancel_current_file_watchers()

View File

@ -1,4 +1,5 @@
# Python imports # Python imports
import zipfile
# Lib imports # Lib imports
import gi import gi
@ -58,6 +59,8 @@ class EditorNotebook(EditorControllerMixin, Gtk.Notebook):
self.connect("key-release-event", self._key_release_event) self.connect("key-release-event", self._key_release_event)
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe("handle-lsp-message", self._handle_lsp_message)
event_system.subscribe("create_view", self._create_view) event_system.subscribe("create_view", self._create_view)
event_system.subscribe("set_buffer_style", self.action_controller) event_system.subscribe("set_buffer_style", self.action_controller)
event_system.subscribe("set_buffer_language", self.action_controller) event_system.subscribe("set_buffer_language", self.action_controller)
@ -120,7 +123,17 @@ class EditorNotebook(EditorControllerMixin, Gtk.Notebook):
return return
if isinstance(gfile, str): if isinstance(gfile, str):
parts = gfile.split(":") parts = gfile.replace("file://", "").split(":")
if len(parts) > 2:
with zipfile.ZipFile(parts[0], 'r') as file:
file.extract(parts[1][1:], "/tmp/newton_extracts")
gfile = Gio.File.new_for_path( f"/tmp/newton_extracts/{ parts[1][1:] }" )
try:
line = int(parts[2])
except Exception:
...
else:
gfile = Gio.File.new_for_path(parts[0]) gfile = Gio.File.new_for_path(parts[0])
try: try:
line = int(parts[1]) if len(parts) > 1 else 0 line = int(parts[1]) if len(parts) > 1 else 0

View File

@ -34,36 +34,21 @@ class LSPCompletionProvider(GObject.Object, GtkSource.CompletionProvider):
return context.get_iter()[1] if isinstance(context.get_iter(), tuple) else context.get_iter() return context.get_iter()[1] if isinstance(context.get_iter(), tuple) else context.get_iter()
def do_match(self, context): def do_match(self, context):
iter = self.get_iter_correctly(context)
buffer = iter.get_buffer()
if buffer.get_context_classes_at_iter(iter) != ['no-spell-check']:
return False
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
return True return True
def do_get_priority(self): def do_get_priority(self):
return 1 return 1
def do_get_activation(self): def do_get_activation(self):
return GtkSource.CompletionActivation.INTERACTIVE return GtkSource.CompletionActivation.USER_REQUESTED
def do_populate(self, context, items = []):
if hasattr(self._source_view, "completion_items"):
items = self._source_view.completion_items
def do_populate(self, context, result = None):
result = event_system.emit_and_await("textDocument/completion", (self._source_view,))
proposals = [] proposals = []
for item in items:
if result:
if not result.items is None:
for item in result.items:
proposals.append( self.create_completion_item(item) ) proposals.append( self.create_completion_item(item) )
else:
proposals.append( self.create_completion_item(result) )
context.add_proposals(self, proposals, True) context.add_proposals(self, proposals, True)
@ -82,17 +67,31 @@ class LSPCompletionProvider(GObject.Object, GtkSource.CompletionProvider):
def create_completion_item(self, item): def create_completion_item(self, item):
comp_item = GtkSource.CompletionItem.new() comp_item = GtkSource.CompletionItem.new()
comp_item.set_label(item.label) keys = item.keys()
comp_item.set_label(item["label"])
if item.textEdit: if "insertText" in keys:
if isinstance(item.textEdit, dict): comp_item.set_text(item["insertText"])
comp_item.set_text(item.textEdit["newText"])
else:
comp_item.set_text(item.textEdit)
else:
comp_item.set_text(item.insertText)
comp_item.set_icon( self.get_icon_for_type(item.kind) ) if "additionalTextEdits" in keys:
comp_item.set_info(item.documentation) comp_item.additionalTextEdits = item["additionalTextEdits"]
return comp_item return comp_item
# def create_completion_item(self, item):
# comp_item = GtkSource.CompletionItem.new()
# comp_item.set_label(item.label)
# if item.textEdit:
# if isinstance(item.textEdit, dict):
# comp_item.set_text(item.textEdit["newText"])
# else:
# comp_item.set_text(item.textEdit)
# else:
# comp_item.set_text(item.insertText)
# comp_item.set_icon( self.get_icon_for_type(item.kind) )
# comp_item.set_info(item.documentation)
# return comp_item

View File

@ -31,12 +31,38 @@ class KeyInputController:
return True return True
if keyname in [ "slash", "Up", "Down", "m", "z", "y" ]: if keyname in [ "slash", "Up", "Down", "m", "z", "y" ]:
if keyname == "Up" and not self.completion_view.get_parent():
self.keyboard_move_lines_up()
if keyname == "Down" and not self.completion_view.get_parent():
self.keyboard_move_lines_down()
if keyname == "z":
self.keyboard_undo()
if keyname == "y":
self.keyboard_redo()
return True return True
if is_alt: if is_alt:
if keyname in [ "Up", "Down", "Left", "Right" ]: if keyname in [ "Up", "Down", "Left", "Right" ]:
return True return True
if keyname in [ "Up", "Down", "Left", "Right" ]:
if self.completion_view.get_parent() and self.completion_view.is_visible():
if keyname == "Up":
self.completion_view.move_selection_up()
if keyname == "Down":
self.completion_view.move_selection_down()
if keyname == "Left":
self.remove( self.completion_view )
if keyname == "Right":
self.remove( self.completion_view )
return True
if keyname in [ "Return", "Enter" ]:
if self.completion_view.get_parent() and self.completion_view.is_visible():
return True
if len(self._multi_insert_marks) > 0: if len(self._multi_insert_marks) > 0:
if keyname == "BackSpace": if keyname == "BackSpace":
@ -81,11 +107,7 @@ class KeyInputController:
return True return True
if keyname in ["z", "y", "m", "s", "h", "g", "d", "k", "u", "equal", "minus", "Up", "Down"]: if keyname in ["z", "y", "m", "s", "h", "g", "d", "k", "u", "space", "equal", "minus"]:
if keyname == "z":
self.keyboard_undo()
if keyname == "y":
self.keyboard_redo()
if keyname == "m": if keyname == "m":
self.keyboard_insert_mark() self.keyboard_insert_mark()
if keyname == "s": if keyname == "s":
@ -100,17 +122,14 @@ class KeyInputController:
self.cut_to_buffer() self.cut_to_buffer()
if keyname == "u": if keyname == "u":
self.paste_cut_buffer() self.paste_cut_buffer()
if keyname == "space":
event_system.emit("textDocument/completion", (self, ))
if keyname == "equal": if keyname == "equal":
self.scale_up_text() self.scale_up_text()
if keyname == "minus": if keyname == "minus":
self.scale_down_text() self.scale_down_text()
if keyname == "Up":
self.keyboard_move_lines_up()
if keyname == "Down":
self.keyboard_move_lines_down()
return True return True
# Note: Sink these requets # Note: Sink these requets
@ -122,7 +141,12 @@ class KeyInputController:
self.keyboard_clear_marks() self.keyboard_clear_marks()
if keyname in {"Return", "Enter"}: if keyname in [ "Return", "Enter" ]:
if self.completion_view.get_parent() and self.completion_view.is_visible():
self.completion_view.activate_completion()
return True
if len(self._multi_insert_marks) > 0: if len(self._multi_insert_marks) > 0:
self.begin_user_action(buffer) self.begin_user_action(buffer)
with buffer.freeze_notify(): with buffer.freeze_notify():

View File

@ -11,8 +11,8 @@ from gi.repository import Gio
from gi.repository import GtkSource from gi.repository import GtkSource
# Application imports # Application imports
# from ..custom_completion_providers.lsp_completion_provider import LSPCompletionProvider from ..custom_completion_providers.lsp_completion_provider import LSPCompletionProvider
from ..custom_completion_providers.python_completion_provider import PythonCompletionProvider # from ..custom_completion_providers.python_completion_provider import PythonCompletionProvider
class FileEventsMixin: class FileEventsMixin:
@ -31,6 +31,7 @@ class FileEventsMixin:
def save_file(self): def save_file(self):
self._skip_file_load = True self._skip_file_load = True
gfile = event_system.emit_and_await("save_file_dialog", (self._current_filename, self._current_file)) if not self._current_file else self._current_file gfile = event_system.emit_and_await("save_file_dialog", (self._current_filename, self._current_file)) if not self._current_file else self._current_file
event_system.emit("textDocument/didSave", (self._current_file.get_uri(), self.get_text()))
if not gfile: if not gfile:
self._skip_file_load = False self._skip_file_load = False
@ -146,18 +147,14 @@ class FileEventsMixin:
buffer.uri = uri buffer.uri = uri
buffer.language_id = self._current_filetype buffer.language_id = self._current_filetype
event_system.emit("textDocument/didOpen", (self._current_filetype, uri,)) event_system.emit("textDocument/didOpen", (self._current_filetype, uri, self.get_text()))
word_completion = GtkSource.CompletionWords.new("word_completion") # word_completion = GtkSource.CompletionWords.new("word_completion")
word_completion.register(buffer) # word_completion.register(buffer)
self._completion.add_provider(word_completion) # self._completion.add_provider(word_completion)
# lsp_completion_provider = LSPCompletionProvider(self) # lsp_completion_provider = LSPCompletionProvider(self)
# self._completion.add_provider(lsp_completion_provider) # self._completion.add_provider(lsp_completion_provider)
# if self._current_filetype in ("python", "python3"):
# py_lsp_completion_provider = PythonCompletionProvider(uri)
# self._completion.add_provider(py_lsp_completion_provider)
self.got_to_line(buffer, line) self.got_to_line(buffer, line)
event_system.emit("buffer_changed_first_load", (buffer, )) event_system.emit("buffer_changed_first_load", (buffer, ))

View File

@ -17,6 +17,7 @@ from .source_view_controller import SourceViewControllerMixin
# from .custom_completion_providers.example_completion_provider import ExampleCompletionProvider # from .custom_completion_providers.example_completion_provider import ExampleCompletionProvider
# from .custom_completion_providers.python_completion_provider import PythonCompletionProvider # from .custom_completion_providers.python_completion_provider import PythonCompletionProvider
from ...completion_view import CompletionView
@ -37,6 +38,7 @@ class SourceView(SourceViewControllerMixin, GtkSource.View):
self._cut_buffer: str = "" self._cut_buffer: str = ""
self._timer: threading.Timer = None self._timer: threading.Timer = None
self._idle_id: int = None self._idle_id: int = None
self._version_id: int = 1
self._skip_file_load = False self._skip_file_load = False
self._ignore_internal_change = False self._ignore_internal_change = False
@ -47,6 +49,9 @@ class SourceView(SourceViewControllerMixin, GtkSource.View):
self._multi_insert_marks = [] self._multi_insert_marks = []
self.freeze_multi_line_insert = False self.freeze_multi_line_insert = False
self.completion_view = CompletionView()
# self.completion_items = []
self._setup_styling() self._setup_styling()
self._setup_signals() self._setup_signals()
self._subscribe_to_events() self._subscribe_to_events()
@ -86,6 +91,7 @@ class SourceView(SourceViewControllerMixin, GtkSource.View):
self.connect("key-press-event", self._key_press_event) self.connect("key-press-event", self._key_press_event)
self.connect("key-release-event", self._key_release_event) self.connect("key-release-event", self._key_release_event)
self.connect("button-press-event", self._button_press_event) self.connect("button-press-event", self._button_press_event)
self.connect("button-release-event", self._button_release_event)
self.connect("scroll-event", self._scroll_event) self.connect("scroll-event", self._scroll_event)
buffer = self.get_buffer() buffer = self.get_buffer()
@ -94,7 +100,6 @@ class SourceView(SourceViewControllerMixin, GtkSource.View):
buffer.connect('insert-text', self._insert_text) buffer.connect('insert-text', self._insert_text)
buffer.connect('modified-changed', self._buffer_modified_changed) buffer.connect('modified-changed', self._buffer_modified_changed)
def _subscribe_to_events(self): def _subscribe_to_events(self):
... ...

View File

@ -13,12 +13,20 @@ from .source_view_events import SourceViewEvents
class SourceViewControllerMixin(KeyInputController, SourceViewEvents): class SourceViewControllerMixin(KeyInputController, SourceViewEvents):
def get_text(self):
buffer = self.get_buffer()
start_itr, end_itr = buffer.get_bounds()
return buffer.get_text(start_itr, end_itr, True)
def get_current_file(self): def get_current_file(self):
return self._current_file return self._current_file
def get_filetype(self): def get_filetype(self):
return self._current_filetype return self._current_filetype
def get_version_id(self):
return self._version_id
def set_buffer_language(self, buffer, language = "python3"): def set_buffer_language(self, buffer, language = "python3"):
buffer.set_language( self._language_manager.get_language(language) ) buffer.set_language( self._language_manager.get_language(language) )
@ -36,11 +44,22 @@ class SourceViewControllerMixin(KeyInputController, SourceViewEvents):
def duplicate_line(self, buffer = None): def duplicate_line(self, buffer = None):
buffer = self.get_buffer() if not buffer else buffer buffer = self.get_buffer() if not buffer else buffer
if not buffer.get_has_selection():
had_selection = False
itr = buffer.get_iter_at_mark( buffer.get_insert() ) itr = buffer.get_iter_at_mark( buffer.get_insert() )
start_itr = itr.copy() start_itr = itr.copy()
end_itr = itr.copy() end_itr = itr.copy()
start_line = itr.get_line() + 1 start_line = itr.get_line() + 1
start_char = itr.get_line_offset() start_char = itr.get_line_offset()
else:
had_selection = True
start_itr, end_itr = buffer.get_selection_bounds()
sline = start_itr.get_line()
eline = end_itr.get_line()
start_line = eline + 1
start_char = start_itr.get_line_offset()
end_char = end_itr.get_line_offset()
range_line_size = eline - sline
start_itr.backward_visible_line() start_itr.backward_visible_line()
start_itr.forward_line() start_itr.forward_line()
@ -48,12 +67,16 @@ class SourceViewControllerMixin(KeyInputController, SourceViewEvents):
end_itr.backward_char() end_itr.backward_char()
line_str = buffer.get_slice(start_itr, end_itr, True) line_str = buffer.get_slice(start_itr, end_itr, True)
end_itr.forward_char() end_itr.forward_char()
buffer.insert(end_itr, f"{line_str}\n", -1) buffer.insert(end_itr, f"{line_str}\n", -1)
if not had_selection:
new_itr = buffer.get_iter_at_line_offset(start_line, start_char) new_itr = buffer.get_iter_at_line_offset(start_line, start_char)
buffer.place_cursor(new_itr) buffer.place_cursor(new_itr)
else:
new_itr = buffer.get_iter_at_line_offset(start_line, start_char)
new_end_itr = buffer.get_iter_at_line_offset((start_line + range_line_size), end_char)
buffer.select_range(new_itr, new_end_itr)
def cut_to_buffer(self, buffer = None): def cut_to_buffer(self, buffer = None):
self.cancel_timer() self.cancel_timer()

View File

@ -14,6 +14,7 @@ from .mixins.source_file_events_mixin import FileEventsMixin
from .mixins.source_mark_events_mixin import MarkEventsMixin from .mixins.source_mark_events_mixin import MarkEventsMixin
class SourceViewEvents(SourceViewDnDMixin, MarkEventsMixin, FileEventsMixin): class SourceViewEvents(SourceViewDnDMixin, MarkEventsMixin, FileEventsMixin):
def _create_default_tag(self, buffer): def _create_default_tag(self, buffer):
general_style_tag = buffer.create_tag('general_style') general_style_tag = buffer.create_tag('general_style')
@ -25,8 +26,11 @@ class SourceViewEvents(SourceViewDnDMixin, MarkEventsMixin, FileEventsMixin):
file_type = self.get_filetype() file_type = self.get_filetype()
if not self._loading_file: if not self._loading_file:
buffer.version_id = self._version_id
self._version_id += 1
event_system.emit("buffer_changed", (buffer, )) event_system.emit("buffer_changed", (buffer, ))
# event_system.emit("textDocument/didChange", (file_type, buffer, )) event_system.emit("textDocument/didChange", (file_type, self.get_current_file().get_uri(), buffer, ))
# event_system.emit("textDocument/completion", (self, )) # event_system.emit("textDocument/completion", (self, ))
self.update_cursor_position(buffer) self.update_cursor_position(buffer)
@ -44,13 +48,23 @@ class SourceViewEvents(SourceViewDnDMixin, MarkEventsMixin, FileEventsMixin):
def _button_press_event(self, widget = None, eve = None, user_data = None): def _button_press_event(self, widget = None, eve = None, user_data = None):
if eve.type == Gdk.EventType.BUTTON_PRESS and eve.button == 1 : # l-click if eve.button == 1 : # l-click
...
elif eve.button == 2: # m-click
...
elif eve.button == 3: # r-click
...
def _button_release_event(self, widget = None, eve = None, user_data = None):
if eve.button == 1 : # l-click
self.keyboard_clear_marks()
if eve.state & Gdk.ModifierType.CONTROL_MASK:
self.go_to_call()
elif eve.button == 2: # m-click
if eve.state & Gdk.ModifierType.CONTROL_MASK: if eve.state & Gdk.ModifierType.CONTROL_MASK:
self.button_press_insert_mark(eve) self.button_press_insert_mark(eve)
return True return True
else: elif eve.button == 3: # r-click
self.keyboard_clear_marks()
elif eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click
... ...
def _scroll_event(self, widget, eve): def _scroll_event(self, widget, eve):

View File

@ -0,0 +1,51 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
class CompletionItem(Gtk.Label):
def __init__(self):
super(CompletionItem, self).__init__()
self.kind: int = -1
self.newText: str = ""
self.insertText: str = ""
self.textEdit: [] = []
self.additionalTextEdits: [] = []
self._setup_styling()
self._setup_signals()
self.show()
def _setup_styling(self):
ctx = self.get_style_context()
ctx.add_class("completion-item")
def _setup_signals(self):
...
def populate_completion_item(self, item):
keys = item.keys()
self.set_label(item["label"])
if "kind" in keys:
self.kind = item["kind"]
if "insertText" in keys:
self.insertText = item["insertText"]
if "textEdit" in keys:
self.textEdit = item["textEdit"]
self.newText = item["textEdit"]["newText"]
if "additionalTextEdits" in keys:
self.additionalTextEdits = item["additionalTextEdits"]

View File

@ -0,0 +1,228 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GLib
# Application imports
from .completion_item import CompletionItem
class CompletionView(Gtk.ScrolledWindow):
def __init__(self):
super(CompletionView, self).__init__()
self.vadjustment = self.get_vadjustment()
self.button_box = None
self._setup_styling()
self._setup_signals()
self._load_widgets()
self.show_all()
def _setup_styling(self):
ctx = self.get_style_context()
ctx.add_class("completion-view")
self.set_margin_top(10)
self.set_margin_bottom(10)
self.set_margin_start(10)
self.set_margin_end(10)
self.set_size_request(320, -1)
self.set_min_content_height(120)
self.set_max_content_height(480)
self.set_overlay_scrolling(False)
self.set_policy( Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC ) # hbar, vbar
def _setup_signals(self):
...
def _load_widgets(self):
viewport = Gtk.Viewport()
self.button_box = Gtk.ListBox()
self.button_box.set_hexpand( True )
self.button_box.set_placeholder( Gtk.Label(label = "No completion data...") )
self.button_box.set_selection_mode( Gtk.SelectionMode.BROWSE )
self.button_box.connect("key-release-event", self._key_release_event)
self.button_box.connect("button-release-event", self._button_release_event)
viewport.add(self.button_box)
self.add(viewport)
def _key_release_event(self, widget, eve):
keyname = Gdk.keyval_name(eve.keyval)
modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK)
is_control = True if modifiers & Gdk.ModifierType.CONTROL_MASK else False
is_shift = True if modifiers & Gdk.ModifierType.SHIFT_MASK else False
if is_control:
return True
if keyname in [ "Enter", "Return" ]:
self.activate_completion()
return True
def _button_release_event(self, widget, eve):
if eve.button == 1: # lclick
self.activate_completion()
return True
def clear_items(self):
for child in self.button_box.get_children():
self.button_box.remove(child)
def add_completion_item(self, item: CompletionItem):
self.button_box.add(item)
def move_selection_up(self):
srow = self.button_box.get_selected_row()
if not srow:
self.select_last_row()
return
index = srow.get_index() - 1
if index == -1:
self.select_last_row()
return
row = self.button_box.get_row_at_index(index)
self.select_and_scroll_to_view(row, self.vadjustment.get_value() - row.get_allocation().height)
def move_selection_down(self):
srow = self.button_box.get_selected_row()
if not srow:
self.select_first_row()
return
index = srow.get_index() + 1
if index > (len( self.button_box.get_children() ) - 1):
index = 0
self.select_first_row()
return
row = self.button_box.get_row_at_index(index)
self.select_and_scroll_to_view(row, self.vadjustment.get_value() + row.get_allocation().height)
def select_first_row(self):
row = self.button_box.get_row_at_index(0)
if not row: return
self.select_and_scroll_to_view(row, self.vadjustment.get_lower())
def select_last_row(self):
row = self.button_box.get_row_at_index( len( self.button_box.get_children() ) - 1 )
if not row: return
self.select_and_scroll_to_view(row, self.vadjustment.get_upper())
def select_and_scroll_to_view(self, row, adjustment: float):
self.button_box.select_row(row)
self.vadjustment.set_value( adjustment )
def activate_completion(self):
completion_item = self.button_box.get_selected_row().get_child()
source_view = self.get_parent()
buffer = source_view.get_buffer()
siter = buffer.get_iter_at_mark( buffer.get_insert() )
pre_char = self.get_pre_char(siter)
if completion_item.textEdit:
self.process_range_insert(buffer, completion_item.textEdit, completion_item.newText)
for edit in completion_item.additionalTextEdits:
self.process_range_insert(buffer, edit, edit["newText"])
source_view.remove(self)
GLib.idle_add( source_view.grab_focus )
return
if pre_char == '.':
buffer.insert(siter, completion_item.insertText, -1)
source_view.remove(self)
GLib.idle_add( source_view.grab_focus )
return
if siter.inside_word() or siter.ends_word() or pre_char == '_':
eiter = siter.copy()
siter.backward_visible_word_start()
self.get_word_start(siter)
if not eiter.ends_word() and not pre_char == '_':
eiter.forward_word_end()
buffer.delete(siter, eiter)
buffer.insert(siter, completion_item.insertText, -1)
source_view.remove(self)
GLib.idle_add( source_view.grab_focus )
def process_range_insert(self, buffer, insert_data: {}, text: str):
sline = insert_data["range"]["start"]["line"]
schar = insert_data["range"]["start"]["character"]
eline = insert_data["range"]["end"]["line"]
echar = insert_data["range"]["end"]["character"]
siter = buffer.get_iter_at_line_offset( sline, schar )
eiter = buffer.get_iter_at_line_offset( eline, echar )
buffer.delete(siter, eiter)
buffer.insert(siter, text, -1)
def get_word_start(self, iter):
pre_char = self.get_pre_char(iter)
while pre_char == '_':
iter.backward_visible_word_start()
pre_char = self.get_pre_char(iter)
def get_pre_char(self, iter):
pre_char = None
if iter.backward_char():
pre_char = iter.get_char()
iter.forward_char()
return pre_char
# export const Text = 1;
# export const Method = 2;
# export const Function = 3;
# export const Constructor = 4;
# export const Field = 5;
# export const Variable = 6;
# export const Class = 7;
# export const Interface = 8;
# export const Module = 9;
# export const Property = 10;
# export const Unit = 11;
# export const Value = 12;
# export const Enum = 13;
# export const Keyword = 14;
# export const Snippet = 15;
# export const Color = 16;
# export const File = 17;
# export const Reference = 18;
# export const Folder = 19;
# export const EnumMember = 20;
# export const Constant = 21;
# export const Struct = 22;
# export const Event = 23;
# export const Operator = 24;
# export const TypeParameter = 25;
def sort_completion_list(self, completion_list: list) -> list:
new_completion_list = []
for item in filtered_list:
keys = item.keys()
if "insertText" in keys or "textEdit" in keys:
new_completion_list.append(item)
return new_completion_list

3
src/libs/dto/__init__.py Normal file
View File

@ -0,0 +1,3 @@
"""
Dasta Class module
"""

View File

@ -0,0 +1,54 @@
# Python imports
from dataclasses import dataclass
import json
# Lib imports
# Application imports
def get_message_obj(data: str):
return json.loads(data)
@dataclass
class LSPResponseRequest(object):
"""
Constructs a new LSP Response Request instance.
:param id result: The id of the given message.
:param dict result: The arguments of the given method.
"""
jsonrpc: str
id: int
result: dict
@dataclass
class LSPResponseNotification(object):
"""
Constructs a new LSP Response Notification instance.
:param str method: The type of lsp notification being made.
:params dict result: The arguments of the given method.
"""
jsonrpc: str
method: str
params: dict
@dataclass
class LSPIDResponseNotification(object):
"""
Constructs a new LSP Response Notification instance.
:param str method: The type of lsp notification being made.
:params dict result: The arguments of the given method.
"""
jsonrpc: str
id: int
method: str
params: dict
class LSPResponseTypes(LSPResponseRequest, LSPResponseNotification, LSPIDResponseNotification):
...