generated from itdominator/Python-With-Gtk-Template
Compare commits
22 Commits
c5f0d8e597
...
c06df187a6
Author | SHA1 | Date | |
---|---|---|---|
c06df187a6 | |||
742ec1baad | |||
8eb8c4d543 | |||
84b24d6767 | |||
e3774ee5f3 | |||
73437ce5e9 | |||
52fa52864b | |||
ed63bcc093 | |||
3d37a2335a | |||
dc387d1d4c | |||
30634d9af7 | |||
765a60d154 | |||
3fd4750b2d | |||
ca923943c9 | |||
d972b93b64 | |||
28b618ea0b | |||
bc47275e57 | |||
8e615349b7 | |||
1ed502f799 | |||
ab74fdd811 | |||
07a0316703 | |||
9836285f86 |
@ -1,3 +1,5 @@
|
|||||||
PyGObject
|
PyGObject
|
||||||
pyxdg
|
pyxdg
|
||||||
setproctitle
|
setproctitle
|
||||||
|
python-lsp-server[all]
|
||||||
|
python-lsp-server[websockets]
|
||||||
|
@ -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
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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
|
@ -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():
|
||||||
|
@ -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, ))
|
@ -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):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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):
|
||||||
|
51
src/core/widgets/completion_item.py
Normal file
51
src/core/widgets/completion_item.py
Normal 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"]
|
228
src/core/widgets/completion_view.py
Normal file
228
src/core/widgets/completion_view.py
Normal 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
3
src/libs/dto/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
Dasta Class module
|
||||||
|
"""
|
54
src/libs/dto/lsp_message_structs.py
Normal file
54
src/libs/dto/lsp_message_structs.py
Normal 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):
|
||||||
|
...
|
Loading…
Reference in New Issue
Block a user