Compare commits

...

30 Commits

Author SHA1 Message Date
0789cec010 extended message types; added traceback logging 2024-10-02 22:13:52 -05:00
46d358aab7 fixed goto/definition with full text transfer; cleanup 2024-09-20 01:54:29 -05:00
4d65197845 Moved lsp manager to lsp client 2024-09-16 23:30:22 -05:00
3617e79150 idle_adding call of event 2024-09-16 02:08:15 -05:00
769eb4adb2 Removing no longer needed logging in plugin 2024-09-15 01:53:27 -05:00
693f569d4f lsp manager plugin updates 2024-09-15 00:26:39 -05:00
b31278e114 Reworked lsp manager plugin; removed websockets library 2024-09-14 01:21:27 -05:00
3edb89ad5c Adding alternate LSP handler 2024-09-12 00:25:40 -05:00
183e7358ce added lua lsp generic config 2024-06-22 20:38:37 -05:00
e3ea9492a3 Added monaco to play around with it 2024-04-19 23:18:38 -05:00
790313b63f Attempted message.backend stubbing when not in webkit; setting additions and changes, LSP efforts 2024-04-17 00:16:12 -05:00
238a05591d style cleanup and alignment; keybinding additions; lsp config expansion 2024-04-09 00:19:21 -05:00
cb4b7faaaa Aligning various popup elements 2024-04-07 00:37:11 -05:00
84b96ace5e Wiring Search/Replace UI 2024-04-06 23:50:50 -05:00
8b555991e4 Creating Search/Replace UI 2024-04-06 04:35:06 -05:00
99e9e9d3ef Fixed LSP Setting window loading JSON to HTML 2024-04-05 22:44:24 -05:00
4fe2c7ed8f Updated LSP inferastructure 2024-04-04 23:29:06 -05:00
b76b7ab780 WIP LSP config ui 2024-03-29 00:22:25 -05:00
14100b09e3 Effort on LSP settings UI 2024-03-28 01:06:53 -05:00
c268a7ef1a Capped const variables; loading config and socket from config 2024-03-26 22:19:01 -05:00
7bec522a31 Added lsp-manager js file; updated lsp config 2024-03-25 21:45:44 -05:00
02ce50cb76 Loadsing LSP from backend blob url generation 2024-03-24 23:16:49 -05:00
32a79032cc Updated method signature; added sleep method; moved to use backend loaded js files on LSP 2024-03-24 17:48:27 -05:00
be5e159fd4 Testing python LSP with ace-linters 2024-03-22 20:31:40 -05:00
b0c519dce5 Re-added context_path folder for Webkit branch needs 2024-03-22 19:53:20 -05:00
459837e85b Adding back user_config 2024-03-19 22:20:35 -05:00
e04e3845c2 moving plugins to sub folders 2024-03-19 19:55:45 -05:00
b72d5b8e40 Moving plugins into branch 2024-03-19 19:43:27 -05:00
ce8361238f Make master branch jus ino about the relese branches 2024-03-01 19:21:30 -06:00
30cf03b76f Merge pull request 'develop' (#1) from develop into master
Reviewed-on: #1
2024-01-12 01:55:22 +00:00
2535 changed files with 708674 additions and 5873 deletions

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 KiB

BIN
images/pic6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 KiB

View File

@ -0,0 +1,2 @@
### Note
TBD

View 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) )

View File

@ -0,0 +1,7 @@
{
"lsp_manager_start_command": ["python", "/opt/lsp-manager.zip"],
"websocket": {
"host": "localhost",
"port": 8765
}
}

View File

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

View 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"
]
}
}
}

View 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) )

View File

@ -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}'")

View File

@ -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
}
}

View File

@ -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)
)

View File

@ -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"
}
}
}

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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]

View File

@ -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