VWED_server/routes/lsp_websocket_api.py

226 lines
8.5 KiB
Python
Raw Normal View History

2025-09-30 13:52:36 +08:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
LSP WebSocket API路由
为脚本编辑器提供基于WebSocket的Language Server Protocol支持
"""
import json
import asyncio
from typing import Dict, Any, Optional
from fastapi import WebSocket, WebSocketDisconnect, APIRouter
from utils.logger import get_logger
from services.online_script.lsp_completion_service import get_lsp_completion_service
logger = get_logger("routes.lsp_websocket")
router = APIRouter(prefix="/lsp", tags=["LSP WebSocket"])
class LSPConnectionManager:
"""LSP WebSocket连接管理器"""
def __init__(self):
self.active_connections: Dict[str, Dict[str, Any]] = {}
self.lsp_service = get_lsp_completion_service()
async def connect(self, websocket: WebSocket, client_id: str):
"""建立WebSocket连接"""
await websocket.accept()
self.active_connections[client_id] = {
'websocket': websocket,
'documents': {}, # 存储文档状态
'initialized': False
}
logger.info(f"LSP客户端连接: {client_id}")
def disconnect(self, client_id: str):
"""断开WebSocket连接"""
if client_id in self.active_connections:
del self.active_connections[client_id]
logger.info(f"LSP客户端断开: {client_id}")
async def send_message(self, client_id: str, message: Dict[str, Any]):
"""发送消息给指定客户端"""
if client_id in self.active_connections:
websocket = self.active_connections[client_id]['websocket']
try:
await websocket.send_text(json.dumps(message, ensure_ascii=False))
except Exception as e:
logger.error(f"发送LSP消息失败 {client_id}: {e}")
self.disconnect(client_id)
async def handle_message(self, client_id: str, message: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""处理LSP消息"""
try:
method = message.get('method')
params = message.get('params', {})
# 更新文档状态
if method == 'textDocument/didOpen':
await self._handle_did_open(client_id, params)
elif method == 'textDocument/didChange':
await self._handle_did_change(client_id, params)
elif method == 'textDocument/didClose':
await self._handle_did_close(client_id, params)
# 为需要文档内容的请求添加文档文本
if method in ['textDocument/completion', 'textDocument/signatureHelp', 'textDocument/hover']:
text_document = params.get('textDocument', {})
uri = text_document.get('uri')
if uri and client_id in self.active_connections:
documents = self.active_connections[client_id]['documents']
if uri in documents:
# 将文档内容添加到context中
if 'context' not in params:
params['context'] = {}
params['context']['documentText'] = documents[uri]['text']
message['params'] = params
# 使用LSP服务处理消息
response = self.lsp_service.handle_lsp_message(message)
return response
except Exception as e:
logger.error(f"处理LSP消息失败 {client_id}: {e}", exc_info=True)
return {
"jsonrpc": "2.0",
"id": message.get('id'),
"error": {
"code": -32603,
"message": f"Internal error: {str(e)}"
}
}
async def _handle_did_open(self, client_id: str, params: Dict[str, Any]):
"""处理文档打开"""
text_document = params.get('textDocument', {})
uri = text_document.get('uri')
text = text_document.get('text', '')
version = text_document.get('version', 1)
if uri and client_id in self.active_connections:
self.active_connections[client_id]['documents'][uri] = {
'text': text,
'version': version
}
logger.debug(f"文档已打开: {uri}")
async def _handle_did_change(self, client_id: str, params: Dict[str, Any]):
"""处理文档变更"""
text_document = params.get('textDocument', {})
uri = text_document.get('uri')
version = text_document.get('version')
content_changes = params.get('contentChanges', [])
if uri and client_id in self.active_connections:
documents = self.active_connections[client_id]['documents']
if uri in documents:
# 应用文档变更
for change in content_changes:
if 'range' not in change:
# 全文更新
documents[uri]['text'] = change.get('text', '')
else:
# 增量更新简化实现实际应用中需要精确处理range
documents[uri]['text'] = change.get('text', documents[uri]['text'])
documents[uri]['version'] = version
logger.debug(f"文档已更新: {uri}, 版本: {version}")
async def _handle_did_close(self, client_id: str, params: Dict[str, Any]):
"""处理文档关闭"""
text_document = params.get('textDocument', {})
uri = text_document.get('uri')
if uri and client_id in self.active_connections:
documents = self.active_connections[client_id]['documents']
if uri in documents:
del documents[uri]
logger.debug(f"文档已关闭: {uri}")
# 全局连接管理器
lsp_manager = LSPConnectionManager()
@router.websocket("/code-completion/{client_id}")
async def lsp_websocket_endpoint(websocket: WebSocket, client_id: str):
"""LSP WebSocket端点"""
await lsp_manager.connect(websocket, client_id)
try:
while True:
# 接收消息
data = await websocket.receive_text()
try:
message = json.loads(data)
logger.debug(f"接收到LSP消息 {client_id}: {message.get('method', 'unknown')}")
# 处理消息
response = await lsp_manager.handle_message(client_id, message)
# 发送响应
if response:
await lsp_manager.send_message(client_id, response)
except json.JSONDecodeError as e:
logger.error(f"JSON解析失败 {client_id}: {e}")
error_response = {
"jsonrpc": "2.0",
"id": None,
"error": {
"code": -32700,
"message": "Parse error"
}
}
await lsp_manager.send_message(client_id, error_response)
except Exception as e:
logger.error(f"处理消息失败 {client_id}: {e}", exc_info=True)
except WebSocketDisconnect:
logger.info(f"LSP客户端断开连接: {client_id}")
except Exception as e:
logger.error(f"WebSocket连接异常 {client_id}: {e}")
finally:
lsp_manager.disconnect(client_id)
@router.get("/health")
async def lsp_health_check():
"""LSP服务健康检查"""
return {
"status": "healthy",
"service": "VWED LSP Code Completion",
"active_connections": len(lsp_manager.active_connections),
"capabilities": [
"textDocument/completion",
"textDocument/signatureHelp",
"textDocument/hover",
"textDocument/didOpen",
"textDocument/didChange",
"textDocument/didClose"
]
}
@router.get("/clients")
async def get_active_clients():
"""获取活跃的LSP客户端"""
clients = []
for client_id, connection in lsp_manager.active_connections.items():
clients.append({
"client_id": client_id,
"initialized": connection.get('initialized', False),
"document_count": len(connection.get('documents', {})),
"documents": list(connection.get('documents', {}).keys())
})
return {
"active_clients": clients,
"total_count": len(clients)
}