mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-19 12:16:35 +00:00
LS: hacky solution for waiting till pyright and ts servers are ready
This commit is contained in:
1 parent
5c530fa642
commit
2fba67f231
2 files changed
+70
-12
No files matched your search
@@ -2,10 +2,12 @@
|
|||||||
Provides Python specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Python.
|
Provides Python specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Python.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import re
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from typing import AsyncIterator, Tuple
|
from typing import AsyncIterator, Tuple
|
||||||
|
|
||||||
@@ -38,6 +40,10 @@ class PyrightServer(LanguageServer):
|
|||||||
"python",
|
"python",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Event to signal when initial workspace analysis is complete
|
||||||
|
self.analysis_complete = asyncio.Event()
|
||||||
|
self.found_source_files = False
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||||
return super().is_ignored_dirname(dirname) or dirname in ["venv", "__pycache__"]
|
return super().is_ignored_dirname(dirname) or dirname in ["venv", "__pycache__"]
|
||||||
@@ -145,16 +151,19 @@ class PyrightServer(LanguageServer):
|
|||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def start_server(self) -> AsyncIterator["PyrightServer"]:
|
async def start_server(self) -> AsyncIterator["PyrightServer"]:
|
||||||
"""
|
"""
|
||||||
Starts the Pyright Language Server, waits for the server to be ready and yields the LanguageServer instance.
|
Starts the Pyright Language Server and waits for initial workspace analysis to complete.
|
||||||
|
|
||||||
|
This prevents zombie processes by ensuring Pyright has finished its initial background
|
||||||
|
tasks before we consider the server ready.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
```
|
```
|
||||||
async with lsp.start_server():
|
async with lsp.start_server():
|
||||||
# LanguageServer has been initialized and ready to serve requests
|
# LanguageServer has been initialized and workspace analysis is complete
|
||||||
await lsp.request_definition(...)
|
await lsp.request_definition(...)
|
||||||
await lsp.request_references(...)
|
await lsp.request_references(...)
|
||||||
# Shutdown the LanguageServer on exit from scope
|
# Shutdown the LanguageServer on exit from scope
|
||||||
# LanguageServer has been shutdown
|
# LanguageServer has been shutdown cleanly
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -164,13 +173,33 @@ class PyrightServer(LanguageServer):
|
|||||||
async def do_nothing(params):
|
async def do_nothing(params):
|
||||||
return
|
return
|
||||||
|
|
||||||
async def check_experimental_status(params):
|
async def window_log_message(msg):
|
||||||
if params["quiescent"] == True:
|
"""
|
||||||
|
Monitor Pyright's log messages to detect when initial analysis is complete.
|
||||||
|
Pyright logs "Found X source files" when it finishes scanning the workspace.
|
||||||
|
"""
|
||||||
|
message_text = msg.get("message", "")
|
||||||
|
self.logger.log(f"LSP: window/logMessage: {message_text}", logging.INFO)
|
||||||
|
|
||||||
|
# Look for "Found X source files" which indicates workspace scanning is complete
|
||||||
|
# Unfortunately, pyright is unreliable and there seems to be no better way
|
||||||
|
if re.search(r"Found \d+ source files?", message_text):
|
||||||
|
self.logger.log("Pyright workspace scanning complete", logging.INFO)
|
||||||
|
self.found_source_files = True
|
||||||
|
self.analysis_complete.set()
|
||||||
self.completions_available.set()
|
self.completions_available.set()
|
||||||
|
|
||||||
async def window_log_message(msg):
|
async def check_experimental_status(params):
|
||||||
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
|
"""
|
||||||
|
Also listen for experimental/serverStatus as a backup signal
|
||||||
|
"""
|
||||||
|
if params.get("quiescent") == True:
|
||||||
|
self.logger.log("Received experimental/serverStatus with quiescent=true", logging.INFO)
|
||||||
|
if not self.found_source_files:
|
||||||
|
self.analysis_complete.set()
|
||||||
|
self.completions_available.set()
|
||||||
|
|
||||||
|
# Set up notification handlers
|
||||||
self.server.on_request("client/registerCapability", do_nothing)
|
self.server.on_request("client/registerCapability", do_nothing)
|
||||||
self.server.on_notification("language/status", do_nothing)
|
self.server.on_notification("language/status", do_nothing)
|
||||||
self.server.on_notification("window/logMessage", window_log_message)
|
self.server.on_notification("window/logMessage", window_log_message)
|
||||||
@@ -195,6 +224,23 @@ class PyrightServer(LanguageServer):
|
|||||||
self.logger.log(f"Received initialize response from pyright server: {init_response}", logging.INFO)
|
self.logger.log(f"Received initialize response from pyright server: {init_response}", logging.INFO)
|
||||||
|
|
||||||
# Verify that the server supports our required features
|
# Verify that the server supports our required features
|
||||||
|
assert "textDocumentSync" in init_response["capabilities"]
|
||||||
|
assert "completionProvider" in init_response["capabilities"]
|
||||||
|
assert "definitionProvider" in init_response["capabilities"]
|
||||||
|
|
||||||
|
# Complete the initialization handshake
|
||||||
self.server.notify.initialized({})
|
self.server.notify.initialized({})
|
||||||
|
|
||||||
|
# Wait for Pyright to complete its initial workspace analysis
|
||||||
|
# This prevents zombie processes by ensuring background tasks finish
|
||||||
|
self.logger.log("Waiting for Pyright to complete initial workspace analysis...", logging.INFO)
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(self.analysis_complete.wait(), timeout=1.0)
|
||||||
|
self.logger.log("Pyright initial analysis complete, server ready", logging.INFO)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
self.logger.log("Timeout waiting for Pyright analysis completion, proceeding anyway", logging.WARNING)
|
||||||
|
# Fallback: assume analysis is complete after timeout
|
||||||
|
self.analysis_complete.set()
|
||||||
|
self.completions_available.set()
|
||||||
|
|
||||||
yield self
|
yield self
|
||||||
+17
-5
@@ -187,11 +187,21 @@ class TypeScriptLanguageServer(LanguageServer):
|
|||||||
async def window_log_message(msg):
|
async def window_log_message(msg):
|
||||||
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
|
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
|
async def check_experimental_status(params):
|
||||||
|
"""
|
||||||
|
Also listen for experimental/serverStatus as a backup signal
|
||||||
|
"""
|
||||||
|
if params.get("quiescent") == True:
|
||||||
|
self.server_ready.set()
|
||||||
|
self.completions_available.set()
|
||||||
|
|
||||||
self.server.on_request("client/registerCapability", register_capability_handler)
|
self.server.on_request("client/registerCapability", register_capability_handler)
|
||||||
self.server.on_notification("window/logMessage", window_log_message)
|
self.server.on_notification("window/logMessage", window_log_message)
|
||||||
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
|
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
|
||||||
self.server.on_notification("$/progress", do_nothing)
|
self.server.on_notification("$/progress", do_nothing)
|
||||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||||
|
self.server.on_notification("experimental/serverStatus", check_experimental_status)
|
||||||
|
|
||||||
async with super().start_server():
|
async with super().start_server():
|
||||||
self.logger.log("Starting TypeScript server process", logging.INFO)
|
self.logger.log("Starting TypeScript server process", logging.INFO)
|
||||||
@@ -213,11 +223,13 @@ class TypeScriptLanguageServer(LanguageServer):
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.server.notify.initialized({})
|
self.server.notify.initialized({})
|
||||||
self.completions_available.set()
|
try:
|
||||||
|
await asyncio.wait_for(self.server_ready.wait(), timeout=1.0)
|
||||||
# TypeScript server is typically ready immediately after initialization
|
except asyncio.TimeoutError:
|
||||||
self.server_ready.set()
|
self.logger.log("Timeout waiting for TypeScript server to become ready, proceeding anyway", logging.INFO)
|
||||||
await self.server_ready.wait()
|
# Fallback: assume server is ready after timeout
|
||||||
|
self.server_ready.set()
|
||||||
|
self.completions_available.set()
|
||||||
|
|
||||||
yield self
|
yield self
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user