#!/usr/bin/env python # -*- coding: utf-8 -*- """ LSP (Language Server Protocol) 代码补全服务 基于WebSocket通信,为前端脚本编辑器提供智能补全功能 """ import ast import re import json import inspect import keyword import builtins from typing import Dict, List, Any, Optional, Tuple, Union from dataclasses import dataclass from utils.logger import get_logger from .built_in_modules import ( VWEDApiModule, VWEDFunctionModule, VWEDEventModule, VWEDTimerModule, VWEDLogModule, VWEDTaskModule, VWEDDataModule, VWEDUtilModule, VWEDDeviceModule ) logger = get_logger("services.lsp_completion") @dataclass class LSPPosition: """LSP位置信息""" line: int character: int @dataclass class LSPRange: """LSP范围信息""" start: LSPPosition end: LSPPosition @dataclass class LSPCompletionItem: """LSP补全项""" label: str kind: int # CompletionItemKind detail: Optional[str] = None documentation: Optional[str] = None insert_text: Optional[str] = None insert_text_format: int = 1 # PlainText = 1, Snippet = 2 sort_text: Optional[str] = None filter_text: Optional[str] = None class LSPCompletionItemKind: """LSP补全项类型枚举""" Text = 1 Method = 2 Function = 3 Constructor = 4 Field = 5 Variable = 6 Class = 7 Interface = 8 Module = 9 Property = 10 Unit = 11 Value = 12 Enum = 13 Keyword = 14 Snippet = 15 Color = 16 File = 17 Reference = 18 class VWEDLSPCompletionService: """VWED LSP代码补全服务""" def __init__(self): self.builtin_modules = self._init_builtin_modules() self.vwed_namespace = self._build_vwed_namespace() self.python_keywords = keyword.kwlist self.python_builtins = dir(builtins) # 打印初始化信息 logger.info(f"LSP代码补全服务初始化完成") logger.info(f"已加载VWED模块: {list(self.builtin_modules.keys())}") logger.info(f"VWED命名空间: {list(self.vwed_namespace.keys())}") for module_name, methods in self.vwed_namespace.items(): logger.debug(f"模块 {module_name} 包含方法: {list(methods.keys())[:5]}...") # 只显示前5个方法 def _init_builtin_modules(self) -> Dict[str, Any]: """初始化内置模块""" return { 'api': VWEDApiModule(None), 'function': VWEDFunctionModule(None), 'event': VWEDEventModule(None), 'timer': VWEDTimerModule(None), 'log': VWEDLogModule(None), 'task': VWEDTaskModule(None), 'data': VWEDDataModule(None), 'util': VWEDUtilModule(None), 'device': VWEDDeviceModule(None), } def _build_vwed_namespace(self) -> Dict[str, Dict[str, Any]]: """构建VWED命名空间结构""" namespace = {} for module_name, module_instance in self.builtin_modules.items(): module_methods = {} for attr_name in dir(module_instance): if not attr_name.startswith('_'): attr = getattr(module_instance, attr_name) if callable(attr): try: sig = inspect.signature(attr) doc = inspect.getdoc(attr) or f"VWED.{module_name}.{attr_name} 方法" module_methods[attr_name] = { 'type': 'method', 'signature': str(sig), 'doc': doc, 'parameters': self._extract_parameters(sig) } except Exception as e: logger.warning(f"获取方法 {attr_name} 签名失败: {e}") module_methods[attr_name] = { 'type': 'method', 'signature': '(...)', 'doc': f"VWED.{module_name}.{attr_name} 方法", 'parameters': [] } namespace[module_name] = module_methods return namespace def _extract_parameters(self, signature: inspect.Signature) -> List[Dict[str, Any]]: """提取函数参数信息""" parameters = [] for param_name, param in signature.parameters.items(): if param_name == 'self': continue param_info = { 'name': param_name, 'kind': param.kind.name, 'default': str(param.default) if param.default != inspect.Parameter.empty else None, 'annotation': str(param.annotation) if param.annotation != inspect.Parameter.empty else None } parameters.append(param_info) return parameters def handle_lsp_message(self, message: Dict[str, Any]) -> Optional[Dict[str, Any]]: """处理LSP消息""" try: method = message.get('method') params = message.get('params', {}) request_id = message.get('id') if method == 'textDocument/completion': result = self._handle_completion(params) return self._create_response(request_id, result) elif method == 'textDocument/signatureHelp': result = self._handle_signature_help(params) return self._create_response(request_id, result) elif method == 'textDocument/hover': result = self._handle_hover(params) return self._create_response(request_id, result) elif method == 'textDocument/didOpen': # 文档打开通知,无需响应 return None elif method == 'textDocument/didChange': # 文档变更通知,无需响应 return None elif method == 'initialize': result = self._handle_initialize(params) return self._create_response(request_id, result) else: logger.warning(f"未处理的LSP方法: {method}") return self._create_error_response(request_id, -32601, "Method not found") except Exception as e: logger.error(f"处理LSP消息失败: {e}", exc_info=True) return self._create_error_response( message.get('id'), -32603, f"Internal error: {str(e)}" ) def _handle_initialize(self, params: Dict[str, Any]) -> Dict[str, Any]: """处理初始化请求""" return { "capabilities": { "completionProvider": { "triggerCharacters": [".", " "], "resolveProvider": False }, "signatureHelpProvider": { "triggerCharacters": ["(", ","] }, "hoverProvider": True, "textDocumentSync": { "openClose": True, "change": 2 # Incremental } }, "serverInfo": { "name": "VWED Python Language Server", "version": "1.0.0" } } def _handle_completion(self, params: Dict[str, Any]) -> Dict[str, Any]: """处理代码补全请求""" try: text_document = params.get('textDocument', {}) position = params.get('position', {}) context = params.get('context', {}) # 这里需要获取文档内容,实际实现中需要维护文档状态 # 暂时从context中获取或者通过其他方式获取完整文档内容 document_text = context.get('documentText', '') line = position.get('line', 0) character = position.get('character', 0) logger.debug(f"补全请求 - 行: {line}, 列: {character}") logger.debug(f"文档内容长度: {len(document_text)}") # 转换为字符位置 cursor_position = self._position_to_offset(document_text, line, character) # 获取补全建议 completions = self._get_completions(document_text, cursor_position) logger.info(f"生成补全建议数量: {len(completions)}") if completions: logger.debug(f"前3个补全建议: {[c['label'] for c in completions[:3]]}") result = { "isIncomplete": False, "items": [self._to_lsp_completion_item(item) for item in completions] } return result except Exception as e: logger.error(f"处理补全请求失败: {e}", exc_info=True) return {"isIncomplete": False, "items": []} def _handle_signature_help(self, params: Dict[str, Any]) -> Optional[Dict[str, Any]]: """处理函数签名帮助请求""" try: position = params.get('position', {}) context = params.get('context', {}) document_text = context.get('documentText', '') line = position.get('line', 0) character = position.get('character', 0) cursor_position = self._position_to_offset(document_text, line, character) signature_info = self._get_signature_help(document_text, cursor_position) if signature_info: return { "signatures": [signature_info], "activeSignature": 0, "activeParameter": signature_info.get('activeParameter', 0) } return None except Exception as e: logger.error(f"处理签名帮助失败: {e}") return None def _handle_hover(self, params: Dict[str, Any]) -> Optional[Dict[str, Any]]: """处理悬停信息请求""" try: position = params.get('position', {}) context = params.get('context', {}) document_text = context.get('documentText', '') line = position.get('line', 0) character = position.get('character', 0) hover_info = self._get_hover_info(document_text, line, character) if hover_info: return { "contents": { "kind": "markdown", "value": hover_info } } return None except Exception as e: logger.error(f"处理悬停信息失败: {e}") return None def _get_completions(self, code: str, cursor_position: int) -> List[Dict[str, Any]]: """获取代码补全建议""" try: line, column = self._get_cursor_position(code, cursor_position) current_line = self._get_current_line(code, line) prefix = self._get_completion_prefix(current_line, column) logger.debug(f"光标位置 - 行: {line}, 列: {column}") logger.debug(f"当前行: '{current_line}'") logger.debug(f"补全前缀: '{prefix}'") # 分析代码获取变量 variables = self._extract_variables_from_code(code) logger.debug(f"提取的变量: {variables}") completions = [] # VWED对象补全 if 'VWED.' in current_line: logger.debug("检测到VWED调用,获取VWED补全") vwed_completions = self._get_vwed_completions(prefix, current_line) completions.extend(vwed_completions) logger.debug(f"VWED补全数量: {len(vwed_completions)}") # Python关键字和内置函数补全 if '.' not in prefix: logger.debug("获取Python关键字和内置函数补全") keyword_completions = self._get_keyword_completions(prefix) builtin_completions = self._get_builtin_completions(prefix) variable_completions = self._get_variable_completions(prefix, variables) completions.extend(keyword_completions) completions.extend(builtin_completions) completions.extend(variable_completions) logger.debug(f"关键字补全数量: {len(keyword_completions)}") logger.debug(f"内置函数补全数量: {len(builtin_completions)}") logger.debug(f"变量补全数量: {len(variable_completions)}") # 如果没有前缀但检测到VWED,也提供基本的VWED模块补全 if not prefix and 'VWED' in current_line: logger.debug("提供基本VWED模块补全") for module in self.vwed_namespace.keys(): completions.append({ 'label': module, 'kind': 'module', 'detail': f'VWED.{module}', 'documentation': f'VWED内置{module}模块', 'insertText': module }) final_completions = self._deduplicate_and_sort(completions, prefix) logger.debug(f"最终补全数量: {len(final_completions)}") return final_completions except Exception as e: logger.error(f"获取补全建议失败: {e}", exc_info=True) return [] def _get_signature_help(self, code: str, cursor_position: int) -> Optional[Dict[str, Any]]: """获取函数签名帮助""" try: line, column = self._get_cursor_position(code, cursor_position) current_line = self._get_current_line(code, line) function_call = self._find_function_call(current_line, column) if function_call and function_call.startswith('VWED.'): parts = function_call.split('.') if len(parts) >= 3: module_name = parts[1] method_name = parts[2] if (module_name in self.vwed_namespace and method_name in self.vwed_namespace[module_name]): method_info = self.vwed_namespace[module_name][method_name] parameters = method_info.get('parameters', []) # 计算当前参数位置 active_param = self._calculate_active_parameter(current_line, column) return { "label": f"{function_call}{method_info['signature']}", "documentation": method_info.get('doc', ''), "parameters": [ {"label": param['name'], "documentation": param.get('annotation', '')} for param in parameters ], "activeParameter": min(active_param, len(parameters) - 1) if parameters else 0 } return None except Exception as e: logger.error(f"获取签名帮助失败: {e}") return None def _get_hover_info(self, code: str, line: int, character: int) -> Optional[str]: """获取悬停信息""" try: current_line = self._get_current_line(code, line) word = self._get_word_at_position(current_line, character) if word.startswith('VWED.'): parts = word.split('.') if len(parts) >= 3: module_name = parts[1] method_name = parts[2] if (module_name in self.vwed_namespace and method_name in self.vwed_namespace[module_name]): method_info = self.vwed_namespace[module_name][method_name] return f"```python\n{word}{method_info['signature']}\n```\n\n{method_info['doc']}" return None except Exception as e: logger.error(f"获取悬停信息失败: {e}") return None def _position_to_offset(self, text: str, line: int, character: int) -> int: """将行列位置转换为字符偏移""" lines = text.split('\n') offset = 0 for i in range(min(line, len(lines) - 1)): offset += len(lines[i]) + 1 # +1 for newline offset += min(character, len(lines[line]) if line < len(lines) else 0) return offset def _get_cursor_position(self, code: str, cursor_position: int) -> Tuple[int, int]: """将光标位置转换为行列坐标""" lines = code[:cursor_position].split('\n') line = len(lines) - 1 column = len(lines[-1]) return line, column def _get_current_line(self, code: str, line_number: int) -> str: """获取指定行的内容""" lines = code.split('\n') if 0 <= line_number < len(lines): return lines[line_number] return "" def _get_completion_prefix(self, line: str, column: int) -> str: """获取补全前缀""" start = column while start > 0 and (line[start - 1].isalnum() or line[start - 1] in ['_', '.']): start -= 1 return line[start:column] def _get_word_at_position(self, line: str, character: int) -> str: """获取指定位置的单词""" start = character end = character # 向左扩展 while start > 0 and (line[start - 1].isalnum() or line[start - 1] in ['_', '.']): start -= 1 # 向右扩展 while end < len(line) and (line[end].isalnum() or line[end] in ['_', '.']): end += 1 return line[start:end] def _find_function_call(self, line: str, column: int) -> Optional[str]: """查找当前位置的函数调用""" paren_pos = line.rfind('(', 0, column) if paren_pos == -1: return None func_end = paren_pos func_start = func_end while func_start > 0 and (line[func_start - 1].isalnum() or line[func_start - 1] in ['_', '.']): func_start -= 1 return line[func_start:func_end] if func_start < func_end else None def _calculate_active_parameter(self, line: str, column: int) -> int: """计算当前活跃的参数位置""" paren_pos = line.rfind('(', 0, column) if paren_pos == -1: return 0 # 计算逗号数量 param_text = line[paren_pos + 1:column] return param_text.count(',') def _extract_variables_from_code(self, code: str) -> List[str]: """从代码中提取变量名""" variables = [] try: tree = ast.parse(code) for node in ast.walk(tree): if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Store): variables.append(node.id) elif isinstance(node, ast.FunctionDef): variables.append(node.name) elif isinstance(node, ast.ClassDef): variables.append(node.name) except SyntaxError: # 语法错误时使用正则表达式 variable_pattern = r'\b([a-zA-Z_][a-zA-Z0-9_]*)\s*=' matches = re.findall(variable_pattern, code) variables.extend(matches) return list(set(variables)) def _get_vwed_completions(self, prefix: str, line: str) -> List[Dict[str, Any]]: """获取VWED对象的补全建议""" completions = [] vwed_match = re.search(r'VWED\.([a-zA-Z_][a-zA-Z0-9_]*)?\.?([a-zA-Z_][a-zA-Z0-9_]*)?', line) if vwed_match: module_name = vwed_match.group(1) method_name = vwed_match.group(2) if module_name and module_name in self.vwed_namespace: # 补全模块方法 for method, info in self.vwed_namespace[module_name].items(): if not method_name or method.startswith(method_name): completions.append({ 'label': method, 'kind': 'method', 'detail': info['signature'], 'documentation': info['doc'], 'insertText': method }) elif not module_name or any(mod.startswith(module_name or '') for mod in self.vwed_namespace): # 补全模块名 for module in self.vwed_namespace.keys(): if not module_name or module.startswith(module_name): completions.append({ 'label': module, 'kind': 'module', 'detail': f'VWED.{module}', 'documentation': f'VWED内置{module}模块', 'insertText': module }) return completions def _get_keyword_completions(self, prefix: str) -> List[Dict[str, Any]]: """获取Python关键字补全""" return [ { 'label': kw, 'kind': 'keyword', 'detail': 'Python关键字', 'insertText': kw } for kw in self.python_keywords if kw.startswith(prefix) ] def _get_builtin_completions(self, prefix: str) -> List[Dict[str, Any]]: """获取Python内置函数补全""" completions = [] for builtin_name in self.python_builtins: if builtin_name.startswith(prefix) and not builtin_name.startswith('_'): builtin_obj = getattr(builtins, builtin_name) completions.append({ 'label': builtin_name, 'kind': 'function' if callable(builtin_obj) else 'variable', 'detail': 'Python内置函数' if callable(builtin_obj) else 'Python内置变量', 'documentation': inspect.getdoc(builtin_obj) or "", 'insertText': builtin_name }) return completions def _get_variable_completions(self, prefix: str, variables: List[str]) -> List[Dict[str, Any]]: """获取变量补全""" return [ { 'label': var_name, 'kind': 'variable', 'detail': '局部变量', 'insertText': var_name } for var_name in variables if var_name.startswith(prefix) ] def _deduplicate_and_sort(self, completions: List[Dict[str, Any]], prefix: str) -> List[Dict[str, Any]]: """去重并排序补全建议""" seen = set() unique_completions = [] for completion in completions: label = completion['label'] if label not in seen: seen.add(label) unique_completions.append(completion) def sort_key(completion): label = completion['label'] if label == prefix: return (0, label) elif label.startswith(prefix): return (1, label) else: return (2, label) unique_completions.sort(key=sort_key) return unique_completions def _to_lsp_completion_item(self, item: Dict[str, Any]) -> Dict[str, Any]: """转换为LSP补全项格式""" kind_mapping = { 'method': LSPCompletionItemKind.Method, 'function': LSPCompletionItemKind.Function, 'variable': LSPCompletionItemKind.Variable, 'keyword': LSPCompletionItemKind.Keyword, 'module': LSPCompletionItemKind.Module, 'class': LSPCompletionItemKind.Class } return { "label": item['label'], "kind": kind_mapping.get(item['kind'], LSPCompletionItemKind.Text), "detail": item.get('detail'), "documentation": item.get('documentation'), "insertText": item.get('insertText', item['label']), "sortText": f"{item.get('priority', 5):02d}_{item['label']}" } def _create_response(self, request_id: Any, result: Any) -> Dict[str, Any]: """创建LSP响应""" return { "jsonrpc": "2.0", "id": request_id, "result": result } def _create_error_response(self, request_id: Any, code: int, message: str) -> Dict[str, Any]: """创建LSP错误响应""" return { "jsonrpc": "2.0", "id": request_id, "error": { "code": code, "message": message } } # 全局LSP补全服务实例 _lsp_completion_service = VWEDLSPCompletionService() def get_lsp_completion_service() -> VWEDLSPCompletionService: """获取LSP代码补全服务实例""" return _lsp_completion_service