Skip to content

Commit

Permalink
support client config with tcp_port but without command (#2300)
Browse files Browse the repository at this point in the history
Co-authored-by: Marek Budik <mbudik@protonmail.com>
Co-authored-by: Rafal Chlodnicki <rchl2k@gmail.com>
  • Loading branch information
3 people authored Aug 15, 2023
1 parent 69f383d commit eac7700
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 30 deletions.
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()
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

0 comments on commit eac7700

Please sign in to comment.