Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support client config with tcp_port but without command #2300

Merged
merged 13 commits into from
Aug 15, 2023
2 changes: 2 additions & 0 deletions docs/src/client_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ The vast majority of language servers can communicate over stdio. To use stdio,

Some language servers can also act as a TCP server accepting incoming TCP connections. So: the language server subprocess is started by this package, and the subprocess will then open a TCP listener port. The editor can then connect as a client and initiate the communication. To use this mode, set `tcp_port` to a positive number designating the port to connect to on `localhost`.

Optionally in this case, you can omit the `command` setting if you don't want Sublime LSP to manage the language server process and you'll take care of it yourself.

### TCP - localhost - editor acts as a TCP server

Some _LSP servers_ instead expect the _LSP client_ to act as a _TCP server_. The _LSP server_ will then connect as a _TCP client_, after which the _LSP client_ is expected to initiate the communication. To use this mode, set `tcp_port` to a negative number designating the port to bind to for accepting new TCP connections.
Expand Down
2 changes: 2 additions & 0 deletions docs/src/language_servers.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ Follow installation instructions on [LSP-gopls](https://github.com/sublimelsp/LS
}
```

If you encounter high cpu load or any other issues you can try omitting the [command] line, and ensure the godot editor is running while you work in sublime.

## GraphQL

Follow installation instructions on [LSP-graphql](https://github.com/sublimelsp/LSP-graphql).
Expand Down
63 changes: 33 additions & 30 deletions plugin/core/transports.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ def _decode(message: bytes) -> Dict[str, Any]:

class ProcessTransport(Transport[T]):

def __init__(self, name: str, process: subprocess.Popen, socket: Optional[socket.socket], reader: IO[bytes],
writer: IO[bytes], stderr: Optional[IO[bytes]], processor: AbstractProcessor[T],
callback_object: TransportCallbacks[T]) -> None:
def __init__(self, name: str, process: Optional[subprocess.Popen], socket: Optional[socket.socket],
reader: IO[bytes], writer: IO[bytes], stderr: Optional[IO[bytes]],
processor: AbstractProcessor[T], callback_object: TransportCallbacks[T]) -> None:
self._closed = False
self._process = process
self._socket = socket
Expand All @@ -106,12 +106,13 @@ def __init__(self, name: str, process: subprocess.Popen, socket: Optional[socket
self._processor = processor
self._reader_thread = threading.Thread(target=self._read_loop, name='{}-reader'.format(name))
self._writer_thread = threading.Thread(target=self._write_loop, name='{}-writer'.format(name))
self._stderr_thread = threading.Thread(target=self._stderr_loop, name='{}-stderr'.format(name))
self._callback_object = weakref.ref(callback_object)
self._send_queue = Queue(0) # type: Queue[Union[T, None]]
self._reader_thread.start()
self._writer_thread.start()
self._stderr_thread.start()
if stderr:
self._stderr_thread = threading.Thread(target=self._stderr_loop, name='{}-stderr'.format(name))
self._stderr_thread.start()

def send(self, payload: T) -> None:
self._send_queue.put_nowait(payload)
Expand All @@ -135,7 +136,8 @@ def __del__(self) -> None:
self.close()
self._join_thread(self._writer_thread)
self._join_thread(self._reader_thread)
self._join_thread(self._stderr_thread)
if self._stderr_thread:
self._join_thread(self._stderr_thread)

def _read_loop(self) -> None:
exception = None
Expand Down Expand Up @@ -164,23 +166,24 @@ def invoke(p: T) -> None:

def _end(self, exception: Optional[Exception]) -> None:
exit_code = 0
if not exception:
try:
# Allow the process to stop itself.
exit_code = self._process.wait(1)
except (AttributeError, ProcessLookupError, subprocess.TimeoutExpired):
pass
if self._process.poll() is None:
try:
# The process didn't stop itself. Terminate!
self._process.kill()
# still wait for the process to die, or zombie processes might be the result
# Ignore the exit code in this case, it's going to be something non-zero because we sent SIGKILL.
self._process.wait()
except (AttributeError, ProcessLookupError):
pass
except Exception as ex:
exception = ex # TODO: Old captured exception is overwritten
if self._process:
if not exception:
try:
# Allow the process to stop itself.
exit_code = self._process.wait(1)
except (AttributeError, ProcessLookupError, subprocess.TimeoutExpired):
pass
if self._process.poll() is None:
try:
# The process didn't stop itself. Terminate!
self._process.kill()
# still wait for the process to die, or zombie processes might be the result
# Ignore the exit code in this case, it's going to be something non-zero because we sent SIGKILL.
self._process.wait()
except (AttributeError, ProcessLookupError):
pass
except Exception as ex:
exception = ex # TODO: Old captured exception is overwritten

def invoke() -> None:
callback_object = self._callback_object()
Expand Down Expand Up @@ -252,13 +255,12 @@ def start_subprocess() -> subprocess.Popen:
if config.listener_socket:
assert isinstance(config.tcp_port, int) and config.tcp_port > 0
process, sock, reader, writer = _await_tcp_connection(
config.name,
config.tcp_port,
config.listener_socket,
start_subprocess
)
config.name, config.tcp_port, config.listener_socket, start_subprocess)
else:
process = start_subprocess()
if config.command:
process = start_subprocess()
rchl marked this conversation as resolved.
Show resolved Hide resolved
elif not config.tcp_port:
raise RuntimeError("Failed to provide command or tcp_port, at least one of them has to be configured")
if config.tcp_port:
sock = _connect_tcp(config.tcp_port)
if sock is None:
Expand All @@ -270,8 +272,9 @@ def start_subprocess() -> subprocess.Popen:
writer = process.stdin # type: ignore
if not reader or not writer:
raise RuntimeError('Failed initializing transport: reader: {}, writer: {}'.format(reader, writer))
stderr = process.stderr if process else None
return ProcessTransport(
config.name, process, sock, reader, writer, process.stderr, json_rpc_processor, callback_object) # type: ignore
config.name, process, sock, reader, writer, stderr, json_rpc_processor, callback_object) # type: ignore


_subprocesses = weakref.WeakSet() # type: weakref.WeakSet[subprocess.Popen]
Expand Down