generated from itdominator/Python-With-Gtk-Template
Compare commits
No commits in common. "0789cec010931b240ad28f35bbd9d880ef3cef0d" and "183e7358ce5bb7bee7a00a2ce154680ce2d67289" have entirely different histories.
0789cec010
...
183e7358ce
201
plugins/gtksourceview/lsp_client/capabilities.py
Normal file
201
plugins/gtksourceview/lsp_client/capabilities.py
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
# 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,182 +0,0 @@
|
|||||||
# 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) )
|
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"lsp_manager_start_command": ["python", "/opt/lsp-manager.zip"],
|
|
||||||
"websocket": {
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 8765
|
|
||||||
}
|
|
||||||
}
|
|
187
plugins/gtksourceview/lsp_client/lsp_controller.py
Normal file
187
plugins/gtksourceview/lsp_client/lsp_controller.py
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
# 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.daemon = True
|
||||||
|
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/Python_Projects/000_Usable/gtk/Newton_Editor/src/'
|
||||||
|
# workspace_folders = [{'name': 'python-lsp', 'uri': root_uri}]
|
||||||
|
root_uri = ''
|
||||||
|
workspace_folders = [{'name': '', '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", \
|
||||||
|
# trace = "on", \
|
||||||
|
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, uri, 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)
|
||||||
|
text_document = pylspclient.lsp_structs.TextDocumentItem(uri, language_id, 1, text)
|
||||||
|
change_event = pylspclient.lsp_structs.TextDocumentContentChangeEvent(range_info, text_length, text)
|
||||||
|
|
||||||
|
return self.lsp_clients[language_id].didChange( text_document, 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),
|
||||||
|
None
|
||||||
|
# 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,54 +0,0 @@
|
|||||||
# 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):
|
|
||||||
...
|
|
@ -3,16 +3,10 @@
|
|||||||
"name": "LSP Client",
|
"name": "LSP Client",
|
||||||
"author": "ITDominator",
|
"author": "ITDominator",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"credit": "",
|
"credit": "Avi Yeger for the pylspclient used by this plugin. Link: https://github.com/yeger00/pylspclient",
|
||||||
"support": "",
|
"support": "",
|
||||||
"requests": {
|
"requests": {
|
||||||
"pass_events": "true",
|
"pass_events": "true"
|
||||||
"pass_ui_objects": [
|
|
||||||
"separator_right"
|
|
||||||
],
|
|
||||||
"bind_keys": [
|
|
||||||
"LSP Client Toggle||tggl_lsp_window:<Control>l"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,270 +1,114 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
import signal
|
import os
|
||||||
import subprocess
|
|
||||||
import json
|
import json
|
||||||
|
import threading
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
from gi.repository import GLib
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
from gi.repository import Gtk
|
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
from plugins.plugin_base import PluginBase
|
from plugins.plugin_base import PluginBase
|
||||||
from .client_ipc import ClientIPC
|
from .lsp_controller import LSPController
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class LSPPliginException(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Plugin(PluginBase):
|
class Plugin(PluginBase):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
super().__init__()
|
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.name = "LSP Client" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
|
||||||
self.config_file = "config.json"
|
# where self.name should not be needed for message comms
|
||||||
self.config: dict = {}
|
self.lsp_config_path: str = os.path.dirname(os.path.realpath(__file__)) + "/../../lsp_servers_config.json"
|
||||||
self.lsp_client_proc = None
|
self.lsp_servers_config: dict = {}
|
||||||
self.lsp_window = None
|
self.lsp_controller = None
|
||||||
|
self.timer = None
|
||||||
|
|
||||||
|
|
||||||
def generate_reference_ui_element(self):
|
def generate_reference_ui_element(self):
|
||||||
...
|
...
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
if os.path.exists(self.lsp_config_path):
|
||||||
with open(self.config_file) as f:
|
with open(self.lsp_config_path, "r") as f:
|
||||||
self.config = json.load(f)
|
self.lsp_servers_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:
|
else:
|
||||||
self.lsp_window.hide()
|
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 subscribe_to_events(self):
|
||||||
self._event_system.subscribe("tggl_lsp_window", self._tggl_lsp_window)
|
...
|
||||||
|
|
||||||
def inner_subscribe_to_events(self):
|
def inner_subscribe_to_events(self):
|
||||||
self._event_system.subscribe("shutting_down", self._shutting_down)
|
self._event_system.subscribe("shutting_down", self._shutting_down)
|
||||||
|
|
||||||
self._event_system.subscribe("textDocument/didOpen", self._lsp_did_open)
|
# self._event_system.subscribe("buffer_changed", self._buffer_changed)
|
||||||
self._event_system.subscribe("textDocument/didSave", self._lsp_did_save)
|
self._event_system.subscribe("textDocument/didChange", self._buffer_changed)
|
||||||
self._event_system.subscribe("textDocument/didClose", self._lsp_did_close)
|
self._event_system.subscribe("textDocument/didOpen", self.lsp_controller.do_open)
|
||||||
self._event_system.subscribe("textDocument/didChange", self._lsp_did_change)
|
self._event_system.subscribe("textDocument/didSave", self.lsp_controller.do_save)
|
||||||
self._event_system.subscribe("textDocument/definition", self._lsp_goto)
|
self._event_system.subscribe("textDocument/didClose", self.lsp_controller.do_close)
|
||||||
self._event_system.subscribe("textDocument/completion", self._lsp_completion)
|
self._event_system.subscribe("textDocument/definition", self._do_goto)
|
||||||
|
self._event_system.subscribe("textDocument/completion", self._do_completion)
|
||||||
|
|
||||||
def start_lsp_manager(self, button):
|
def _shutting_down(self):
|
||||||
if self.lsp_client_proc: return
|
self.lsp_controller._shutting_down()
|
||||||
self.lsp_client_proc = subprocess.Popen(self.config["lsp_manager_start_command"])
|
|
||||||
self._load_client_ipc_server()
|
|
||||||
|
|
||||||
def _load_client_ipc_server(self):
|
def _buffer_changed(self, file_type, buffer):
|
||||||
self.client_ipc = ClientIPC()
|
iter = buffer.get_iter_at_mark( buffer.get_insert() )
|
||||||
self.client_ipc.set_event_system(self._event_system)
|
line = iter.get_line()
|
||||||
self._ipc_realization_check(self.client_ipc)
|
start = iter.copy()
|
||||||
|
end = iter.copy()
|
||||||
|
|
||||||
if not self.client_ipc.is_ipc_alive:
|
start.backward_line()
|
||||||
raise AppLaunchException(f"LSP IPC Server Already Exists...")
|
start.forward_line()
|
||||||
|
end.forward_to_line_end()
|
||||||
|
|
||||||
def _ipc_realization_check(self, ipc_server):
|
text = buffer.get_text(start, end, include_hidden_chars = False)
|
||||||
try:
|
result = self.lsp_controller.do_change(buffer.uri, buffer.language_id, line, start, end, text)
|
||||||
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):
|
def _do_completion(self, source_view):
|
||||||
# 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()
|
filepath = source_view.get_current_file()
|
||||||
|
|
||||||
if not filepath: return
|
if not filepath: return
|
||||||
|
|
||||||
uri = filepath.get_uri()
|
uri = filepath.get_uri()
|
||||||
buffer = source_view.get_buffer()
|
buffer = source_view.get_buffer()
|
||||||
iter = buffer.get_iter_at_mark( buffer.get_insert() )
|
iter = buffer.get_iter_at_mark( buffer.get_insert() )
|
||||||
line = iter.get_line()
|
line = iter.get_line()
|
||||||
column = iter.get_line_offset()
|
|
||||||
char = iter.get_char()
|
|
||||||
|
|
||||||
data = {
|
_char = iter.get_char()
|
||||||
"method": "textDocument/completion",
|
if iter.backward_char():
|
||||||
"language_id": source_view.get_filetype(),
|
_char = iter.get_char()
|
||||||
"uri": uri,
|
|
||||||
"version": source_view.get_version_id(),
|
|
||||||
"text": "",
|
|
||||||
"line": line,
|
|
||||||
"column": column,
|
|
||||||
"char": char
|
|
||||||
}
|
|
||||||
|
|
||||||
self.send_message(data)
|
offset = iter.get_line_offset()
|
||||||
|
result = self.lsp_controller.do_completion(
|
||||||
|
source_view.get_filetype(),
|
||||||
|
uri,
|
||||||
|
line,
|
||||||
|
offset,
|
||||||
|
_char
|
||||||
|
)
|
||||||
|
|
||||||
def send_message(self, data: dict):
|
return result
|
||||||
self.client_ipc.send_manager_ipc_message( json.dumps(data) )
|
|
||||||
|
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)
|
21
plugins/gtksourceview/lsp_client/pylspclient/LICENSE
Normal file
21
plugins/gtksourceview/lsp_client/pylspclient/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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.
|
6
plugins/gtksourceview/lsp_client/pylspclient/__init__.py
Normal file
6
plugins/gtksourceview/lsp_client/pylspclient/__init__.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
__all__ = []
|
||||||
|
|
||||||
|
from .json_rpc_endpoint import JsonRpcEndpoint
|
||||||
|
from .lsp_client import LspClient
|
||||||
|
from .lsp_endpoint import LspEndpoint
|
||||||
|
from . import lsp_structs
|
@ -0,0 +1,105 @@
|
|||||||
|
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)
|
257
plugins/gtksourceview/lsp_client/pylspclient/lsp_client.py
Normal file
257
plugins/gtksourceview/lsp_client/pylspclient/lsp_client.py
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.lsp_endpoint.call_method("shutdown")
|
||||||
|
|
||||||
|
def exit(self):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
|
||||||
|
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 TextDocumentItem textDocument: The text document.
|
||||||
|
: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]
|
111
plugins/gtksourceview/lsp_client/pylspclient/lsp_endpoint.py
Normal file
111
plugins/gtksourceview/lsp_client/pylspclient/lsp_endpoint.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
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)
|
566
plugins/gtksourceview/lsp_client/pylspclient/lsp_structs.py
Normal file
566
plugins/gtksourceview/lsp_client/pylspclient/lsp_structs.py
Normal file
@ -0,0 +1,566 @@
|
|||||||
|
import enum
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def to_type(o, new_type):
|
||||||
|
'''
|
||||||
|
Helper funciton that receives an object or a dict and convert it to a new
|
||||||
|
given type.
|
||||||
|
|
||||||
|
:param object|dict o: The object to convert
|
||||||
|
:param Type new_type: The type to convert to.
|
||||||
|
'''
|
||||||
|
|
||||||
|
return o if new_type == type(o) else new_type(**o)
|
||||||
|
|
||||||
|
|
||||||
|
class Position(object):
|
||||||
|
def __init__(self, line, character):
|
||||||
|
"""
|
||||||
|
Constructs a new Position instance.
|
||||||
|
|
||||||
|
:param int line: Line position in a document (zero-based).
|
||||||
|
:param int character: Character offset on a line in a document
|
||||||
|
(zero-based).
|
||||||
|
"""
|
||||||
|
self.line = line
|
||||||
|
self.character = character
|
||||||
|
|
||||||
|
|
||||||
|
class Range(object):
|
||||||
|
def __init__(self, start, end):
|
||||||
|
"""
|
||||||
|
Constructs a new Range instance.
|
||||||
|
|
||||||
|
:param Position start: The range's start position.
|
||||||
|
:param Position end: The range's end position.
|
||||||
|
"""
|
||||||
|
self.start = to_type(start, Position)
|
||||||
|
self.end = to_type(end, Position)
|
||||||
|
|
||||||
|
|
||||||
|
class Location(object):
|
||||||
|
"""
|
||||||
|
Represents a location inside a resource, such as a line inside a text file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, uri, range):
|
||||||
|
"""
|
||||||
|
Constructs a new Location instance.
|
||||||
|
|
||||||
|
:param str uri: Resource file.
|
||||||
|
:param Range range: The range inside the file
|
||||||
|
"""
|
||||||
|
self.uri = uri
|
||||||
|
self.range = to_type(range, Range)
|
||||||
|
|
||||||
|
|
||||||
|
class LocationLink(object):
|
||||||
|
"""
|
||||||
|
Represents a link between a source and a target location.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, originSelectionRange, targetUri, targetRange, targetSelectionRange):
|
||||||
|
"""
|
||||||
|
Constructs a new LocationLink instance.
|
||||||
|
|
||||||
|
:param Range originSelectionRange: Span of the origin of this link.
|
||||||
|
Used as the underlined span for mouse interaction. Defaults to the word range at the mouse position.
|
||||||
|
:param str targetUri: The target resource identifier of this link.
|
||||||
|
:param Range targetRange: The full target range of this link. If the target for example is a symbol then target
|
||||||
|
range is the range enclosing this symbol not including leading/trailing whitespace but everything else
|
||||||
|
like comments. This information is typically used to highlight the range in the editor.
|
||||||
|
:param Range targetSelectionRange: The range that should be selected and revealed when this link is being followed,
|
||||||
|
e.g the name of a function. Must be contained by the the `targetRange`. See also `DocumentSymbol#range`
|
||||||
|
"""
|
||||||
|
self.originSelectionRange = to_type(originSelectionRange, Range)
|
||||||
|
self.targetUri = targetUri
|
||||||
|
self.targetRange = to_type(targetRange, Range)
|
||||||
|
self.targetSelectionRange = to_type(targetSelectionRange, Range)
|
||||||
|
|
||||||
|
|
||||||
|
class Diagnostic(object):
|
||||||
|
def __init__(self, range, severity, code, source, message, relatedInformation):
|
||||||
|
"""
|
||||||
|
Constructs a new Diagnostic instance.
|
||||||
|
:param Range range: The range at which the message applies.Resource file.
|
||||||
|
:param int severity: The diagnostic's severity. Can be omitted. If omitted it is up to the
|
||||||
|
client to interpret diagnostics as error, warning, info or hint.
|
||||||
|
:param str code: The diagnostic's code, which might appear in the user interface.
|
||||||
|
:param str source: A human-readable string describing the source of this
|
||||||
|
diagnostic, e.g. 'typescript' or 'super lint'.
|
||||||
|
:param str message: The diagnostic's message.
|
||||||
|
:param list relatedInformation: An array of related diagnostic information, e.g. when symbol-names within
|
||||||
|
a scope collide all definitions can be marked via this property.
|
||||||
|
"""
|
||||||
|
self.range = range
|
||||||
|
self.severity = severity
|
||||||
|
self.code = code
|
||||||
|
self.source = source
|
||||||
|
self.message = message
|
||||||
|
self.relatedInformation = relatedInformation
|
||||||
|
|
||||||
|
|
||||||
|
class DiagnosticSeverity(object):
|
||||||
|
Error = 1
|
||||||
|
Warning = 2 # TODO: warning is known in python
|
||||||
|
Information = 3
|
||||||
|
Hint = 4
|
||||||
|
|
||||||
|
|
||||||
|
class DiagnosticRelatedInformation(object):
|
||||||
|
def __init__(self, location, message):
|
||||||
|
"""
|
||||||
|
Constructs a new Diagnostic instance.
|
||||||
|
:param Location location: The location of this related diagnostic information.
|
||||||
|
:param str message: The message of this related diagnostic information.
|
||||||
|
"""
|
||||||
|
self.location = location
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
|
||||||
|
class Command(object):
|
||||||
|
def __init__(self, title, command, arguments):
|
||||||
|
"""
|
||||||
|
Constructs a new Diagnostic instance.
|
||||||
|
:param str title: Title of the command, like `save`.
|
||||||
|
:param str command: The identifier of the actual command handler.
|
||||||
|
:param list argusments: Arguments that the command handler should be invoked with.
|
||||||
|
"""
|
||||||
|
self.title = title
|
||||||
|
self.command = command
|
||||||
|
self.arguments = arguments
|
||||||
|
|
||||||
|
|
||||||
|
class TextDocumentItem(object):
|
||||||
|
"""
|
||||||
|
An item to transfer a text document from the client to the server.
|
||||||
|
"""
|
||||||
|
def __init__(self, uri, languageId, version, text):
|
||||||
|
"""
|
||||||
|
Constructs a new Diagnostic instance.
|
||||||
|
|
||||||
|
:param DocumentUri uri: Title of the command, like `save`.
|
||||||
|
:param str languageId: The identifier of the actual command handler.
|
||||||
|
:param int version: Arguments that the command handler should be invoked with.
|
||||||
|
:param str text: Arguments that the command handler should be invoked with.
|
||||||
|
"""
|
||||||
|
self.uri = uri
|
||||||
|
self.languageId = languageId
|
||||||
|
self.version = version
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
|
||||||
|
class TextDocumentIdentifier(object):
|
||||||
|
"""
|
||||||
|
Text documents are identified using a URI. On the protocol level, URIs are passed as strings.
|
||||||
|
"""
|
||||||
|
def __init__(self, uri):
|
||||||
|
"""
|
||||||
|
Constructs a new TextDocumentIdentifier instance.
|
||||||
|
|
||||||
|
:param DocumentUri uri: The text document's URI.
|
||||||
|
"""
|
||||||
|
self.uri = uri
|
||||||
|
|
||||||
|
|
||||||
|
class VersionedTextDocumentIdentifier(TextDocumentIdentifier):
|
||||||
|
"""
|
||||||
|
An identifier to denote a specific version of a text document.
|
||||||
|
"""
|
||||||
|
def __init__(self, uri, version):
|
||||||
|
"""
|
||||||
|
Constructs a new TextDocumentIdentifier instance.
|
||||||
|
|
||||||
|
:param DocumentUri uri: The text document's URI.
|
||||||
|
:param int version: The version number of this document. If a versioned
|
||||||
|
text document identifier is sent from the server to the client and
|
||||||
|
the file is not open in the editor (the server has not received an
|
||||||
|
open notification before) the server can send `null` to indicate
|
||||||
|
that the version is known and the content on disk is the truth (as
|
||||||
|
speced with document content ownership).
|
||||||
|
The version number of a document will increase after each change, including
|
||||||
|
undo/redo. The number doesn't need to be consecutive.
|
||||||
|
"""
|
||||||
|
super(VersionedTextDocumentIdentifier, self).__init__(uri)
|
||||||
|
self.version = version
|
||||||
|
|
||||||
|
|
||||||
|
class TextDocumentContentChangeEvent(object):
|
||||||
|
"""
|
||||||
|
An event describing a change to a text document. If range and rangeLength are omitted
|
||||||
|
the new text is considered to be the full content of the document.
|
||||||
|
"""
|
||||||
|
def __init__(self, range, rangeLength, text):
|
||||||
|
"""
|
||||||
|
Constructs a new TextDocumentContentChangeEvent instance.
|
||||||
|
|
||||||
|
:param Range range: The range of the document that changed.
|
||||||
|
:param int rangeLength: The length of the range that got replaced.
|
||||||
|
:param str text: The new text of the range/document.
|
||||||
|
"""
|
||||||
|
self.range = range
|
||||||
|
self.rangeLength = rangeLength
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
|
||||||
|
class TextDocumentPositionParams(object):
|
||||||
|
"""
|
||||||
|
A parameter literal used in requests to pass a text document and a position inside that document.
|
||||||
|
"""
|
||||||
|
def __init__(self, textDocument, position):
|
||||||
|
"""
|
||||||
|
Constructs a new TextDocumentPositionParams instance.
|
||||||
|
|
||||||
|
:param TextDocumentIdentifier textDocument: The text document.
|
||||||
|
:param Position position: The position inside the text document.
|
||||||
|
"""
|
||||||
|
self.textDocument = textDocument
|
||||||
|
self.position = position
|
||||||
|
|
||||||
|
|
||||||
|
class LANGUAGE_IDENTIFIER(object):
|
||||||
|
BAT = "bat"
|
||||||
|
BIBTEX = "bibtex"
|
||||||
|
CLOJURE = "clojure"
|
||||||
|
COFFESCRIPT = "coffeescript"
|
||||||
|
C = "c"
|
||||||
|
CPP = "cpp"
|
||||||
|
CSHARP = "csharp"
|
||||||
|
CSS = "css"
|
||||||
|
DIFF = "diff"
|
||||||
|
DOCKERFILE = "dockerfile"
|
||||||
|
FSHARP = "fsharp"
|
||||||
|
GIT_COMMIT = "git-commit"
|
||||||
|
GIT_REBASE = "git-rebase"
|
||||||
|
GO = "go"
|
||||||
|
GROOVY = "groovy"
|
||||||
|
HANDLEBARS = "handlebars"
|
||||||
|
HTML = "html"
|
||||||
|
INI = "ini"
|
||||||
|
JAVA = "java"
|
||||||
|
JAVASCRIPT = "javascript"
|
||||||
|
JSON = "json"
|
||||||
|
LATEX = "latex"
|
||||||
|
LESS = "less"
|
||||||
|
LUA = "lua"
|
||||||
|
MAKEFILE = "makefile"
|
||||||
|
MARKDOWN = "markdown"
|
||||||
|
OBJECTIVE_C = "objective-c"
|
||||||
|
OBJECTIVE_CPP = "objective-cpp"
|
||||||
|
Perl = "perl"
|
||||||
|
PHP = "php"
|
||||||
|
POWERSHELL = "powershell"
|
||||||
|
PUG = "jade"
|
||||||
|
PYTHON = "python"
|
||||||
|
R = "r"
|
||||||
|
RAZOR = "razor"
|
||||||
|
RUBY = "ruby"
|
||||||
|
RUST = "rust"
|
||||||
|
SASS = "sass"
|
||||||
|
SCSS = "scss"
|
||||||
|
ShaderLab = "shaderlab"
|
||||||
|
SHELL_SCRIPT = "shellscript"
|
||||||
|
SQL = "sql"
|
||||||
|
SWIFT = "swift"
|
||||||
|
TYPE_SCRIPT = "typescript"
|
||||||
|
TEX = "tex"
|
||||||
|
VB = "vb"
|
||||||
|
XML = "xml"
|
||||||
|
XSL = "xsl"
|
||||||
|
YAML = "yaml"
|
||||||
|
|
||||||
|
|
||||||
|
class SymbolKind(enum.Enum):
|
||||||
|
File = 1
|
||||||
|
Module = 2
|
||||||
|
Namespace = 3
|
||||||
|
Package = 4
|
||||||
|
Class = 5
|
||||||
|
Method = 6
|
||||||
|
Property = 7
|
||||||
|
Field = 8
|
||||||
|
Constructor = 9
|
||||||
|
Enum = 10
|
||||||
|
Interface = 11
|
||||||
|
Function = 12
|
||||||
|
Variable = 13
|
||||||
|
Constant = 14
|
||||||
|
String = 15
|
||||||
|
Number = 16
|
||||||
|
Boolean = 17
|
||||||
|
Array = 18
|
||||||
|
Object = 19
|
||||||
|
Key = 20
|
||||||
|
Null = 21
|
||||||
|
EnumMember = 22
|
||||||
|
Struct = 23
|
||||||
|
Event = 24
|
||||||
|
Operator = 25
|
||||||
|
TypeParameter = 26
|
||||||
|
|
||||||
|
|
||||||
|
class SymbolInformation(object):
|
||||||
|
"""
|
||||||
|
Represents information about programming constructs like variables, classes, interfaces etc.
|
||||||
|
"""
|
||||||
|
def __init__(self, name, kind, location, containerName = None, deprecated = False):
|
||||||
|
"""
|
||||||
|
Constructs a new SymbolInformation instance.
|
||||||
|
|
||||||
|
:param str name: The name of this symbol.
|
||||||
|
:param int kind: The kind of this symbol.
|
||||||
|
:param bool Location: The location of this symbol. The location's range is used by a tool
|
||||||
|
to reveal the location in the editor. If the symbol is selected in the
|
||||||
|
tool the range's start information is used to position the cursor. So
|
||||||
|
the range usually spans more then the actual symbol's name and does
|
||||||
|
normally include things like visibility modifiers.
|
||||||
|
|
||||||
|
The range doesn't have to denote a node range in the sense of a abstract
|
||||||
|
syntax tree. It can therefore not be used to re-construct a hierarchy of
|
||||||
|
the symbols.
|
||||||
|
:param str containerName: The name of the symbol containing this symbol. This information is for
|
||||||
|
user interface purposes (e.g. to render a qualifier in the user interface
|
||||||
|
if necessary). It can't be used to re-infer a hierarchy for the document
|
||||||
|
symbols.
|
||||||
|
:param bool deprecated: Indicates if this symbol is deprecated.
|
||||||
|
"""
|
||||||
|
self.name = name
|
||||||
|
self.kind = SymbolKind(kind)
|
||||||
|
self.deprecated = deprecated
|
||||||
|
self.location = to_type(location, Location)
|
||||||
|
self.containerName = containerName
|
||||||
|
|
||||||
|
|
||||||
|
class ParameterInformation(object):
|
||||||
|
"""
|
||||||
|
Represents a parameter of a callable-signature. A parameter can
|
||||||
|
have a label and a doc-comment.
|
||||||
|
"""
|
||||||
|
def __init__(self, label, documentation = ""):
|
||||||
|
"""
|
||||||
|
Constructs a new ParameterInformation instance.
|
||||||
|
|
||||||
|
:param str label: The label of this parameter. Will be shown in the UI.
|
||||||
|
:param str documentation: The human-readable doc-comment of this parameter. Will be shown in the UI but can be omitted.
|
||||||
|
"""
|
||||||
|
self.label = label
|
||||||
|
self.documentation = documentation
|
||||||
|
|
||||||
|
|
||||||
|
class SignatureInformation(object):
|
||||||
|
"""
|
||||||
|
Represents the signature of something callable. A signature
|
||||||
|
can have a label, like a function-name, a doc-comment, and
|
||||||
|
a set of parameters.
|
||||||
|
"""
|
||||||
|
def __init__(self, label, documentation = "", parameters = []):
|
||||||
|
"""
|
||||||
|
Constructs a new SignatureInformation instance.
|
||||||
|
|
||||||
|
:param str label: The label of this signature. Will be shown in the UI.
|
||||||
|
:param str documentation: The human-readable doc-comment of this signature. Will be shown in the UI but can be omitted.
|
||||||
|
:param ParameterInformation[] parameters: The parameters of this signature.
|
||||||
|
"""
|
||||||
|
self.label = label
|
||||||
|
self.documentation = documentation
|
||||||
|
self.parameters = [to_type(parameter, ParameterInformation) for parameter in parameters]
|
||||||
|
|
||||||
|
|
||||||
|
class SignatureHelp(object):
|
||||||
|
"""
|
||||||
|
Signature help represents the signature of something
|
||||||
|
callable. There can be multiple signature but only one
|
||||||
|
active and only one active parameter.
|
||||||
|
"""
|
||||||
|
def __init__(self, signatures, activeSignature = 0, activeParameter = 0):
|
||||||
|
"""
|
||||||
|
Constructs a new SignatureHelp instance.
|
||||||
|
|
||||||
|
:param SignatureInformation[] signatures: One or more signatures.
|
||||||
|
:param int activeSignature:
|
||||||
|
:param int activeParameter:
|
||||||
|
"""
|
||||||
|
self.signatures = [to_type(signature, SignatureInformation) for signature in signatures]
|
||||||
|
self.activeSignature = activeSignature
|
||||||
|
self.activeParameter = activeParameter
|
||||||
|
|
||||||
|
|
||||||
|
class CompletionTriggerKind(object):
|
||||||
|
Invoked = 1
|
||||||
|
TriggerCharacter = 2
|
||||||
|
TriggerForIncompleteCompletions = 3
|
||||||
|
|
||||||
|
|
||||||
|
class CompletionContext(object):
|
||||||
|
"""
|
||||||
|
Contains additional information about the context in which a completion request is triggered.
|
||||||
|
"""
|
||||||
|
def __init__(self, triggerKind, triggerCharacter = None):
|
||||||
|
"""
|
||||||
|
Constructs a new CompletionContext instance.
|
||||||
|
|
||||||
|
:param CompletionTriggerKind triggerKind: How the completion was triggered.
|
||||||
|
:param str triggerCharacter: The trigger character (a single character) that has trigger code complete.
|
||||||
|
Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter`
|
||||||
|
"""
|
||||||
|
self.triggerKind = triggerKind
|
||||||
|
if triggerCharacter:
|
||||||
|
self.triggerCharacter = triggerCharacter
|
||||||
|
|
||||||
|
|
||||||
|
class TextEdit(object):
|
||||||
|
"""
|
||||||
|
A textual edit applicable to a text document.
|
||||||
|
"""
|
||||||
|
def __init__(self, range, newText):
|
||||||
|
"""
|
||||||
|
:param Range range: The range of the text document to be manipulated. To insert
|
||||||
|
text into a document create a range where start === end.
|
||||||
|
:param str newText: The string to be inserted. For delete operations use an empty string.
|
||||||
|
"""
|
||||||
|
self.range = range
|
||||||
|
self.newText = newText
|
||||||
|
|
||||||
|
|
||||||
|
class InsertTextFormat(object):
|
||||||
|
PlainText = 1
|
||||||
|
Snippet = 2
|
||||||
|
|
||||||
|
|
||||||
|
class CompletionItem(object):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
def __init__(self, label, \
|
||||||
|
kind = None, \
|
||||||
|
detail = None, \
|
||||||
|
documentation = None, \
|
||||||
|
deprecated = None, \
|
||||||
|
preselect = None, \
|
||||||
|
sortText = None, \
|
||||||
|
filterText = None, \
|
||||||
|
insertText = None, \
|
||||||
|
insertTextFormat = None, \
|
||||||
|
textEdit = None, \
|
||||||
|
additionalTextEdits = None, \
|
||||||
|
commitCharacters = None, \
|
||||||
|
command = None, \
|
||||||
|
data = None, \
|
||||||
|
score = 0.0
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
:param str label: The label of this completion item. By default also the text that is inserted when selecting
|
||||||
|
this completion.
|
||||||
|
:param int kind: The kind of this completion item. Based of the kind an icon is chosen by the editor.
|
||||||
|
:param str detail: A human-readable string with additional information about this item, like type or symbol information.
|
||||||
|
:param tr ocumentation: A human-readable string that represents a doc-comment.
|
||||||
|
:param bool deprecated: Indicates if this item is deprecated.
|
||||||
|
:param bool preselect: Select this item when showing. Note: that only one completion item can be selected and that the
|
||||||
|
tool / client decides which item that is. The rule is that the first item of those that match best is selected.
|
||||||
|
:param str sortText: A string that should be used when comparing this item with other items. When `falsy` the label is used.
|
||||||
|
:param str filterText: A string that should be used when filtering a set of completion items. When `falsy` the label is used.
|
||||||
|
:param str insertText: A string that should be inserted into a document when selecting this completion. When `falsy` the label is used.
|
||||||
|
The `insertText` is subject to interpretation by the client side. Some tools might not take the string literally. For example
|
||||||
|
VS Code when code complete is requested in this example `con<cursor position>` and a completion item with an `insertText` of `console` is provided it
|
||||||
|
will only insert `sole`. Therefore it is recommended to use `textEdit` instead since it avoids additional client side interpretation.
|
||||||
|
@deprecated Use textEdit instead.
|
||||||
|
:param InsertTextFormat insertTextFormat: The format of the insert text. The format applies to both the `insertText` property
|
||||||
|
and the `newText` property of a provided `textEdit`.
|
||||||
|
:param TextEdit textEdit: An edit which is applied to a document when selecting this completion. When an edit is provided the value of `insertText` is ignored.
|
||||||
|
Note:* The range of the edit must be a single line range and it must contain the position at which completion
|
||||||
|
has been requested.
|
||||||
|
:param TextEdit additionalTextEdits: An optional array of additional text edits that are applied when selecting this completion.
|
||||||
|
Edits must not overlap (including the same insert position) with the main edit nor with themselves.
|
||||||
|
Additional text edits should be used to change text unrelated to the current cursor position
|
||||||
|
(for example adding an import statement at the top of the file if the completion item will
|
||||||
|
insert an unqualified type).
|
||||||
|
:param str commitCharacters: An optional set of characters that when pressed while this completion is active will accept it first and
|
||||||
|
then type that character. *Note* that all commit characters should have `length=1` and that superfluous
|
||||||
|
characters will be ignored.
|
||||||
|
:param Command command: An optional command that is executed *after* inserting this completion. Note: that
|
||||||
|
additional modifications to the current document should be described with the additionalTextEdits-property.
|
||||||
|
:param data: An data entry field that is preserved on a completion item between a completion and a completion resolve request.
|
||||||
|
:param float score: Score of the code completion item.
|
||||||
|
"""
|
||||||
|
self.label = label
|
||||||
|
self.kind = kind
|
||||||
|
self.detail = detail
|
||||||
|
self.documentation = documentation
|
||||||
|
self.deprecated = deprecated
|
||||||
|
self.preselect = preselect
|
||||||
|
self.sortText = sortText
|
||||||
|
self.filterText = filterText
|
||||||
|
self.insertText = insertText
|
||||||
|
self.insertTextFormat = insertTextFormat
|
||||||
|
self.textEdit = textEdit
|
||||||
|
self.additionalTextEdits = additionalTextEdits
|
||||||
|
self.commitCharacters = commitCharacters
|
||||||
|
self.command = command
|
||||||
|
self.data = data
|
||||||
|
self.score = score
|
||||||
|
|
||||||
|
|
||||||
|
class CompletionItemKind(enum.Enum):
|
||||||
|
Text = 1
|
||||||
|
Method = 2
|
||||||
|
Function = 3
|
||||||
|
Constructor = 4
|
||||||
|
Field = 5
|
||||||
|
Variable = 6
|
||||||
|
Class = 7
|
||||||
|
Interface = 8
|
||||||
|
Module = 9
|
||||||
|
Property = 10
|
||||||
|
Unit = 11
|
||||||
|
Value = 12
|
||||||
|
Enum = 13
|
||||||
|
Keyword = 14
|
||||||
|
Snippet = 15
|
||||||
|
Color = 16
|
||||||
|
File = 17
|
||||||
|
Reference = 18
|
||||||
|
Folder = 19
|
||||||
|
EnumMember = 20
|
||||||
|
Constant = 21
|
||||||
|
Struct = 22
|
||||||
|
Event = 23
|
||||||
|
Operator = 24
|
||||||
|
TypeParameter = 25
|
||||||
|
|
||||||
|
|
||||||
|
class CompletionList(object):
|
||||||
|
"""
|
||||||
|
Represents a collection of [completion items](#CompletionItem) to be preselect in the editor.
|
||||||
|
"""
|
||||||
|
def __init__(self, isIncomplete, items):
|
||||||
|
"""
|
||||||
|
Constructs a new CompletionContext instance.
|
||||||
|
|
||||||
|
:param bool isIncomplete: This list it not complete. Further typing should result in recomputing this list.
|
||||||
|
:param CompletionItem items: The completion items.
|
||||||
|
"""
|
||||||
|
self.isIncomplete = isIncomplete
|
||||||
|
self.items = [to_type(i, CompletionItem) for i in items]
|
||||||
|
|
||||||
|
class ErrorCodes(enum.Enum):
|
||||||
|
# Defined by JSON RPC
|
||||||
|
ParseError = -32700
|
||||||
|
InvalidRequest = -32600
|
||||||
|
MethodNotFound = -32601
|
||||||
|
InvalidParams = -32602
|
||||||
|
InternalError = -32603
|
||||||
|
serverErrorStart = -32099
|
||||||
|
serverErrorEnd = -32000
|
||||||
|
ServerNotInitialized = -32002
|
||||||
|
UnknownErrorCode = -32001
|
||||||
|
|
||||||
|
# Defined by the protocol.
|
||||||
|
RequestCancelled = -32800
|
||||||
|
ContentModified = -32801
|
||||||
|
|
||||||
|
class ResponseError(Exception):
|
||||||
|
def __init__(self, code, message, data = None):
|
||||||
|
self.code = code
|
||||||
|
self.message = message
|
||||||
|
if data:
|
||||||
|
self.data = data
|
Loading…
Reference in New Issue
Block a user