mirror of
https://github.com/HKUDS/OpenSpace.git
synced 2026-08-28 05:15:00 +00:00
138 lines
5.3 KiB
Python
138 lines
5.3 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from openspace.services.lsp.config import get_all_lsp_servers
|
|
from openspace.services.lsp.server_instance import LSPServerInstance, _file_uri
|
|
from openspace.services.lsp.types import LSPServerConfig
|
|
from openspace.utils.logging import Logger
|
|
|
|
logger = Logger.get_logger(__name__)
|
|
|
|
|
|
class LSPServerManager:
|
|
"""Routes files to configured language servers and lazily starts them."""
|
|
|
|
def __init__(self, *, cwd: str | None = None) -> None:
|
|
self.cwd = cwd or os.getcwd()
|
|
self._servers: dict[str, LSPServerInstance] = {}
|
|
self._extension_map: dict[str, list[str]] = {}
|
|
self._opened_files: dict[str, str] = {}
|
|
|
|
async def initialize(self) -> None:
|
|
configs = get_all_lsp_servers(self.cwd)
|
|
for name, config in configs.items():
|
|
try:
|
|
if not config.command or not config.extension_to_language:
|
|
continue
|
|
for ext in config.extension_to_language:
|
|
self._extension_map.setdefault(ext.lower(), []).append(name)
|
|
self._servers[name] = LSPServerInstance(name, config)
|
|
self._servers[name].on_request("workspace/configuration", self._handle_workspace_configuration)
|
|
except Exception as exc:
|
|
logger.debug("Failed to initialize LSP server %s: %s", name, exc)
|
|
|
|
async def shutdown(self) -> None:
|
|
results = await asyncio.gather(
|
|
*(server.stop() for server in self._servers.values() if server.state in {"running", "error"}),
|
|
return_exceptions=True,
|
|
)
|
|
self._servers.clear()
|
|
self._extension_map.clear()
|
|
self._opened_files.clear()
|
|
errors = [str(result) for result in results if isinstance(result, Exception)]
|
|
if errors:
|
|
raise RuntimeError(f"Failed to stop {len(errors)} LSP server(s): {'; '.join(errors)}")
|
|
|
|
def get_server_for_file(self, file_path: str) -> LSPServerInstance | None:
|
|
ext = Path(file_path).suffix.lower()
|
|
names = self._extension_map.get(ext) or []
|
|
return self._servers.get(names[0]) if names else None
|
|
|
|
async def ensure_server_started(self, file_path: str) -> LSPServerInstance | None:
|
|
server = self.get_server_for_file(file_path)
|
|
if server is None:
|
|
return None
|
|
if server.state in {"stopped", "error"}:
|
|
await server.start()
|
|
return server
|
|
|
|
async def send_request(self, file_path: str, method: str, params: Any) -> Any | None:
|
|
server = await self.ensure_server_started(file_path)
|
|
if server is None:
|
|
return None
|
|
return await server.send_request(method, params)
|
|
|
|
def get_all_servers(self) -> dict[str, LSPServerInstance]:
|
|
return self._servers
|
|
|
|
async def open_file(self, file_path: str, content: str) -> None:
|
|
server = await self.ensure_server_started(file_path)
|
|
if server is None:
|
|
return
|
|
file_uri = _file_uri(file_path)
|
|
if self._opened_files.get(file_uri) == server.name:
|
|
return
|
|
ext = Path(file_path).suffix.lower()
|
|
language_id = server.config.extension_to_language.get(ext, "plaintext")
|
|
await server.send_notification(
|
|
"textDocument/didOpen",
|
|
{
|
|
"textDocument": {
|
|
"uri": file_uri,
|
|
"languageId": language_id,
|
|
"version": 1,
|
|
"text": content,
|
|
}
|
|
},
|
|
)
|
|
self._opened_files[file_uri] = server.name
|
|
|
|
async def change_file(self, file_path: str, content: str) -> None:
|
|
server = self.get_server_for_file(file_path)
|
|
if server is None or server.state != "running":
|
|
await self.open_file(file_path, content)
|
|
return
|
|
file_uri = _file_uri(file_path)
|
|
if self._opened_files.get(file_uri) != server.name:
|
|
await self.open_file(file_path, content)
|
|
return
|
|
await server.send_notification(
|
|
"textDocument/didChange",
|
|
{
|
|
"textDocument": {"uri": file_uri, "version": 1},
|
|
"contentChanges": [{"text": content}],
|
|
},
|
|
)
|
|
|
|
async def save_file(self, file_path: str) -> None:
|
|
server = self.get_server_for_file(file_path)
|
|
if server is None or server.state != "running":
|
|
return
|
|
await server.send_notification("textDocument/didSave", {"textDocument": {"uri": _file_uri(file_path)}})
|
|
|
|
async def close_file(self, file_path: str) -> None:
|
|
server = self.get_server_for_file(file_path)
|
|
if server is None or server.state != "running":
|
|
return
|
|
file_uri = _file_uri(file_path)
|
|
await server.send_notification("textDocument/didClose", {"textDocument": {"uri": file_uri}})
|
|
self._opened_files.pop(file_uri, None)
|
|
|
|
def is_file_open(self, file_path: str) -> bool:
|
|
return _file_uri(file_path) in self._opened_files
|
|
|
|
@staticmethod
|
|
def _handle_workspace_configuration(params: Any) -> list[None]:
|
|
items = []
|
|
if isinstance(params, dict):
|
|
raw_items = params.get("items")
|
|
if isinstance(raw_items, list):
|
|
items = raw_items
|
|
return [None for _ in items]
|
|
|
|
|
|
__all__ = ["LSPServerManager", "LSPServerConfig"]
|