generated from itdominator/Python-With-Gtk-Template
Compare commits
30 Commits
release/ne
...
master
Author | SHA1 | Date | |
---|---|---|---|
0789cec010 | |||
46d358aab7 | |||
4d65197845 | |||
3617e79150 | |||
769eb4adb2 | |||
693f569d4f | |||
b31278e114 | |||
3edb89ad5c | |||
183e7358ce | |||
e3ea9492a3 | |||
790313b63f | |||
238a05591d | |||
cb4b7faaaa | |||
84b96ace5e | |||
8b555991e4 | |||
99e9e9d3ef | |||
4fe2c7ed8f | |||
b76b7ab780 | |||
14100b09e3 | |||
c268a7ef1a | |||
7bec522a31 | |||
02ce50cb76 | |||
32a79032cc | |||
be5e159fd4 | |||
b0c519dce5 | |||
459837e85b | |||
e04e3845c2 | |||
b72d5b8e40 | |||
ce8361238f | |||
30cf03b76f |
19
README.md
19
README.md
@ -7,13 +7,24 @@ A Python and Gtk+ quasi-IDE.
|
||||
* pyxdg
|
||||
|
||||
### Note
|
||||
Currently very much a WIP effort.
|
||||
This is currently very much a WIP effort with two branches to take note of. The first uses GtkSource.View widget to handle text edits and display. The second uses the Gtk Webkit2 widget along with Ace Editor to handle text editing. The first is more traditional in visual design and editing. While the second is more like Vi, Vim, NeoVim.
|
||||
|
||||
* release/newton-with-gtksource
|
||||
* release/newton-with-webkit2
|
||||
|
||||
# LSP
|
||||
Add lsp-ws-proxy somewhere in $PATH and make executable. Download from:
|
||||
|
||||
* https://github.com/qualified/lsp-ws-proxy
|
||||
|
||||
* Move respetive sub folder content under user_config to the same places in Linux. Though, /user/share/newton can go to ~/.config folder if prefered.
|
||||
* In additiion, place the plugins folder in the same app folder you moved to /usr/share/newton or ~/.config/newton .
|
||||
|
||||
# Images
|
||||
* GtkSource.View
|
||||
![1 Newton default view. ](images/pic1.png)
|
||||
![2 Newton search and replace plus menu shown. ](images/pic2.png)
|
||||
![3 Newton displaying inline colors. ](images/pic3.png)
|
||||
![4 Newton as transparent with youtube playing below it. ](images/pic4.png)
|
||||
![4 Newton as transparent with youtube playing below it. ](images/pic4.png)
|
||||
|
||||
* Gtk Webkit2 + Ace Editor
|
||||
![5 Newton default view. ](images/pic5.png)
|
||||
![6 Bufers list. ](images/pic6.png)
|
BIN
images/pic5.png
Normal file
BIN
images/pic5.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 826 KiB |
BIN
images/pic6.png
Normal file
BIN
images/pic6.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 517 KiB |
2
plugins/ace-editor/README.txt
Normal file
2
plugins/ace-editor/README.txt
Normal file
@ -0,0 +1,2 @@
|
||||
### Note
|
||||
TBD
|
182
plugins/gtksourceview/lsp_client/client_ipc.py
Normal file
182
plugins/gtksourceview/lsp_client/client_ipc.py
Normal file
@ -0,0 +1,182 @@
|
||||
# Python imports
|
||||
import traceback
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import json
|
||||
import base64
|
||||
from multiprocessing.connection import Client
|
||||
from multiprocessing.connection import Listener
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
from .lsp_message_structs import LSPResponseRequest
|
||||
from .lsp_message_structs import LSPResponseNotification, LSPIDResponseNotification
|
||||
from .lsp_message_structs import get_message_obj
|
||||
|
||||
|
||||
|
||||
class ClientIPC:
|
||||
""" Create a Messenger so talk to LSP Manager. """
|
||||
def __init__(self, ipc_address: str = '127.0.0.1', conn_type: str = "socket"):
|
||||
self.is_ipc_alive = False
|
||||
self._ipc_port = 4848
|
||||
self._ipc_address = ipc_address
|
||||
self._conn_type = conn_type
|
||||
self._ipc_authkey = b'' + bytes(f'lsp-client-endpoint-ipc', 'utf-8')
|
||||
self._manager_ipc_authkey = b'' + bytes(f'lsp-manager-endpoint-ipc', 'utf-8')
|
||||
self._ipc_timeout = 15.0
|
||||
self._event_system = None
|
||||
|
||||
if conn_type == "socket":
|
||||
self._ipc_address = f'/tmp/lsp-client-endpoint-ipc.sock'
|
||||
self._manager_ipc_address = f'/tmp/lsp-manager-endpoint-ipc.sock'
|
||||
elif conn_type == "full_network":
|
||||
self._ipc_address = '0.0.0.0'
|
||||
elif conn_type == "full_network_unsecured":
|
||||
self._ipc_authkey = None
|
||||
self._ipc_address = '0.0.0.0'
|
||||
elif conn_type == "local_network_unsecured":
|
||||
self._ipc_authkey = None
|
||||
|
||||
|
||||
def set_event_system(self, event_system):
|
||||
self._event_system = event_system
|
||||
|
||||
def create_ipc_listener(self) -> None:
|
||||
if self._conn_type == "socket":
|
||||
if os.path.exists(self._ipc_address) and settings_manager.is_dirty_start():
|
||||
os.unlink(self._ipc_address)
|
||||
|
||||
listener = Listener(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey)
|
||||
elif "unsecured" not in self._conn_type:
|
||||
listener = Listener((self._ipc_address, self._ipc_port), authkey=self._ipc_authkey)
|
||||
else:
|
||||
listener = Listener((self._ipc_address, self._ipc_port))
|
||||
|
||||
|
||||
self.is_ipc_alive = True
|
||||
self._run_ipc_loop(listener)
|
||||
|
||||
@daemon_threaded
|
||||
def _run_ipc_loop(self, listener) -> None:
|
||||
# NOTE: Not thread safe if using with Gtk. Need to import GLib and use idle_add
|
||||
while self.is_ipc_alive:
|
||||
try:
|
||||
conn = listener.accept()
|
||||
start_time = time.perf_counter()
|
||||
self._handle_ipc_message(conn, start_time)
|
||||
except Exception as e:
|
||||
logger.debug( traceback.print_exc() )
|
||||
|
||||
listener.close()
|
||||
|
||||
def _handle_ipc_message(self, conn, start_time) -> None:
|
||||
while self.is_ipc_alive:
|
||||
msg = conn.recv()
|
||||
|
||||
if "MANAGER|" in msg:
|
||||
data = msg.split("MANAGER|")[1].strip()
|
||||
|
||||
if data:
|
||||
data_str = base64.b64decode(data.encode("utf-8")).decode("utf-8")
|
||||
lsp_response = None
|
||||
keys = None
|
||||
|
||||
try:
|
||||
lsp_response = json.loads(data_str)
|
||||
keys = lsp_response.keys()
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
if "result" in keys:
|
||||
lsp_response = LSPResponseRequest(**get_message_obj(data_str))
|
||||
|
||||
if "method" in keys:
|
||||
lsp_response = LSPResponseNotification( **get_message_obj(data_str) ) if not "id" in keys else LSPIDResponseNotification( **get_message_obj(data_str) )
|
||||
|
||||
if "notification" in keys:
|
||||
...
|
||||
|
||||
if "response" in keys:
|
||||
...
|
||||
|
||||
if "ignorable" in keys:
|
||||
...
|
||||
|
||||
if lsp_response:
|
||||
GLib.idle_add(self._do_emit, lsp_response)
|
||||
|
||||
conn.close()
|
||||
break
|
||||
|
||||
if msg in ['close connection', 'close server']:
|
||||
conn.close()
|
||||
break
|
||||
|
||||
# NOTE: Not perfect but insures we don't lock up the connection for too long.
|
||||
end_time = time.perf_counter()
|
||||
if (end_time - start_time) > self._ipc_timeout:
|
||||
conn.close()
|
||||
break
|
||||
|
||||
def _do_emit(self, lsp_response):
|
||||
self._event_system.emit("handle-lsp-message", (lsp_response,))
|
||||
|
||||
def send_manager_ipc_message(self, message: str) -> None:
|
||||
try:
|
||||
if self._conn_type == "socket":
|
||||
if not os.path.exists(self._manager_ipc_address):
|
||||
logger.error(f"Socket: {self._manager_ipc_address} doesn't exist. NOT sending message...")
|
||||
return
|
||||
|
||||
conn = Client(address=self._manager_ipc_address, family="AF_UNIX", authkey=self._manager_ipc_authkey)
|
||||
elif "unsecured" not in self._conn_type:
|
||||
conn = Client((self._ipc_address, self._ipc_port), authkey=self._ipc_authkey)
|
||||
else:
|
||||
conn = Client((self._ipc_address, self._ipc_port))
|
||||
|
||||
conn.send( f"CLIENT|{ base64.b64encode(message.encode('utf-8')).decode('utf-8') }" )
|
||||
conn.close()
|
||||
except ConnectionRefusedError as e:
|
||||
logger.error("Connection refused...")
|
||||
except Exception as e:
|
||||
logger.error( repr(e) )
|
||||
|
||||
|
||||
def send_ipc_message(self, message: str = "Empty Data...") -> None:
|
||||
try:
|
||||
if self._conn_type == "socket":
|
||||
conn = Client(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey)
|
||||
elif "unsecured" not in self._conn_type:
|
||||
conn = Client((self._ipc_address, self._ipc_port), authkey=self._ipc_authkey)
|
||||
else:
|
||||
conn = Client((self._ipc_address, self._ipc_port))
|
||||
|
||||
conn.send(message)
|
||||
conn.close()
|
||||
except ConnectionRefusedError as e:
|
||||
logger.error("Connection refused...")
|
||||
except Exception as e:
|
||||
logger.error( repr(e) )
|
||||
|
||||
def send_test_ipc_message(self, message: str = "Empty Data...") -> None:
|
||||
try:
|
||||
if self._conn_type == "socket":
|
||||
conn = Client(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey)
|
||||
elif "unsecured" not in self._conn_type:
|
||||
conn = Client((self._ipc_address, self._ipc_port), authkey=self._ipc_authkey)
|
||||
else:
|
||||
conn = Client((self._ipc_address, self._ipc_port))
|
||||
|
||||
conn.send(message)
|
||||
conn.close()
|
||||
except ConnectionRefusedError as e:
|
||||
if self._conn_type == "socket":
|
||||
logger.error("LSP Socket no longer valid.... Removing.")
|
||||
os.unlink(self._ipc_address)
|
||||
except Exception as e:
|
||||
logger.error( repr(e) )
|
7
plugins/gtksourceview/lsp_client/config.json
Normal file
7
plugins/gtksourceview/lsp_client/config.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"lsp_manager_start_command": ["python", "/opt/lsp-manager.zip"],
|
||||
"websocket": {
|
||||
"host": "localhost",
|
||||
"port": 8765
|
||||
}
|
||||
}
|
54
plugins/gtksourceview/lsp_client/lsp_message_structs.py
Normal file
54
plugins/gtksourceview/lsp_client/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):
|
||||
...
|
18
plugins/gtksourceview/lsp_client/manifest.json
Normal file
18
plugins/gtksourceview/lsp_client/manifest.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"manifest": {
|
||||
"name": "LSP Client",
|
||||
"author": "ITDominator",
|
||||
"version": "0.0.1",
|
||||
"credit": "",
|
||||
"support": "",
|
||||
"requests": {
|
||||
"pass_events": "true",
|
||||
"pass_ui_objects": [
|
||||
"separator_right"
|
||||
],
|
||||
"bind_keys": [
|
||||
"LSP Client Toggle||tggl_lsp_window:<Control>l"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
270
plugins/gtksourceview/lsp_client/plugin.py
Normal file
270
plugins/gtksourceview/lsp_client/plugin.py
Normal file
@ -0,0 +1,270 @@
|
||||
# Python imports
|
||||
import signal
|
||||
import subprocess
|
||||
import json
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from plugins.plugin_base import PluginBase
|
||||
from .client_ipc import ClientIPC
|
||||
|
||||
|
||||
|
||||
class Plugin(PluginBase):
|
||||
def __init__(self):
|
||||
|
||||
super().__init__()
|
||||
self.name = "LSP Client" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
|
||||
# where self.name should not be needed for message comms
|
||||
self.config_file = "config.json"
|
||||
self.config: dict = {}
|
||||
self.lsp_client_proc = None
|
||||
self.lsp_window = None
|
||||
|
||||
|
||||
def generate_reference_ui_element(self):
|
||||
...
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
with open(self.config_file) as f:
|
||||
self.config = json.load(f)
|
||||
except Exception as e:
|
||||
raise Exception(f"Couldn't load config.json...\n{repr(e)}")
|
||||
|
||||
self.lsp_window = Gtk.Window()
|
||||
box1 = Gtk.Box()
|
||||
box2 = Gtk.Box()
|
||||
start_btn = Gtk.Button(label = "Start LSP Client")
|
||||
stop_btn = Gtk.Button(label = "Stop LSP Client")
|
||||
pid_label = Gtk.Label(label = "LSP PID: ")
|
||||
|
||||
box1.set_orientation( Gtk.Orientation.VERTICAL )
|
||||
|
||||
self.lsp_window.set_deletable(False)
|
||||
self.lsp_window.set_skip_pager_hint(True)
|
||||
self.lsp_window.set_skip_taskbar_hint(True)
|
||||
self.lsp_window.set_title("LSP Manager")
|
||||
self.lsp_window.set_size_request(480, 320)
|
||||
|
||||
start_btn.connect("clicked", self.start_lsp_manager)
|
||||
stop_btn.connect("clicked", self.stop_lsp_manager)
|
||||
|
||||
box1.add(pid_label)
|
||||
box2.add(start_btn)
|
||||
box2.add(stop_btn)
|
||||
box1.add(box2)
|
||||
self.lsp_window.add(box1)
|
||||
|
||||
box1.show_all()
|
||||
|
||||
self.inner_subscribe_to_events()
|
||||
|
||||
def _shutting_down(self):
|
||||
self.stop_lsp_manager()
|
||||
|
||||
def _tear_down(self, widget, eve):
|
||||
return True
|
||||
|
||||
def _tggl_lsp_window(self, widget = None):
|
||||
if not self.lsp_window.is_visible():
|
||||
self.lsp_window.show()
|
||||
else:
|
||||
self.lsp_window.hide()
|
||||
|
||||
|
||||
def subscribe_to_events(self):
|
||||
self._event_system.subscribe("tggl_lsp_window", self._tggl_lsp_window)
|
||||
|
||||
def inner_subscribe_to_events(self):
|
||||
self._event_system.subscribe("shutting_down", self._shutting_down)
|
||||
|
||||
self._event_system.subscribe("textDocument/didOpen", self._lsp_did_open)
|
||||
self._event_system.subscribe("textDocument/didSave", self._lsp_did_save)
|
||||
self._event_system.subscribe("textDocument/didClose", self._lsp_did_close)
|
||||
self._event_system.subscribe("textDocument/didChange", self._lsp_did_change)
|
||||
self._event_system.subscribe("textDocument/definition", self._lsp_goto)
|
||||
self._event_system.subscribe("textDocument/completion", self._lsp_completion)
|
||||
|
||||
def start_lsp_manager(self, button):
|
||||
if self.lsp_client_proc: return
|
||||
self.lsp_client_proc = subprocess.Popen(self.config["lsp_manager_start_command"])
|
||||
self._load_client_ipc_server()
|
||||
|
||||
def _load_client_ipc_server(self):
|
||||
self.client_ipc = ClientIPC()
|
||||
self.client_ipc.set_event_system(self._event_system)
|
||||
self._ipc_realization_check(self.client_ipc)
|
||||
|
||||
if not self.client_ipc.is_ipc_alive:
|
||||
raise AppLaunchException(f"LSP IPC Server Already Exists...")
|
||||
|
||||
def _ipc_realization_check(self, ipc_server):
|
||||
try:
|
||||
ipc_server.create_ipc_listener()
|
||||
except Exception:
|
||||
ipc_server.send_test_ipc_message()
|
||||
|
||||
try:
|
||||
ipc_server.create_ipc_listener()
|
||||
except Exception as e:
|
||||
...
|
||||
|
||||
def stop_lsp_manager(self, button = None):
|
||||
if not self.lsp_client_proc: return
|
||||
if not self.lsp_client_proc.poll() is None:
|
||||
self.lsp_client_proc = None
|
||||
return
|
||||
|
||||
self.lsp_client_proc.terminate()
|
||||
self.client_ipc.is_ipc_alive = False
|
||||
self.lsp_client_proc = None
|
||||
|
||||
def _lsp_did_open(self, language_id: str, uri: str, text: str):
|
||||
if not self.lsp_client_proc: return
|
||||
|
||||
data = {
|
||||
"method": "textDocument/didOpen",
|
||||
"language_id": language_id,
|
||||
"uri": uri,
|
||||
"version": -1,
|
||||
"text": text,
|
||||
"line": -1,
|
||||
"column": -1,
|
||||
"char": ""
|
||||
}
|
||||
|
||||
self.send_message(data)
|
||||
|
||||
def _lsp_did_save(self, uri: str, text: str):
|
||||
if not self.lsp_client_proc: return
|
||||
|
||||
data = {
|
||||
"method": "textDocument/didSave",
|
||||
"language_id": "",
|
||||
"uri": uri,
|
||||
"version": -1,
|
||||
"text": text,
|
||||
"line": -1,
|
||||
"column": -1,
|
||||
"char": ""
|
||||
}
|
||||
|
||||
self.send_message(data)
|
||||
|
||||
def _lsp_did_close(self, uri: str):
|
||||
if not self.lsp_client_proc: return
|
||||
|
||||
data = {
|
||||
"method": "textDocument/didClose",
|
||||
"language_id": "",
|
||||
"uri": uri,
|
||||
"version": -1,
|
||||
"text": "",
|
||||
"line": -1,
|
||||
"column": -1,
|
||||
"char": ""
|
||||
}
|
||||
|
||||
self.send_message(data)
|
||||
|
||||
def _lsp_did_change(self, language_id: str, uri: str, buffer):
|
||||
if not self.lsp_client_proc: return
|
||||
|
||||
iter = buffer.get_iter_at_mark( buffer.get_insert() )
|
||||
line = iter.get_line()
|
||||
column = iter.get_line_offset()
|
||||
|
||||
start, end = buffer.get_bounds()
|
||||
|
||||
text = buffer.get_text(start, end, include_hidden_chars = True)
|
||||
data = {
|
||||
"method": "textDocument/didChange",
|
||||
"language_id": language_id,
|
||||
"uri": uri,
|
||||
"version": buffer.version_id,
|
||||
"text": text,
|
||||
"line": line,
|
||||
"column": column,
|
||||
"char": ""
|
||||
}
|
||||
|
||||
self.send_message(data)
|
||||
|
||||
|
||||
# def _lsp_did_change(self, language_id: str, uri: str, buffer):
|
||||
# if not self.lsp_client_proc: return
|
||||
|
||||
# iter = buffer.get_iter_at_mark( buffer.get_insert() )
|
||||
# line = iter.get_line()
|
||||
# column = iter.get_line_offset()
|
||||
# start = iter.copy()
|
||||
# end = iter.copy()
|
||||
|
||||
# start.backward_line()
|
||||
# start.forward_line()
|
||||
# end.forward_line()
|
||||
|
||||
# text = buffer.get_text(start, end, include_hidden_chars = True)
|
||||
# data = {
|
||||
# "method": "textDocument/didChange",
|
||||
# "language_id": language_id,
|
||||
# "uri": uri,
|
||||
# "version": buffer.version_id,
|
||||
# "text": text,
|
||||
# "line": line,
|
||||
# "column": column,
|
||||
# "char": ""
|
||||
# }
|
||||
|
||||
# self.send_message(data)
|
||||
|
||||
|
||||
def _lsp_goto(self, language_id: str, uri: str, line: int, column: int):
|
||||
if not self.lsp_client_proc: return
|
||||
|
||||
data = {
|
||||
"method": "textDocument/definition",
|
||||
"language_id": language_id,
|
||||
"uri": uri,
|
||||
"version": -1,
|
||||
"text": "",
|
||||
"line": line,
|
||||
"column": column,
|
||||
"char": ""
|
||||
}
|
||||
|
||||
self.send_message(data)
|
||||
|
||||
def _lsp_completion(self, source_view):
|
||||
if not self.lsp_client_proc: return
|
||||
|
||||
filepath = source_view.get_current_file()
|
||||
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()
|
||||
column = iter.get_line_offset()
|
||||
char = iter.get_char()
|
||||
|
||||
data = {
|
||||
"method": "textDocument/completion",
|
||||
"language_id": source_view.get_filetype(),
|
||||
"uri": uri,
|
||||
"version": source_view.get_version_id(),
|
||||
"text": "",
|
||||
"line": line,
|
||||
"column": column,
|
||||
"char": char
|
||||
}
|
||||
|
||||
self.send_message(data)
|
||||
|
||||
def send_message(self, data: dict):
|
||||
self.client_ipc.send_manager_ipc_message( json.dumps(data) )
|
@ -63,4 +63,4 @@ class StylingMixin:
|
||||
plural = "s" if total_count > 1 else ""
|
||||
|
||||
if total_count == 0: self.update_style(2)
|
||||
self._find_status_lbl.set_label(f"{count} results{plural} found for '{query}'")
|
||||
self._find_status_lbl.set_label(f"{count} result{plural} found for '{query}'")
|
@ -1,201 +0,0 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class Capabilities:
|
||||
data = {
|
||||
"textDocument": {
|
||||
"codeAction": {
|
||||
"dynamicRegistration": True
|
||||
},
|
||||
"codeLens": {
|
||||
"dynamicRegistration": True
|
||||
},
|
||||
"colorProvider": {
|
||||
"dynamicRegistration": True
|
||||
},
|
||||
"completion": {
|
||||
"completionItem": {
|
||||
"commitCharactersSupport": True,
|
||||
"documentationFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
],
|
||||
"snippetSupport": True
|
||||
},
|
||||
"completionItemKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25
|
||||
]
|
||||
},
|
||||
"contextSupport": True,
|
||||
"dynamicRegistration": True
|
||||
},
|
||||
"definition": {
|
||||
"dynamicRegistration": True
|
||||
},
|
||||
"documentHighlight": {
|
||||
"dynamicRegistration": True
|
||||
},
|
||||
"documentLink": {
|
||||
"dynamicRegistration": True
|
||||
},
|
||||
"documentSymbol": {
|
||||
"dynamicRegistration": True,
|
||||
"symbolKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
]
|
||||
}
|
||||
},
|
||||
"formatting": {
|
||||
"dynamicRegistration": True
|
||||
},
|
||||
"hover": {
|
||||
"contentFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
],
|
||||
"dynamicRegistration": True
|
||||
},
|
||||
"implementation": {
|
||||
"dynamicRegistration": True
|
||||
},
|
||||
"onTypeFormatting": {
|
||||
"dynamicRegistration": True
|
||||
},
|
||||
"publishDiagnostics": {
|
||||
"relatedInformation": True
|
||||
},
|
||||
"rangeFormatting": {
|
||||
"dynamicRegistration": True
|
||||
},
|
||||
"references": {
|
||||
"dynamicRegistration": True
|
||||
},
|
||||
"rename": {
|
||||
"dynamicRegistration": True
|
||||
},
|
||||
"signatureHelp": {
|
||||
"dynamicRegistration": True,
|
||||
"signatureInformation": {
|
||||
"documentationFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
]
|
||||
}
|
||||
},
|
||||
"synchronization": {
|
||||
"didSave": True,
|
||||
"dynamicRegistration": True,
|
||||
"willSave": True,
|
||||
"willSaveWaitUntil": True
|
||||
},
|
||||
"typeDefinition": {
|
||||
"dynamicRegistration": True
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"applyEdit": True,
|
||||
"configuration": True,
|
||||
"didChangeConfiguration": {
|
||||
"dynamicRegistration": True
|
||||
},
|
||||
"didChangeWatchedFiles": {
|
||||
"dynamicRegistration": True
|
||||
},
|
||||
"executeCommand": {
|
||||
"dynamicRegistration": True
|
||||
},
|
||||
"symbol": {
|
||||
"dynamicRegistration": True,
|
||||
"symbolKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
]
|
||||
}
|
||||
},
|
||||
"workspaceEdit": {
|
||||
"documentChanges": True
|
||||
},
|
||||
"workspaceFolders": True
|
||||
}
|
||||
}
|
@ -1,179 +0,0 @@
|
||||
# Python imports
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
# Lib imports
|
||||
from . import pylspclient
|
||||
|
||||
# Application imports
|
||||
from .capabilities import Capabilities
|
||||
|
||||
|
||||
|
||||
class ReadPipe(threading.Thread):
|
||||
def __init__(self, pipe):
|
||||
threading.Thread.__init__(self)
|
||||
self.pipe = pipe
|
||||
|
||||
def run(self):
|
||||
line = self.pipe.readline().decode('utf-8')
|
||||
while line:
|
||||
line = self.pipe.readline().decode('utf-8')
|
||||
|
||||
|
||||
|
||||
class LSPController:
|
||||
def __init__(self, lsp_servers_config = {}):
|
||||
super().__init__()
|
||||
|
||||
self.lsp_servers_config = lsp_servers_config
|
||||
self.lsp_clients = {}
|
||||
|
||||
def _blame(self, response):
|
||||
for d in response['diagnostics']:
|
||||
if d['severity'] == 1:
|
||||
print(f"An error occurs in {response['uri']} at {d['range']}:")
|
||||
print(f"\t[{d['source']}] {d['message']}")
|
||||
|
||||
def _shutting_down(self):
|
||||
keys = self.lsp_clients.keys()
|
||||
for key in keys:
|
||||
print(f"LSP Server: ( {key} ) Shutting Down...")
|
||||
self.lsp_clients[key].shutdown()
|
||||
self.lsp_clients[key].exit()
|
||||
|
||||
def _generate_client(self, language = "", server_proc = None):
|
||||
if not language or not server_proc: return False
|
||||
|
||||
json_rpc_endpoint = pylspclient.JsonRpcEndpoint(server_proc.stdin, server_proc.stdout)
|
||||
|
||||
callbacks = {
|
||||
"window/showMessage": print,
|
||||
"textDocument/symbolStatus": print,
|
||||
"textDocument/publishDiagnostics": self._blame,
|
||||
}
|
||||
|
||||
lsp_endpoint = pylspclient.LspEndpoint(json_rpc_endpoint, notify_callbacks = callbacks)
|
||||
lsp_client = pylspclient.LspClient(lsp_endpoint)
|
||||
|
||||
self.lsp_clients[language] = lsp_client
|
||||
return lsp_client
|
||||
|
||||
def create_client(self, language = "", server_proc = None, initialization_options = None):
|
||||
if not language or not server_proc: return False
|
||||
|
||||
root_path = None
|
||||
root_uri = 'file:///home/abaddon/Coding/Projects/Active/C_n_CPP_Projects/gtk/Newton/src/'
|
||||
workspace_folders = [{'name': 'python-lsp', 'uri': root_uri}]
|
||||
|
||||
lsp_client = self._generate_client(language, server_proc)
|
||||
lsp_client.initialize(
|
||||
processId = server_proc.pid, \
|
||||
rootPath = root_path, \
|
||||
rootUri = root_uri, \
|
||||
initializationOptions = initialization_options, \
|
||||
capabilities = Capabilities.data, \
|
||||
trace = "off", \
|
||||
workspaceFolders = workspace_folders
|
||||
)
|
||||
|
||||
lsp_client.initialized()
|
||||
|
||||
return True
|
||||
|
||||
def create_lsp_server(self, server_command: [] = []):
|
||||
if not server_command: return None
|
||||
|
||||
server_proc = subprocess.Popen(server_command, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
|
||||
read_pipe = ReadPipe(server_proc.stderr)
|
||||
read_pipe.start()
|
||||
|
||||
return server_proc
|
||||
|
||||
|
||||
def do_open(self, language_id, uri):
|
||||
if language_id in self.lsp_clients.keys():
|
||||
lsp_client = self.lsp_clients[language_id]
|
||||
else:
|
||||
lsp_client = self.load_lsp_server(language_id)
|
||||
|
||||
if lsp_client:
|
||||
self.register_opened_file(language_id, uri, lsp_client)
|
||||
|
||||
def do_save(self, language_id, uri):
|
||||
if language_id in self.lsp_clients.keys():
|
||||
self.lsp_clients[language_id].didSave(
|
||||
pylspclient.lsp_structs.TextDocumentIdentifier(uri)
|
||||
)
|
||||
|
||||
def do_close(self, language_id, uri):
|
||||
if language_id in self.lsp_clients.keys():
|
||||
self.lsp_clients[language_id].didClose(
|
||||
pylspclient.lsp_structs.TextDocumentIdentifier(uri)
|
||||
)
|
||||
|
||||
def do_goto(self, language_id, uri, line, offset):
|
||||
if language_id in self.lsp_clients.keys():
|
||||
return self.lsp_clients[language_id].definition(
|
||||
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():
|
||||
return
|
||||
|
||||
command = self.lsp_servers_config[language_id]["command"]
|
||||
config_options = self.lsp_servers_config[language_id]["initialization_options"]
|
||||
|
||||
if command:
|
||||
server_proc = self.create_lsp_server(command)
|
||||
if self.create_client(language_id, server_proc, config_options):
|
||||
return self.lsp_clients[language_id]
|
||||
|
||||
return None
|
||||
|
||||
def register_opened_file(self, language_id = "", uri = "", lsp_client = None):
|
||||
if not language_id or not uri: return
|
||||
|
||||
text = open(uri[7:], "r").read()
|
||||
version = 1
|
||||
|
||||
lsp_client.didOpen(
|
||||
pylspclient.lsp_structs.TextDocumentItem(uri, language_id, version, text)
|
||||
)
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"manifest": {
|
||||
"name": "LSP Client",
|
||||
"author": "ITDominator",
|
||||
"version": "0.0.1",
|
||||
"credit": "Avi Yeger for the pylspclient used by this plugin. Link: https://github.com/yeger00/pylspclient",
|
||||
"support": "",
|
||||
"requests": {
|
||||
"pass_events": "true"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
# Python imports
|
||||
import os
|
||||
import json
|
||||
import threading
|
||||
|
||||
# Lib imports
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
from plugins.plugin_base import PluginBase
|
||||
from .lsp_controller import LSPController
|
||||
|
||||
|
||||
|
||||
class LSPPliginException(Exception):
|
||||
...
|
||||
|
||||
|
||||
|
||||
class Plugin(PluginBase):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.name = "LSP Client" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
|
||||
# where self.name should not be needed for message comms
|
||||
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):
|
||||
...
|
||||
|
||||
def run(self):
|
||||
if os.path.exists(self.lsp_config_path):
|
||||
with open(self.lsp_config_path, "r") as f:
|
||||
self.lsp_servers_config = json.load(f)
|
||||
else:
|
||||
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
|
||||
|
||||
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("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):
|
||||
self.lsp_controller._shutting_down()
|
||||
|
||||
def cancel_timer(self):
|
||||
if self.timer:
|
||||
self.timer.cancel()
|
||||
GLib.idle_remove_by_data(None)
|
||||
|
||||
def delay_completion_glib(self, source_view, context, callback):
|
||||
GLib.idle_add(self._do_completion, source_view, context, callback)
|
||||
|
||||
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()
|
||||
|
||||
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_file()
|
||||
|
||||
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)
|
||||
|
||||
if len(results) == 1:
|
||||
result = results[0]
|
||||
file = result.uri[7:]
|
||||
line = result.range.end.line
|
||||
message = f"FILE|{file}:{line}"
|
||||
self._event_system.emit("post_file_to_ipc", message)
|
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Avi Yeger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -1,6 +0,0 @@
|
||||
__all__ = []
|
||||
|
||||
from .json_rpc_endpoint import JsonRpcEndpoint
|
||||
from .lsp_client import LspClient
|
||||
from .lsp_endpoint import LspEndpoint
|
||||
from . import lsp_structs
|
@ -1,105 +0,0 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import threading
|
||||
import json
|
||||
|
||||
from . import lsp_structs
|
||||
|
||||
|
||||
|
||||
JSON_RPC_REQ_FORMAT = "Content-Length: {json_string_len}\r\n\r\n{json_string}"
|
||||
LEN_HEADER = "Content-Length: "
|
||||
TYPE_HEADER = "Content-Type: "
|
||||
|
||||
|
||||
# TODO: add content-type
|
||||
|
||||
|
||||
class MyEncoder(json.JSONEncoder):
|
||||
"""
|
||||
Encodes an object in JSON
|
||||
"""
|
||||
|
||||
def default(self, o): # pylint: disable=E0202
|
||||
return o.__dict__
|
||||
|
||||
|
||||
class JsonRpcEndpoint(object):
|
||||
'''
|
||||
Thread safe JSON RPC endpoint implementation. Responsible to recieve and
|
||||
send JSON RPC messages, as described in the protocol. More information can
|
||||
be found: https://www.jsonrpc.org/
|
||||
'''
|
||||
|
||||
def __init__(self, stdin, stdout):
|
||||
self.stdin = stdin
|
||||
self.stdout = stdout
|
||||
self.read_lock = threading.Lock()
|
||||
self.write_lock = threading.Lock()
|
||||
|
||||
@staticmethod
|
||||
def __add_header(json_string):
|
||||
'''
|
||||
Adds a header for the given json string
|
||||
|
||||
:param str json_string: The string
|
||||
:return: the string with the header
|
||||
'''
|
||||
return JSON_RPC_REQ_FORMAT.format(json_string_len = len(json_string), json_string = json_string)
|
||||
|
||||
def send_request(self, message):
|
||||
'''
|
||||
Sends the given message.
|
||||
|
||||
:param dict message: The message to send.
|
||||
'''
|
||||
json_string = json.dumps(message, cls = MyEncoder)
|
||||
jsonrpc_req = self.__add_header(json_string)
|
||||
with self.write_lock:
|
||||
self.stdin.write(jsonrpc_req.encode())
|
||||
self.stdin.flush()
|
||||
|
||||
def recv_response(self):
|
||||
'''
|
||||
Recives a message.
|
||||
|
||||
:return: a message
|
||||
'''
|
||||
with self.read_lock:
|
||||
message_size = None
|
||||
while True:
|
||||
# read header
|
||||
line = self.stdout.readline()
|
||||
if not line:
|
||||
# server quit
|
||||
return None
|
||||
line = line.decode("utf-8")
|
||||
if not line.endswith("\r\n"):
|
||||
raise lsp_structs.ResponseError(
|
||||
lsp_structs.ErrorCodes.ParseError,
|
||||
"Bad header: missing newline")
|
||||
# remove the "\r\n"
|
||||
line = line[:-2]
|
||||
if line == "":
|
||||
# done with the headers
|
||||
break
|
||||
elif line.startswith(LEN_HEADER):
|
||||
line = line[len(LEN_HEADER):]
|
||||
if not line.isdigit():
|
||||
raise lsp_structs.ResponseError(
|
||||
lsp_structs.ErrorCodes.ParseError,
|
||||
"Bad header: size is not int")
|
||||
message_size = int(line)
|
||||
elif line.startswith(TYPE_HEADER):
|
||||
...
|
||||
else:
|
||||
raise lsp_structs.ResponseError(
|
||||
lsp_structs.ErrorCodes.ParseError,
|
||||
"Bad header: unkown header")
|
||||
if not message_size:
|
||||
raise lsp_structs.ResponseError(
|
||||
lsp_structs.ErrorCodes.ParseError,
|
||||
"Bad header: missing size")
|
||||
|
||||
jsonrpc_res = self.stdout.read(message_size).decode("utf-8")
|
||||
return json.loads(jsonrpc_res)
|
@ -1,258 +0,0 @@
|
||||
from . import lsp_structs
|
||||
|
||||
|
||||
|
||||
class LspClient(object):
|
||||
def __init__(self, lsp_endpoint):
|
||||
"""
|
||||
Constructs a new LspClient instance.
|
||||
|
||||
:param lsp_endpoint: TODO
|
||||
"""
|
||||
|
||||
self.lsp_endpoint = lsp_endpoint
|
||||
|
||||
def initialize(self, processId, rootPath, rootUri, initializationOptions, capabilities, trace, workspaceFolders):
|
||||
"""
|
||||
The initialize request is sent as the first request from the client to the server. If the server receives a request or notification
|
||||
before the initialize request it should act as follows:
|
||||
|
||||
1. For a request the response should be an error with code: -32002. The message can be picked by the server.
|
||||
2. Notifications should be dropped, except for the exit notification. This will allow the exit of a server without an initialize request.
|
||||
|
||||
Until the server has responded to the initialize request with an InitializeResult, the client must not send any additional requests or
|
||||
notifications to the server. In addition the server is not allowed to send any requests or notifications to the client until it has responded
|
||||
with an InitializeResult, with the exception that during the initialize request the server is allowed to send the notifications window/showMessage,
|
||||
window/logMessage and telemetry/event as well as the window/showMessageRequest request to the client.
|
||||
|
||||
The initialize request may only be sent once.
|
||||
|
||||
:param int processId: The process Id of the parent process that started the server. Is null if the process has not been started by another process.
|
||||
If the parent process is not alive then the server should exit (see exit notification) its process.
|
||||
:param str rootPath: The rootPath of the workspace. Is null if no folder is open. Deprecated in favour of rootUri.
|
||||
:param DocumentUri rootUri: The rootUri of the workspace. Is null if no folder is open. If both `rootPath` and `rootUri` are set
|
||||
`rootUri` wins.
|
||||
:param any initializationOptions: User provided initialization options.
|
||||
:param ClientCapabilities capabilities: The capabilities provided by the client (editor or tool).
|
||||
:param Trace trace: The initial trace setting. If omitted trace is disabled ('off').
|
||||
:param list workspaceFolders: The workspace folders configured in the client when the server starts. This property is only available if the client supports workspace folders.
|
||||
It can be `null` if the client supports workspace folders but none are configured.
|
||||
"""
|
||||
|
||||
self.lsp_endpoint.start()
|
||||
return self.lsp_endpoint.call_method("initialize", \
|
||||
processId = processId, \
|
||||
rootPath = rootPath, \
|
||||
rootUri = rootUri, \
|
||||
initializationOptions = initializationOptions, \
|
||||
capabilities = capabilities, \
|
||||
trace = trace, \
|
||||
workspaceFolders = workspaceFolders
|
||||
)
|
||||
|
||||
def initialized(self):
|
||||
"""
|
||||
The initialized notification is sent from the client to the server after the client received the result of the initialize request
|
||||
but before the client is sending any other request or notification to the server. The server can use the initialized notification
|
||||
for example to dynamically register capabilities. The initialized notification may only be sent once.
|
||||
"""
|
||||
|
||||
self.lsp_endpoint.send_notification("initialized")
|
||||
|
||||
def shutdown(self):
|
||||
"""
|
||||
The initialized notification is sent from the client to the server after the client received the result of the initialize request
|
||||
but before the client is sending any other request or notification to the server. The server can use the initialized notification
|
||||
for example to dynamically register capabilities. The initialized notification may only be sent once.
|
||||
"""
|
||||
|
||||
return self.lsp_endpoint.call_method("shutdown")
|
||||
|
||||
def exit(self):
|
||||
"""
|
||||
The initialized notification is sent from the client to the server after the client received the result of the initialize request
|
||||
but before the client is sending any other request or notification to the server. The server can use the initialized notification
|
||||
for example to dynamically register capabilities. The initialized notification may only be sent once.
|
||||
"""
|
||||
|
||||
self.lsp_endpoint.send_notification("exit")
|
||||
self.lsp_endpoint.stop()
|
||||
|
||||
|
||||
def didOpen(self, textDocument):
|
||||
"""
|
||||
The document open notification is sent from the client to the server to signal newly opened text documents. The document's truth is
|
||||
now managed by the client and the server must not try to read the document's truth using the document's uri. Open in this sense
|
||||
means it is managed by the client. It doesn't necessarily mean that its content is presented in an editor. An open notification must
|
||||
not be sent more than once without a corresponding close notification send before. This means open and close notification must be
|
||||
balanced and the max open count for a particular textDocument is one. Note that a server's ability to fulfill requests is independent
|
||||
of whether a text document is open or closed.
|
||||
|
||||
The DidOpenTextDocumentParams contain the language id the document is associated with. If the language Id of a document changes, the
|
||||
client needs to send a textDocument/didClose to the server followed by a textDocument/didOpen with the new language id if the server
|
||||
handles the new language id as well.
|
||||
|
||||
:param TextDocumentItem textDocument: The document that was opened.
|
||||
"""
|
||||
|
||||
return self.lsp_endpoint.send_notification("textDocument/didOpen", textDocument = textDocument)
|
||||
|
||||
def didSave(self, textDocument):
|
||||
"""
|
||||
:param TextDocumentIdentifier textDocument: The document that was saved.
|
||||
"""
|
||||
|
||||
return self.lsp_endpoint.send_notification("textDocument/didSave", textDocument = textDocument)
|
||||
|
||||
def didClose(self, textDocument):
|
||||
"""
|
||||
:param TextDocumentIdentifier textDocument: The document that was closed.
|
||||
"""
|
||||
return self.lsp_endpoint.send_notification("textDocument/didClose", textDocument = textDocument)
|
||||
|
||||
def didChange(self, textDocument, contentChanges):
|
||||
"""
|
||||
The document change notification is sent from the client to the server to signal changes to a text document.
|
||||
In 2.0 the shape of the params has changed to include proper version numbers and language ids.
|
||||
|
||||
:param VersionedTextDocumentIdentifier textDocument: The initial trace setting. If omitted trace is disabled ('off').
|
||||
:param TextDocumentContentChangeEvent[] contentChanges: The actual content changes. The content changes describe single state changes
|
||||
to the document. So if there are two content changes c1 and c2 for a document in state S then c1 move the document
|
||||
to S' and c2 to S''.
|
||||
"""
|
||||
|
||||
return self.lsp_endpoint.send_notification("textDocument/didChange", textDocument = textDocument, contentChanges = contentChanges)
|
||||
|
||||
def documentSymbol(self, textDocument):
|
||||
"""
|
||||
The document symbol request is sent from the client to the server to
|
||||
return a flat list of all symbols found in a given text document.
|
||||
Neither the symbol's location range nor the symbol's container name
|
||||
should be used to infer a hierarchy.
|
||||
|
||||
:param TextDocumentItem textDocument: The text document.
|
||||
"""
|
||||
result_dict = self.lsp_endpoint.call_method( "textDocument/documentSymbol", textDocument=textDocument )
|
||||
|
||||
if not result_dict: return []
|
||||
return [lsp_structs.SymbolInformation(**sym) for sym in result_dict]
|
||||
|
||||
def declaration(self, textDocument, position):
|
||||
"""
|
||||
The go to declaration request is sent from the client to the server to
|
||||
resolve the declaration location of a symbol at a given text document
|
||||
position.
|
||||
|
||||
The result type LocationLink[] got introduce with version 3.14.0 and
|
||||
depends in the corresponding client capability
|
||||
`clientCapabilities.textDocument.declaration.linkSupport`.
|
||||
|
||||
:param TextDocumentItem textDocument: The text document.
|
||||
:param Position position: The position inside the text document.
|
||||
"""
|
||||
|
||||
result_dict = self.lsp_endpoint.call_method("textDocument/declaration",
|
||||
textDocument = textDocument,
|
||||
position = position
|
||||
)
|
||||
|
||||
if not result_dict: return []
|
||||
|
||||
if "uri" in result_dict:
|
||||
return lsp_structs.Location(**result_dict)
|
||||
|
||||
return [lsp_structs.Location(**loc) if "uri" in loc else lsp_structs.LinkLocation(**loc) for loc in result_dict]
|
||||
|
||||
def definition(self, textDocument, position):
|
||||
"""
|
||||
The goto definition request is sent from the client to the server to
|
||||
resolve the definition location of a symbol at a given text document
|
||||
position.
|
||||
|
||||
:param TextDocumentItem textDocument: The text document.
|
||||
:param Position position: The position inside the text document.
|
||||
"""
|
||||
|
||||
result_dict = self.lsp_endpoint.call_method("textDocument/definition",
|
||||
textDocument = textDocument,
|
||||
position = position
|
||||
)
|
||||
|
||||
if not result_dict: return []
|
||||
return [lsp_structs.Location(**loc) for loc in result_dict]
|
||||
|
||||
def typeDefinition(self, textDocument, position):
|
||||
"""
|
||||
The goto type definition request is sent from the client to the server
|
||||
to resolve the type definition location of a symbol at a given text
|
||||
document position.
|
||||
|
||||
:param TextDocumentItem textDocument: The text document.
|
||||
:param Position position: The position inside the text document.
|
||||
"""
|
||||
|
||||
result_dict = self.lsp_endpoint.call_method("textDocument/typeDefinition",
|
||||
textDocument = textDocument,
|
||||
position = position
|
||||
)
|
||||
|
||||
if not result_dict: return []
|
||||
return [lsp_structs.Location(**loc) for loc in result_dict]
|
||||
|
||||
def signatureHelp(self, textDocument, position):
|
||||
"""
|
||||
The signature help request is sent from the client to the server to
|
||||
request signature information at a given cursor position.
|
||||
|
||||
:param TextDocumentItem textDocument: The text document.
|
||||
:param Position position: The position inside the text document.
|
||||
"""
|
||||
|
||||
result_dict = self.lsp_endpoint.call_method( "textDocument/signatureHelp",
|
||||
textDocument = textDocument,
|
||||
position = position
|
||||
)
|
||||
|
||||
if not result_dict: return []
|
||||
return lsp_structs.SignatureHelp(**result_dict)
|
||||
|
||||
def completion(self, textDocument, position, context):
|
||||
"""
|
||||
The signature help request is sent from the client to the server to
|
||||
request signature information at a given cursor position.
|
||||
|
||||
:param TextDocumentItem textDocument: The text document.
|
||||
:param Position position: The position inside the text document.
|
||||
:param CompletionContext context: The completion context. This is only
|
||||
available if the client specifies
|
||||
to send this using `ClientCapabilities.textDocument.completion.contextSupport === true`
|
||||
"""
|
||||
|
||||
result_dict = self.lsp_endpoint.call_method("textDocument/completion",
|
||||
textDocument = textDocument,
|
||||
position = position,
|
||||
context = context
|
||||
)
|
||||
if not result_dict: return []
|
||||
|
||||
if "isIncomplete" in result_dict:
|
||||
return lsp_structs.CompletionList(**result_dict)
|
||||
|
||||
return [lsp_structs.CompletionItem(**loc) for loc in result_dict]
|
||||
|
||||
def references(self, textDocument, position):
|
||||
"""
|
||||
The references request is sent from the client to the server to resolve
|
||||
project-wide references for the symbol denoted by the given text
|
||||
document position.
|
||||
|
||||
:param TextDocumentItem textDocument: The text document.
|
||||
:param Position position: The position inside the text document.
|
||||
"""
|
||||
|
||||
result_dict = self.lsp_endpoint.call_method("textDocument/references",
|
||||
textDocument = textDocument,
|
||||
position = position)
|
||||
|
||||
if not result_dict: return []
|
||||
return [lsp_structs.Location(**loc) for loc in result_dict]
|
@ -1,111 +0,0 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import threading
|
||||
|
||||
from . import lsp_structs
|
||||
|
||||
|
||||
|
||||
class LspEndpoint(threading.Thread):
|
||||
def __init__(self, json_rpc_endpoint, method_callbacks = {}, notify_callbacks = {}, timeout = 2):
|
||||
threading.Thread.__init__(self)
|
||||
self.json_rpc_endpoint = json_rpc_endpoint
|
||||
self.notify_callbacks = notify_callbacks
|
||||
self.method_callbacks = method_callbacks
|
||||
self.event_dict = {}
|
||||
self.response_dict = {}
|
||||
self.next_id = 0
|
||||
self._timeout = timeout
|
||||
self.shutdown_flag = False
|
||||
|
||||
def handle_result(self, rpc_id, result, error):
|
||||
self.response_dict[rpc_id] = (result, error)
|
||||
cond = self.event_dict[rpc_id]
|
||||
cond.acquire()
|
||||
cond.notify()
|
||||
cond.release()
|
||||
|
||||
def stop(self):
|
||||
self.shutdown_flag = True
|
||||
|
||||
def run(self):
|
||||
while not self.shutdown_flag:
|
||||
try:
|
||||
jsonrpc_message = self.json_rpc_endpoint.recv_response()
|
||||
if jsonrpc_message is None: break
|
||||
|
||||
method = jsonrpc_message.get("method")
|
||||
result = jsonrpc_message.get("result")
|
||||
error = jsonrpc_message.get("error")
|
||||
rpc_id = jsonrpc_message.get("id")
|
||||
params = jsonrpc_message.get("params")
|
||||
|
||||
if method:
|
||||
if rpc_id is not None:
|
||||
if method not in self.method_callbacks:
|
||||
raise lsp_structs.ResponseError(
|
||||
lsp_structs.ErrorCodes.MethodNotFound,
|
||||
"Method not found: {method}"
|
||||
.format(method=method))
|
||||
result = self.method_callbacks[method](params)
|
||||
self.send_response(rpc_id, result, None)
|
||||
else:
|
||||
if method not in self.notify_callbacks:
|
||||
print("Notify method not found: {method}.".format(method=method))
|
||||
else:
|
||||
self.notify_callbacks[method](params)
|
||||
else:
|
||||
self.handle_result(rpc_id, result, error)
|
||||
except lsp_structs.ResponseError as e:
|
||||
self.send_response(rpc_id, None, e)
|
||||
|
||||
def send_response(self, id, result, error):
|
||||
message_dict = {}
|
||||
message_dict["jsonrpc"] = "2.0"
|
||||
message_dict["id"] = id
|
||||
|
||||
if result:
|
||||
message_dict["result"] = result
|
||||
if error:
|
||||
message_dict["error"] = error
|
||||
|
||||
self.json_rpc_endpoint.send_request(message_dict)
|
||||
|
||||
def send_message(self, method_name, params, id=None):
|
||||
message_dict = {}
|
||||
message_dict["jsonrpc"] = "2.0"
|
||||
|
||||
if id is not None:
|
||||
message_dict["id"] = id
|
||||
|
||||
message_dict["method"] = method_name
|
||||
message_dict["params"] = params
|
||||
|
||||
self.json_rpc_endpoint.send_request(message_dict)
|
||||
|
||||
def call_method(self, method_name, **kwargs):
|
||||
current_id = self.next_id
|
||||
self.next_id += 1
|
||||
cond = threading.Condition()
|
||||
self.event_dict[current_id] = cond
|
||||
|
||||
cond.acquire()
|
||||
self.send_message(method_name, kwargs, current_id)
|
||||
if self.shutdown_flag:
|
||||
return None
|
||||
|
||||
if not cond.wait(timeout=self._timeout):
|
||||
raise TimeoutError()
|
||||
cond.release()
|
||||
|
||||
self.event_dict.pop(current_id)
|
||||
result, error = self.response_dict.pop(current_id)
|
||||
if error:
|
||||
raise lsp_structs.ResponseError(error.get("code"),
|
||||
error.get("message"),
|
||||
error.get("data"))
|
||||
|
||||
return result
|
||||
|
||||
def send_notification(self, method_name, **kwargs):
|
||||
self.send_message(method_name, kwargs)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user