mirror of
https://github.com/tiennm99/serena.git
synced 2026-07-17 18:18:06 +00:00
LS: hacky solution for waiting till pyright and ts servers are ready
This commit is contained in:
committed by
Michael Panchenko
parent
5c530fa642
commit
2fba67f231
@@ -2,10 +2,12 @@
|
||||
Provides Python specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Python.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import AsyncIterator, Tuple
|
||||
|
||||
@@ -38,6 +40,10 @@ class PyrightServer(LanguageServer):
|
||||
"python",
|
||||
)
|
||||
|
||||
# Event to signal when initial workspace analysis is complete
|
||||
self.analysis_complete = asyncio.Event()
|
||||
self.found_source_files = False
|
||||
|
||||
@override
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["venv", "__pycache__"]
|
||||
@@ -145,16 +151,19 @@ class PyrightServer(LanguageServer):
|
||||
@asynccontextmanager
|
||||
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:
|
||||
```
|
||||
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_references(...)
|
||||
# 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):
|
||||
return
|
||||
|
||||
async def check_experimental_status(params):
|
||||
if params["quiescent"] == True:
|
||||
async def window_log_message(msg):
|
||||
"""
|
||||
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()
|
||||
|
||||
async def window_log_message(msg):
|
||||
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.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_notification("language/status", do_nothing)
|
||||
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)
|
||||
|
||||
# Verify that the server supports our required features
|
||||
self.server.notify.initialized({})
|
||||
assert "textDocumentSync" in init_response["capabilities"]
|
||||
assert "completionProvider" in init_response["capabilities"]
|
||||
assert "definitionProvider" in init_response["capabilities"]
|
||||
|
||||
yield self
|
||||
# Complete the initialization handshake
|
||||
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
|
||||
+17
-5
@@ -186,12 +186,22 @@ class TypeScriptLanguageServer(LanguageServer):
|
||||
|
||||
async def window_log_message(msg):
|
||||
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_notification("window/logMessage", window_log_message)
|
||||
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
|
||||
self.server.on_notification("$/progress", do_nothing)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
self.server.on_notification("experimental/serverStatus", check_experimental_status)
|
||||
|
||||
async with super().start_server():
|
||||
self.logger.log("Starting TypeScript server process", logging.INFO)
|
||||
@@ -213,11 +223,13 @@ class TypeScriptLanguageServer(LanguageServer):
|
||||
}
|
||||
|
||||
self.server.notify.initialized({})
|
||||
self.completions_available.set()
|
||||
|
||||
# TypeScript server is typically ready immediately after initialization
|
||||
self.server_ready.set()
|
||||
await self.server_ready.wait()
|
||||
try:
|
||||
await asyncio.wait_for(self.server_ready.wait(), timeout=1.0)
|
||||
except asyncio.TimeoutError:
|
||||
self.logger.log("Timeout waiting for TypeScript server to become ready, proceeding anyway", logging.INFO)
|
||||
# Fallback: assume server is ready after timeout
|
||||
self.server_ready.set()
|
||||
self.completions_available.set()
|
||||
|
||||
yield self
|
||||
|
||||
|
||||
Reference in New Issue
Block a user