Skip to main content

summer_lsp/scanner/
plugin.rs

1//! 插件扫描器模块
2//!
3//! 扫描项目中的所有插件注册(.add_plugin() 调用)
4
5use crate::protocol::types::{LocationResponse, PositionResponse, RangeResponse};
6
7use lsp_types::Url;
8use serde::{Deserialize, Serialize};
9use std::fs;
10use std::path::Path;
11
12/// 插件扫描器
13pub struct PluginScanner;
14
15impl PluginScanner {
16    /// 创建新的插件扫描器
17    pub fn new() -> Self {
18        Self
19    }
20
21    /// 扫描项目中的所有插件
22    ///
23    /// # Arguments
24    ///
25    /// * `project_path` - 项目根目录路径
26    ///
27    /// # Returns
28    ///
29    /// 返回扫描到的所有插件信息
30    pub fn scan_plugins(&self, project_path: &Path) -> Result<Vec<PluginInfoResponse>, ScanError> {
31        let mut plugins = Vec::new();
32
33        // 查找 main.rs
34        let main_path = project_path.join("src/main.rs");
35        if !main_path.exists() {
36            // 如果没有 main.rs,尝试 lib.rs
37            let lib_path = project_path.join("src/lib.rs");
38            if !lib_path.exists() {
39                return Err(ScanError::InvalidProject(
40                    "Neither main.rs nor lib.rs found".to_string(),
41                ));
42            }
43            self.scan_file(&lib_path, &mut plugins)?;
44            return Ok(plugins);
45        }
46
47        self.scan_file(&main_path, &mut plugins)?;
48        Ok(plugins)
49    }
50
51    /// 扫描单个文件中的插件
52    fn scan_file(
53        &self,
54        file_path: &Path,
55        plugins: &mut Vec<PluginInfoResponse>,
56    ) -> Result<(), ScanError> {
57        let content = fs::read_to_string(file_path)?;
58        let file_url = Url::from_file_path(file_path)
59            .map_err(|_| ScanError::InvalidProject("Failed to convert path to URL".to_string()))?;
60
61        // 简单的文本搜索 .add_plugin() 调用
62        // TODO: 使用 syn 进行更精确的 AST 分析
63        for (line_num, line) in content.lines().enumerate() {
64            if line.contains(".add_plugin(") {
65                // 提取插件类型名
66                if let Some(plugin_name) = self.extract_plugin_name(line) {
67                    plugins.push(PluginInfoResponse {
68                        name: plugin_name.clone(),
69                        type_name: plugin_name,
70                        config_prefix: None, // TODO: 从配置中推断
71                        location: LocationResponse {
72                            uri: file_url.to_string(),
73                            range: RangeResponse {
74                                start: PositionResponse {
75                                    line: line_num as u32,
76                                    character: 0,
77                                },
78                                end: PositionResponse {
79                                    line: line_num as u32,
80                                    character: line.len() as u32,
81                                },
82                            },
83                        },
84                    });
85                }
86            }
87        }
88
89        Ok(())
90    }
91
92    /// 从代码行中提取插件名称
93    fn extract_plugin_name(&self, line: &str) -> Option<String> {
94        // 查找 .add_plugin( 后面的内容
95        if let Some(start) = line.find(".add_plugin(") {
96            let after = &line[start + 12..]; // ".add_plugin(" 长度为 12
97
98            // 查找第一个非空白字符
99            let trimmed = after.trim_start();
100
101            // 提取插件类型名(到第一个括号、逗号或空白)
102            let end = trimmed
103                .find(|c: char| c == '(' || c == ')' || c == ',' || c.is_whitespace())
104                .unwrap_or(trimmed.len());
105
106            let plugin_name = &trimmed[..end];
107            if !plugin_name.is_empty() {
108                return Some(plugin_name.to_string());
109            }
110        }
111        None
112    }
113}
114
115impl Default for PluginScanner {
116    fn default() -> Self {
117        Self::new()
118    }
119}
120
121/// 插件信息响应(用于 JSON 序列化)
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct PluginInfoResponse {
124    /// 插件名称
125    pub name: String,
126    /// 插件类型名
127    #[serde(rename = "typeName")]
128    pub type_name: String,
129    /// 配置前缀(如果有)
130    #[serde(rename = "configPrefix")]
131    pub config_prefix: Option<String>,
132    /// 源代码位置
133    pub location: LocationResponse,
134}
135
136/// summer/plugins 请求参数
137#[derive(Debug, Deserialize)]
138pub struct PluginsRequest {
139    /// 应用路径
140    #[serde(rename = "appPath")]
141    pub app_path: String,
142}
143
144/// summer/plugins 响应
145#[derive(Debug, Serialize)]
146pub struct PluginsResponse {
147    /// 插件列表
148    pub plugins: Vec<PluginInfoResponse>,
149}
150
151/// 扫描错误
152#[derive(Debug, thiserror::Error)]
153pub enum ScanError {
154    #[error("Failed to read file: {0}")]
155    FileRead(#[from] std::io::Error),
156
157    #[error("Invalid project structure: {0}")]
158    InvalidProject(String),
159
160    #[error("No plugins found")]
161    NoPlugins,
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_plugin_scanner_new() {
170        let _scanner = PluginScanner::new();
171        // 验证扫描器创建成功(不会 panic)
172    }
173
174    #[test]
175    fn test_extract_plugin_name() {
176        let scanner = PluginScanner::new();
177
178        assert_eq!(
179            scanner.extract_plugin_name("    .add_plugin(WebPlugin)"),
180            Some("WebPlugin".to_string())
181        );
182
183        assert_eq!(
184            scanner.extract_plugin_name(".add_plugin(SqlxPlugin::new())"),
185            Some("SqlxPlugin::new".to_string())
186        );
187    }
188}