Compare commits

...

9 Commits

6 changed files with 227 additions and 114 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.egg-info
__pycache__
*.pyc

58
java
View File

@ -1,58 +0,0 @@
import os
import sys
FILE_URI = "file://"
def locate_product_directory (jdt_ls_path):
return os.path.join(jdt_ls_path, "org.eclipse.jdt.ls.product/target/repository")
def locate_launcher_jar (jdt_ls_path):
jdt_ls_product_directory = locate_product_directory(jdt_ls_path)
plugins_directory = os.path.join(jdt_ls_product_directory, "plugins")
return os.path.join(plugins_directory, [jar for jar in os.listdir(plugins_directory) if jar.startswith("org.eclipse.equinox.launcher_")][0])
def locate_launcher_configuration (jdt_ls_path):
return os.path.join(locate_product_directory(jdt_ls_path), "config_linux")
# TODO
def locate_lombok ():
return os.path.join(os.environ['HOME'], ".m2/repository/org/projectlombok/lombok/1.18.16/lombok-1.18.16.jar")
class Java(LspServer):
def __init__ (self, arguments):
self.path = arguments[0]
command = ["java"]
if "--enable-lombok" in arguments:
arguments.remove("--enable-lombok")
command.append(f"-javaagent:{locate_lombok()}")
command = command + [
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044",
"-Declipse.application=org.eclipse.jdt.ls.core.id1",
"-Dosgi.bundles.defaultStartLevel=4",
"-Declipse.product=org.eclipse.jdt.ls.core.product",
"-noverify",
"-Xmx1G",
"-jar", locate_launcher_jar(self.path),
"-configuration", locate_launcher_configuration(self.path),
"-data", os.path.join(self.path, "data"),
"--add-modules=ALL-SYSTEM",
"--add-opens", "java.base/java.util=ALL-UNNAMED",
"--add-opens", "java.base/java.lang=ALL-UNNAMED"
] + arguments[1:]
super().__init__(command)
@command("java.apply.workspaceEdit")
def workspaceEdit (self, arguments):
for argument in arguments:
self.send_to_client({
"id": -1,
"method": "workspace/applyEdit",
"params": {
"edit": {
"changes": argument['changes']
}
}
})

38
lsp_proxy/__init__.py Executable file
View File

@ -0,0 +1,38 @@
import os
import sys
import inspect
from .lsp import LspServer, handler, method, command
from .java import Java
def find_server_classes (values=None):
values = values or globals()
return dict([(item[0].lower(), item[1]) for item in values.items() if inspect.isclass(item[1]) and LspServer in inspect.getmro(item[1])])
def select_server_class (argument):
classes = find_server_classes()
if argument.lower() in classes:
return classes[argument]
if os.path.isfile(argument):
script_locals = {
"LspServer": LspServer,
"handler": handler,
"method": method,
"command": command
}
with open(argument) as server_script:
exec(server_script.read(), script_locals)
return [item[1] for item in find_server_classes(script_locals).items() if not item[1] == LspServer][0]
def main ():
server_class = select_server_class(sys.argv[1])
server = server_class(sys.argv[2:])
try:
server.start()
except Exception as e:
server.close()
raise e

128
lsp_proxy/java.py Normal file
View File

@ -0,0 +1,128 @@
import os
import sys
import tarfile
from urllib import request
from .lsp import LspServer, command, get_data_home
FILE_URI = "file://"
VERSION = "1.3.0"
DOWNLOADS_URI = "http://download.eclipse.org/jdtls/milestones/{version}/{file}"
LATEST = "latest.txt"
def locate_product_directory (jdt_ls_path):
if os.path.isdir(os.path.join(jdt_ls_path, "plugins")):
return jdt_ls_path
return os.path.join(jdt_ls_path, "org.eclipse.jdt.ls.product/target/repository")
def locate_launcher_jar (jdt_ls_path):
plugins_directory = os.path.join(jdt_ls_path, "plugins")
return os.path.join(plugins_directory, [jar for jar in os.listdir(plugins_directory) if jar.startswith("org.eclipse.equinox.launcher_")][0])
def locate_launcher_configuration (jdt_ls_path):
return os.path.join(jdt_ls_path, "config_linux")
def log (message, *args, **kwargs):
print(message.format(*args, **kwargs), file=sys.stderr)
def download_jdt_ls (version, destination):
if not os.path.isdir(destination):
os.makedirs(destination)
current_version = None
current_version_file = os.path.join(destination, LATEST)
if os.path.isfile(current_version_file):
with open(current_version_file) as f:
current_version = f.read().strip()
log("Currently downloaded version is {version}:", version=current_version)
latest_version = None
with request.urlopen(DOWNLOADS_URI.format(version=version, file=LATEST)) as f:
latest_version = f.read().decode().strip()
log("Latest available version is {version}:", version=latest_version)
if not latest_version == current_version:
latest_uri = DOWNLOADS_URI.format(version=version, file=latest_version)
log("Downloading latest version from: {uri}:", uri=latest_uri)
with request.urlopen(latest_uri) as f:
with tarfile.open(fileobj=f, mode="r|gz") as tar:
tar.extractall(destination)
print(tar)
with open(current_version_file, "w") as cvf:
cvf.write(latest_version)
return destination
# TODO
def locate_lombok ():
return os.path.join(os.environ['HOME'], ".m2/repository/org/projectlombok/lombok/1.18.16/lombok-1.18.16.jar")
def get_argument (arguments, arg_name):
if not arg_name in args:
return
value = arguments.pop(arguments.index(arg_name) + 1)
arguments.remove(arg_name)
return value
class Java(LspServer):
def __init__ (self, arguments):
self.path = arguments[0] if arguments else None
if self.path is None or not os.path.exists(self.path):
version = VERSION
if "--version" in arguments:
version = get_argument(arguments, "--version")
self.path = download_jdt_ls(version, os.path.join(get_data_home(), "java", "jdt.ls", version))
else:
self.path = locate_product_directory(self.path)
command = ["java"]
if "--enable-lombok" in arguments:
arguments.remove("--enable-lombok")
command.append(f"-javaagent:{locate_lombok()}")
data_directory = None
if "--data" in arguments:
data_directory = get_argument(arguments, "--data")
else:
data_directory = os.path.join(get_data_home(), "java", "data")
if "--enable-debugger" in arguments:
arguments.remove("--enable-debugger")
command.append("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044")
command = command + [
"-Declipse.application=org.eclipse.jdt.ls.core.id1",
"-Dosgi.bundles.defaultStartLevel=4",
"-Declipse.product=org.eclipse.jdt.ls.core.product",
"-noverify",
"-Xmx1G",
"-jar", locate_launcher_jar(self.path),
"-configuration", locate_launcher_configuration(self.path),
"-data", data_directory,
"--add-modules=ALL-SYSTEM",
"--add-opens", "java.base/java.util=ALL-UNNAMED",
"--add-opens", "java.base/java.lang=ALL-UNNAMED"
] + arguments[1:]
super().__init__(command)
@command("java.apply.workspaceEdit")
def workspaceEdit (self, arguments):
for argument in arguments:
self.send_to_client({
"id": -1,
"method": "workspace/applyEdit",
"params": {
"edit": {
"changes": argument['changes']
}
}
})

89
lsp-proxy → lsp_proxy/lsp.py Executable file → Normal file
View File

@ -1,5 +1,3 @@
#!/usr/bin/env python3
import os
import sys
import inspect
@ -8,36 +6,44 @@ import json
from subprocess import run, Popen, PIPE
from threading import Thread
from xdg import xdg_data_home
CONTENT_LENGTH = "Content-Length: "
def process_messages (source, sink, handlers, log):
print("start message handler", file=log)
def log (message, *args, **kwargs):
print(message.format(*args, **kwargs), file=sys.stderr)
def process_messages (label, source, sink, handlers):
log(f"[{label}] start process_messages")
while True:
line = source.readline()
if not line:
break
line = line.decode()
if line.startswith(CONTENT_LENGTH):
content_length = int(line[len(CONTENT_LENGTH):].strip())
print(f">> ce: [{content_length}]", file=log)
try:
line = line.decode()
if line.startswith(CONTENT_LENGTH):
content_length = int(line[len(CONTENT_LENGTH):].strip())
log(f">> [{label}] ce: [{content_length}]")
source.readline()
source.readline()
payload = source.read(content_length).decode()
print(f">> payload: [{payload}]", file=log)
payload = source.read(content_length).decode()
log(">> [{label}] payload: [{payload}]", label=label, payload=payload)
if handlers:
payload_parsed = json.loads(payload)
for handler in handlers:
try:
handler(payload_parsed)
except Exception as e:
print(f"Error from handler: {e}", file=log)
if handlers:
payload_parsed = json.loads(payload)
for handler in handlers:
try:
handler(payload_parsed)
except Exception as e:
log(f"Error from handler: {e}")
transmit_payload(payload, sink)
transmit_payload(payload, sink)
except Exception as e:
log(f"[{label}] Error decoding input: {e}")
print("stop message handler", file=log)
log(f"[{label}] exit process_messages")
def transmit_payload (payload, sink):
if isinstance(payload, dict):
@ -77,6 +83,12 @@ def command (name):
return method("workspace/executeCommand")(handle_command)
return command_handler
def get_data_home ():
data_directory = os.path.join(xdg_data_home(), "lsp-proxy")
if not os.path.exists(data_directory):
os.makedirs(data_directory)
return data_directory
class LspServer:
def __init__ (self, command):
self.command = command
@ -85,13 +97,16 @@ class LspServer:
def start (self):
handlers = self.handlers + handler.get_handlers(self)
log("starting lsp process: {}", " ".join(self.command))
with Popen(self.command, stdin=PIPE, stdout=PIPE) as process:
self.process = process
self.process_reader = Thread(target=process_messages, args=[process.stdout, sys.stdout.buffer, handlers, sys.stderr])
self.process_reader = Thread(target=process_messages, args=["process.stdout -> sys.stdout reader", process.stdout, sys.stdout.buffer, handlers])
self.process_reader.start()
process_messages(sys.stdin.buffer, process.stdin, handlers, sys.stderr)
process_messages("sys.stdin -> process.stdin", sys.stdin.buffer, process.stdin, handlers)
log("lsp process ended")
def start2 (self):
run(self.command)
@ -106,33 +121,3 @@ class LspServer:
if self.process:
self.process.terminate()
def find_server_classes (values=None):
values = values or globals()
return dict([item for item in values.items() if inspect.isclass(item[1]) and LspServer in inspect.getmro(item[1])])
def select_server_class (argument):
classes = find_server_classes()
if argument in classes:
return classes[argument]
if os.path.isfile(argument):
script_locals = {
"LspServer": LspServer,
"handler": handler,
"method": method,
"command": command
}
with open(argument) as server_script:
exec(server_script.read(), script_locals)
return [item[1] for item in find_server_classes(script_locals).items() if not item[1] == LspServer][0]
server_class = select_server_class(sys.argv[1])
server = server_class(sys.argv[2:])
try:
server.start()
except Exception as e:
server.close()
raise e

17
setup.py Normal file
View File

@ -0,0 +1,17 @@
#!/usr/bin/env python
from distutils.core import setup
setup(
name='lsp-proxy',
version='0.0.1',
description='lsp-proxy is a proxy for language servers.',
author='Adrian Malacoda',
packages=['lsp_proxy'],
install_requires=['xdg'],
entry_points={
'console_scripts': [
'lsp-proxy = lsp_proxy:main'
]
}
)