Skip to main content

sh_layer3/builtin_tools/
code.rs

1//! # Code Analysis Tools
2//!
3//! 代码分析工具集:基于 LSP 的符号查找工具。
4//!
5//! ## 版本说明
6//!
7//! **当前版本**: v2 - LSP 集成版
8//!
9//! ### 能力范围
10//! - 查找函数、类、结构体、变量定义(跨模块)
11//! - 查找符号引用(项目级)
12//! - 获取类型信息和文档
13//! - 重命名符号(重构)
14//! - 多语言支持(Rust, Python, JavaScript/TypeScript, Java, Go, C/C++)
15//! - LSP 服务器自动管理
16//!
17//! ### 后备策略
18//! - 当 LSP 服务器不可用时,自动回退到正则匹配
19//! - 确保在任何环境下都能提供基本功能
20
21use crate::builtin_tools::BuiltinTool;
22use crate::lsp::{LspClient, Position};
23use crate::types::{Layer3Result, ToolCategory};
24use async_trait::async_trait;
25use regex::Regex;
26use std::fs;
27use std::path::{Path, PathBuf};
28use std::sync::Arc;
29use tokio::sync::Mutex;
30use tokio::sync::OnceCell;
31
32/// 工具版本标识
33pub const CODE_ANALYSIS_VERSION: &str = "v2-lsp-integrated";
34
35/// 全局 LSP 客户端(懒加载)
36static GLOBAL_LSP_CLIENT: OnceCell<Arc<Mutex<LspClient>>> = OnceCell::const_new();
37
38/// 获取或创建全局 LSP 客户端
39async fn get_lsp_client() -> Arc<Mutex<LspClient>> {
40    GLOBAL_LSP_CLIENT
41        .get_or_init(|| async { Arc::new(Mutex::new(LspClient::new())) })
42        .await
43        .clone()
44}
45
46// ============================================================================
47// Go to Definition Tool
48// ============================================================================
49
50/// Go to Definition Tool (LSP 版)
51///
52/// 使用 LSP 服务器查找符号定义位置,支持跨模块解析。
53///
54/// **优先级**:
55/// 1. 尝试使用 LSP 服务器(支持跨模块)
56/// 2. 回退到正则匹配(仅同目录)
57pub struct GoToDefinitionTool;
58
59#[async_trait]
60impl BuiltinTool for GoToDefinitionTool {
61    fn name(&self) -> &str {
62        "go_to_definition"
63    }
64
65    fn description(&self) -> &str {
66        "Find the definition of a symbol at a given location. \
67         [LSP VERSION] Uses Language Server Protocol for cross-module resolution. \
68         Automatically falls back to regex matching when LSP server is unavailable."
69    }
70
71    fn parameters_schema(&self) -> serde_json::Value {
72        serde_json::json!({
73            "type": "object",
74            "properties": {
75                "file": {
76                    "type": "string",
77                    "description": "The file path"
78                },
79                "line": {
80                    "type": "integer",
81                    "description": "Line number (1-based)"
82                },
83                "column": {
84                    "type": "integer",
85                    "description": "Column number (1-based)"
86                },
87                "symbol": {
88                    "type": "string",
89                    "description": "Optional: the symbol name to search for"
90                }
91            },
92            "required": ["file", "line", "column"]
93        })
94    }
95
96    fn category(&self) -> ToolCategory {
97        ToolCategory::CodeAnalysis
98    }
99
100    async fn execute(&self, args: serde_json::Value) -> Layer3Result<String> {
101        let file_path = args["file"]
102            .as_str()
103            .ok_or_else(|| anyhow::anyhow!("Missing file parameter"))?;
104
105        let line = args["line"].as_u64().unwrap_or(1) as usize;
106        let column = args["column"].as_u64().unwrap_or(1) as usize;
107        let symbol = args["symbol"].as_str();
108
109        let path = PathBuf::from(file_path);
110
111        // 尝试使用 LSP
112        match self.execute_with_lsp(&path, line, column).await {
113            Ok(result) => return Ok(result),
114            Err(e) => {
115                tracing::debug!("LSP failed, falling back to regex: {}", e);
116            }
117        }
118
119        // 回退到正则匹配
120        self.execute_with_regex(&path, line, column, symbol).await
121    }
122}
123
124impl GoToDefinitionTool {
125    /// 使用 LSP 查找定义
126    async fn execute_with_lsp(
127        &self,
128        file_path: &Path,
129        line: usize,
130        column: usize,
131    ) -> Layer3Result<String> {
132        let ext = file_path
133            .extension()
134            .and_then(|e| e.to_str())
135            .ok_or_else(|| anyhow::anyhow!("Unknown file extension"))?;
136
137        let language = crate::lsp::server::LanguageServerManager::get_language_from_extension(ext)
138            .ok_or_else(|| anyhow::anyhow!("No LSP server for extension: {}", ext))?;
139
140        let client = get_lsp_client().await;
141        let client = client.lock().await;
142
143        // 初始化服务器(如果未初始化)
144        let root_path = file_path.parent().unwrap_or(Path::new("."));
145
146        if !client.is_connected(language).await {
147            client
148                .initialize(language, root_path)
149                .await
150                .map_err(|e| anyhow::anyhow!("Failed to initialize LSP server: {}", e))?;
151        }
152
153        // 打开文档
154        client
155            .open_document(language, file_path)
156            .await
157            .map_err(|e| anyhow::anyhow!("Failed to open document: {}", e))?;
158
159        // LSP 使用 0-based 行列
160        let position = Position::new((line - 1) as u32, (column - 1) as u32);
161
162        // 查找定义
163        let locations = client
164            .go_to_definition(language, file_path, position)
165            .await
166            .map_err(|e| anyhow::anyhow!("LSP request failed: {}", e))?;
167
168        if locations.is_empty() {
169            return Err(anyhow::anyhow!("No definition found via LSP"));
170        }
171
172        // 格式化输出
173        let mut results = Vec::new();
174        for loc in locations {
175            let loc_path = loc
176                .uri
177                .strip_prefix("file://")
178                .unwrap_or(&loc.uri)
179                .strip_prefix('/')
180                .unwrap_or(&loc.uri);
181            results.push(format!(
182                "Definition found in {} at line {}, column {}:\n  [LSP cross-module result]",
183                loc_path,
184                loc.range.start.line + 1,
185                loc.range.start.character + 1
186            ));
187        }
188
189        Ok(results.join("\n"))
190    }
191
192    /// 使用正则回退查找
193    async fn execute_with_regex(
194        &self,
195        file_path: &Path,
196        line: usize,
197        column: usize,
198        symbol: Option<&str>,
199    ) -> Layer3Result<String> {
200        // 读取文件
201        let content = fs::read_to_string(file_path)
202            .map_err(|e| anyhow::anyhow!("Failed to read file {:?}: {}", file_path, e))?;
203
204        let lines: Vec<&str> = content.lines().collect();
205
206        // 获取当前行的符号
207        let current_line = lines.get(line - 1).copied().unwrap_or("");
208        let target_symbol = symbol
209            .map(|s| s.to_string())
210            .unwrap_or_else(|| extract_symbol_at_position(current_line, column));
211
212        if target_symbol.is_empty() {
213            return Ok("No symbol found at specified location".to_string());
214        }
215
216        // 根据文件类型确定定义模式
217        let file_ext = file_path.extension().and_then(|e| e.to_str()).unwrap_or("");
218
219        let definition_patterns = get_definition_patterns(file_ext, &target_symbol);
220
221        // 搜索定义
222        for pattern_str in definition_patterns {
223            let pattern =
224                Regex::new(&pattern_str).map_err(|e| anyhow::anyhow!("Invalid regex: {}", e))?;
225
226            // 先在当前文件中搜索
227            for (line_num, line_content) in lines.iter().enumerate() {
228                if pattern.is_match(line_content) {
229                    let match_info = pattern.find(line_content).unwrap();
230                    return Ok(format!(
231                        "Definition found in {} at line {}, column {}:\n{}",
232                        file_path.display(),
233                        line_num + 1,
234                        match_info.start() + 1,
235                        line_content.trim()
236                    ));
237                }
238            }
239
240            // 如果当前文件没找到,搜索同目录的其他文件
241            let dir = file_path.parent().unwrap_or(Path::new("."));
242            if let Ok(entries) = fs::read_dir(dir) {
243                for entry in entries.flatten() {
244                    let entry_path = entry.path();
245                    if entry_path.is_file() && entry_path != *file_path {
246                        let ext = entry_path
247                            .extension()
248                            .and_then(|e| e.to_str())
249                            .unwrap_or("");
250                        if ext == file_ext {
251                            if let Ok(other_content) = fs::read_to_string(&entry_path) {
252                                for (line_num, line_content) in other_content.lines().enumerate() {
253                                    if pattern.is_match(line_content) {
254                                        return Ok(format!(
255                                            "Definition found in {} at line {}:\n{}",
256                                            entry_path.display(),
257                                            line_num + 1,
258                                            line_content.trim()
259                                        ));
260                                    }
261                                }
262                            }
263                        }
264                    }
265                }
266            }
267        }
268
269        Ok(format!("No definition found for symbol: {}", target_symbol))
270    }
271}
272
273// ============================================================================
274// Find References Tool
275// ============================================================================
276
277/// Find References Tool (LSP 版)
278///
279/// 使用 LSP 服务器查找符号的所有引用位置,支持项目级搜索。
280pub struct FindReferencesTool;
281
282#[async_trait]
283impl BuiltinTool for FindReferencesTool {
284    fn name(&self) -> &str {
285        "find_references"
286    }
287
288    fn description(&self) -> &str {
289        "Find all references to a symbol at a given location. \
290         [LSP VERSION] Uses Language Server Protocol for project-wide search. \
291         Automatically falls back to regex matching when LSP server is unavailable."
292    }
293
294    fn parameters_schema(&self) -> serde_json::Value {
295        serde_json::json!({
296            "type": "object",
297            "properties": {
298                "file": {
299                    "type": "string",
300                    "description": "The file path"
301                },
302                "line": {
303                    "type": "integer",
304                    "description": "Line number (1-based)"
305                },
306                "column": {
307                    "type": "integer",
308                    "description": "Column number (1-based)"
309                },
310                "symbol": {
311                    "type": "string",
312                    "description": "Optional: the symbol name to search for"
313                },
314                "include_declaration": {
315                    "type": "boolean",
316                    "description": "Include declaration in results (default: true)"
317                }
318            },
319            "required": ["file", "line", "column"]
320        })
321    }
322
323    fn category(&self) -> ToolCategory {
324        ToolCategory::CodeAnalysis
325    }
326
327    async fn execute(&self, args: serde_json::Value) -> Layer3Result<String> {
328        let file_path = args["file"]
329            .as_str()
330            .ok_or_else(|| anyhow::anyhow!("Missing file parameter"))?;
331
332        let line = args["line"].as_u64().unwrap_or(1) as usize;
333        let column = args["column"].as_u64().unwrap_or(1) as usize;
334        let symbol = args["symbol"].as_str();
335        let include_declaration = args["include_declaration"].as_bool().unwrap_or(true);
336
337        let path = PathBuf::from(file_path);
338
339        // 尝试使用 LSP
340        match self
341            .execute_with_lsp(&path, line, column, include_declaration)
342            .await
343        {
344            Ok(result) => return Ok(result),
345            Err(e) => {
346                tracing::debug!("LSP failed, falling back to regex: {}", e);
347            }
348        }
349
350        // 回退到正则匹配
351        self.execute_with_regex(&path, line, column, symbol, include_declaration)
352            .await
353    }
354}
355
356impl FindReferencesTool {
357    /// 使用 LSP 查找引用
358    async fn execute_with_lsp(
359        &self,
360        file_path: &Path,
361        line: usize,
362        column: usize,
363        include_declaration: bool,
364    ) -> Layer3Result<String> {
365        let ext = file_path
366            .extension()
367            .and_then(|e| e.to_str())
368            .ok_or_else(|| anyhow::anyhow!("Unknown file extension"))?;
369
370        let language = crate::lsp::server::LanguageServerManager::get_language_from_extension(ext)
371            .ok_or_else(|| anyhow::anyhow!("No LSP server for extension: {}", ext))?;
372
373        let client = get_lsp_client().await;
374        let client = client.lock().await;
375
376        let root_path = file_path.parent().unwrap_or(Path::new("."));
377
378        if !client.is_connected(language).await {
379            client
380                .initialize(language, root_path)
381                .await
382                .map_err(|e| anyhow::anyhow!("Failed to initialize LSP server: {}", e))?;
383        }
384
385        client
386            .open_document(language, file_path)
387            .await
388            .map_err(|e| anyhow::anyhow!("Failed to open document: {}", e))?;
389
390        let position = Position::new((line - 1) as u32, (column - 1) as u32);
391
392        let locations = client
393            .find_references(language, file_path, position, include_declaration)
394            .await
395            .map_err(|e| anyhow::anyhow!("LSP request failed: {}", e))?;
396
397        if locations.is_empty() {
398            return Err(anyhow::anyhow!("No references found via LSP"));
399        }
400
401        let mut results = Vec::new();
402        for loc in locations {
403            let loc_path = loc
404                .uri
405                .strip_prefix("file://")
406                .unwrap_or(&loc.uri)
407                .strip_prefix('/')
408                .unwrap_or(&loc.uri);
409            results.push(format!(
410                "{}:{}:{}",
411                loc_path,
412                loc.range.start.line + 1,
413                loc.range.start.character + 1
414            ));
415        }
416
417        Ok(format!(
418            "Found {} references (LSP project-wide search):\n{}",
419            results.len(),
420            results.join("\n")
421        ))
422    }
423
424    /// 使用正则回退查找
425    async fn execute_with_regex(
426        &self,
427        file_path: &Path,
428        line: usize,
429        column: usize,
430        symbol: Option<&str>,
431        include_declaration: bool,
432    ) -> Layer3Result<String> {
433        let content = fs::read_to_string(file_path)
434            .map_err(|e| anyhow::anyhow!("Failed to read file {:?}: {}", file_path, e))?;
435
436        let lines: Vec<&str> = content.lines().collect();
437        let current_line = lines.get(line - 1).copied().unwrap_or("");
438
439        let target_symbol = symbol
440            .map(|s| s.to_string())
441            .unwrap_or_else(|| extract_symbol_at_position(current_line, column));
442
443        if target_symbol.is_empty() {
444            return Ok("No symbol found at specified location".to_string());
445        }
446
447        let reference_pattern = Regex::new(&format!(r"\b{}\b", target_symbol))
448            .map_err(|e| anyhow::anyhow!("Invalid regex for symbol: {}", e))?;
449
450        let mut results = Vec::new();
451
452        // 搜索当前文件
453        for (line_num, line_content) in lines.iter().enumerate() {
454            if reference_pattern.is_match(line_content) {
455                let is_declaration = is_definition_line(line_content, &target_symbol);
456                if include_declaration || !is_declaration {
457                    let matches: Vec<_> = reference_pattern.find_iter(line_content).collect();
458                    for m in matches {
459                        results.push(format!(
460                            "{}:{}:{} - {}",
461                            file_path.display(),
462                            line_num + 1,
463                            m.start() + 1,
464                            line_content.trim()
465                        ));
466                    }
467                }
468            }
469        }
470
471        // 搜索同目录的其他文件
472        let dir = file_path.parent().unwrap_or(Path::new("."));
473        let file_ext = file_path.extension().and_then(|e| e.to_str()).unwrap_or("");
474
475        if let Ok(entries) = fs::read_dir(dir) {
476            for entry in entries.flatten() {
477                let entry_path = entry.path();
478                if entry_path.is_file() && entry_path != *file_path {
479                    let ext = entry_path
480                        .extension()
481                        .and_then(|e| e.to_str())
482                        .unwrap_or("");
483                    if ext == file_ext {
484                        if let Ok(other_content) = fs::read_to_string(&entry_path) {
485                            for (line_num, line_content) in other_content.lines().enumerate() {
486                                if reference_pattern.is_match(line_content) {
487                                    let is_decl = is_definition_line(line_content, &target_symbol);
488                                    if include_declaration || !is_decl {
489                                        let matches: Vec<_> =
490                                            reference_pattern.find_iter(line_content).collect();
491                                        for m in matches {
492                                            results.push(format!(
493                                                "{}:{}:{} - {}",
494                                                entry_path.display(),
495                                                line_num + 1,
496                                                m.start() + 1,
497                                                line_content.trim()
498                                            ));
499                                        }
500                                    }
501                                }
502                            }
503                        }
504                    }
505                }
506            }
507        }
508
509        if results.is_empty() {
510            Ok(format!("No references found for symbol: {}", target_symbol))
511        } else {
512            Ok(format!(
513                "Found {} references:\n{}",
514                results.len(),
515                results.join("\n")
516            ))
517        }
518    }
519}
520
521// ============================================================================
522// Get Hover Tool
523// ============================================================================
524
525/// Get Hover Tool (LSP 版)
526///
527/// 获取符号的类型信息和文档。
528pub struct GetHoverTool;
529
530#[async_trait]
531impl BuiltinTool for GetHoverTool {
532    fn name(&self) -> &str {
533        "get_hover"
534    }
535
536    fn description(&self) -> &str {
537        "Get type information and documentation for a symbol at a given location. \
538         [LSP VERSION] Uses Language Server Protocol for accurate type information."
539    }
540
541    fn parameters_schema(&self) -> serde_json::Value {
542        serde_json::json!({
543            "type": "object",
544            "properties": {
545                "file": {
546                    "type": "string",
547                    "description": "The file path"
548                },
549                "line": {
550                    "type": "integer",
551                    "description": "Line number (1-based)"
552                },
553                "column": {
554                    "type": "integer",
555                    "description": "Column number (1-based)"
556                }
557            },
558            "required": ["file", "line", "column"]
559        })
560    }
561
562    fn category(&self) -> ToolCategory {
563        ToolCategory::CodeAnalysis
564    }
565
566    async fn execute(&self, args: serde_json::Value) -> Layer3Result<String> {
567        let file_path = args["file"]
568            .as_str()
569            .ok_or_else(|| anyhow::anyhow!("Missing file parameter"))?;
570
571        let line = args["line"].as_u64().unwrap_or(1) as usize;
572        let column = args["column"].as_u64().unwrap_or(1) as usize;
573
574        let path = PathBuf::from(file_path);
575
576        // 尝试使用 LSP
577        match self.execute_with_lsp(&path, line, column).await {
578            Ok(result) => return Ok(result),
579            Err(e) => {
580                tracing::debug!("LSP failed, falling back to basic info: {}", e);
581            }
582        }
583
584        // 回退到基本信息
585        self.execute_basic(&path, line, column).await
586    }
587}
588
589impl GetHoverTool {
590    async fn execute_with_lsp(
591        &self,
592        file_path: &Path,
593        line: usize,
594        column: usize,
595    ) -> Layer3Result<String> {
596        let ext = file_path
597            .extension()
598            .and_then(|e| e.to_str())
599            .ok_or_else(|| anyhow::anyhow!("Unknown file extension"))?;
600
601        let language = crate::lsp::server::LanguageServerManager::get_language_from_extension(ext)
602            .ok_or_else(|| anyhow::anyhow!("No LSP server for extension: {}", ext))?;
603
604        let client = get_lsp_client().await;
605        let client = client.lock().await;
606
607        let root_path = file_path.parent().unwrap_or(Path::new("."));
608
609        if !client.is_connected(language).await {
610            client
611                .initialize(language, root_path)
612                .await
613                .map_err(|e| anyhow::anyhow!("Failed to initialize LSP server: {}", e))?;
614        }
615
616        client
617            .open_document(language, file_path)
618            .await
619            .map_err(|e| anyhow::anyhow!("Failed to open document: {}", e))?;
620
621        let position = Position::new((line - 1) as u32, (column - 1) as u32);
622
623        let hover = client
624            .get_hover(language, file_path, position)
625            .await
626            .map_err(|e| anyhow::anyhow!("LSP request failed: {}", e))?
627            .ok_or_else(|| anyhow::anyhow!("No hover information available"))?;
628
629        // 格式化 hover 内容
630        let content = match hover.contents {
631            crate::lsp::HoverContents::Markup(markup) => {
632                format!(
633                    "```{}\n{}\n```",
634                    match markup.kind {
635                        crate::lsp::MarkupKind::PlainText => "",
636                        crate::lsp::MarkupKind::Markdown => "markdown",
637                    },
638                    markup.value
639                )
640            }
641            crate::lsp::HoverContents::String(s) => s,
642            crate::lsp::HoverContents::Array(arr) => arr
643                .iter()
644                .map(|item| match item {
645                    crate::lsp::MarkedString::String(s) => s.clone(),
646                    crate::lsp::MarkedString::LanguageString(ls) => {
647                        format!("```{}\n{}\n```", ls.language, ls.value)
648                    }
649                })
650                .collect::<Vec<_>>()
651                .join("\n\n"),
652        };
653
654        Ok(content)
655    }
656
657    async fn execute_basic(
658        &self,
659        file_path: &Path,
660        line: usize,
661        column: usize,
662    ) -> Layer3Result<String> {
663        let content = fs::read_to_string(file_path)
664            .map_err(|e| anyhow::anyhow!("Failed to read file: {}", e))?;
665
666        let lines: Vec<&str> = content.lines().collect();
667        let current_line = lines.get(line - 1).copied().unwrap_or("");
668
669        let symbol = extract_symbol_at_position(current_line, column);
670
671        if symbol.is_empty() {
672            return Ok(format!("Line {}: {}", line, current_line.trim()));
673        }
674
675        Ok(format!(
676            "**{}**\n\n```\n{}\n```",
677            symbol,
678            current_line.trim()
679        ))
680    }
681}
682
683// ============================================================================
684// Rename Symbol Tool
685// ============================================================================
686
687/// Rename Symbol Tool (LSP 版)
688///
689/// 重命名符号,支持跨文件重构。
690pub struct RenameSymbolTool;
691
692#[async_trait]
693impl BuiltinTool for RenameSymbolTool {
694    fn name(&self) -> &str {
695        "rename_symbol"
696    }
697
698    fn description(&self) -> &str {
699        "Rename a symbol across the entire project. \
700         [LSP VERSION] Uses Language Server Protocol for project-wide refactoring."
701    }
702
703    fn parameters_schema(&self) -> serde_json::Value {
704        serde_json::json!({
705            "type": "object",
706            "properties": {
707                "file": {
708                    "type": "string",
709                    "description": "The file path"
710                },
711                "line": {
712                    "type": "integer",
713                    "description": "Line number (1-based)"
714                },
715                "column": {
716                    "type": "integer",
717                    "description": "Column number (1-based)"
718                },
719                "new_name": {
720                    "type": "string",
721                    "description": "The new name for the symbol"
722                },
723                "dry_run": {
724                    "type": "boolean",
725                    "description": "If true, only preview changes without applying them (default: true)"
726                }
727            },
728            "required": ["file", "line", "column", "new_name"]
729        })
730    }
731
732    fn category(&self) -> ToolCategory {
733        ToolCategory::CodeAnalysis
734    }
735
736    fn requires_confirmation(&self) -> bool {
737        true
738    }
739
740    async fn execute(&self, args: serde_json::Value) -> Layer3Result<String> {
741        let file_path = args["file"]
742            .as_str()
743            .ok_or_else(|| anyhow::anyhow!("Missing file parameter"))?;
744
745        let line = args["line"].as_u64().unwrap_or(1) as usize;
746        let column = args["column"].as_u64().unwrap_or(1) as usize;
747        let new_name = args["new_name"]
748            .as_str()
749            .ok_or_else(|| anyhow::anyhow!("Missing new_name parameter"))?;
750        let dry_run = args["dry_run"].as_bool().unwrap_or(true);
751
752        let path = PathBuf::from(file_path);
753
754        // 重命名必须使用 LSP
755        let ext = path
756            .extension()
757            .and_then(|e| e.to_str())
758            .ok_or_else(|| anyhow::anyhow!("Unknown file extension"))?;
759
760        let language = crate::lsp::server::LanguageServerManager::get_language_from_extension(ext)
761            .ok_or_else(|| anyhow::anyhow!("No LSP server for extension: {}", ext))?;
762
763        let client = get_lsp_client().await;
764        let client = client.lock().await;
765
766        let root_path = path.parent().unwrap_or(Path::new("."));
767
768        if !client.is_connected(language).await {
769            client
770                .initialize(language, root_path)
771                .await
772                .map_err(|e| anyhow::anyhow!("Failed to initialize LSP server: {}", e))?;
773        }
774
775        client
776            .open_document(language, &path)
777            .await
778            .map_err(|e| anyhow::anyhow!("Failed to open document: {}", e))?;
779
780        let position = Position::new((line - 1) as u32, (column - 1) as u32);
781
782        let workspace_edit = client
783            .rename_symbol(language, &path, position, new_name)
784            .await
785            .map_err(|e| anyhow::anyhow!("LSP rename request failed: {}", e))?
786            .ok_or_else(|| anyhow::anyhow!("Cannot rename symbol at this location"))?;
787
788        // 格式化变更
789        let mut changes = Vec::new();
790
791        if let Some(ref change_map) = workspace_edit.changes {
792            for (uri, edits) in change_map {
793                let file = uri
794                    .strip_prefix("file://")
795                    .unwrap_or(uri.as_str())
796                    .strip_prefix('/')
797                    .unwrap_or(uri.as_str());
798                for edit in edits {
799                    changes.push(format!(
800                        "{}:{}:{} -> {}",
801                        file,
802                        edit.range.start.line + 1,
803                        edit.range.start.character + 1,
804                        edit.new_text
805                    ));
806                }
807            }
808        }
809
810        if dry_run {
811            Ok(format!(
812                "Preview: {} changes would be made to rename symbol to '{}':\n{}",
813                changes.len(),
814                new_name,
815                changes.join("\n")
816            ))
817        } else {
818            // 应用变更
819            self.apply_changes(&workspace_edit)?;
820            Ok(format!(
821                "Successfully renamed symbol to '{}' ({} changes applied)",
822                new_name,
823                changes.len()
824            ))
825        }
826    }
827}
828
829impl RenameSymbolTool {
830    fn apply_changes(&self, workspace_edit: &crate::lsp::WorkspaceEdit) -> Layer3Result<()> {
831        if let Some(changes) = &workspace_edit.changes {
832            for (uri, edits) in changes {
833                let file_path = uri
834                    .strip_prefix("file://")
835                    .unwrap_or(uri.as_str())
836                    .strip_prefix('/')
837                    .unwrap_or(uri.as_str());
838
839                let file_path = if cfg!(windows) {
840                    file_path.replace('/', "\\")
841                } else {
842                    file_path.to_string()
843                };
844
845                let content = fs::read_to_string(&file_path)?;
846                let mut lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
847
848                // 按位置倒序排列,以便从后往前修改
849                let mut sorted_edits = edits.clone();
850                sorted_edits.sort_by(|a, b| {
851                    b.range
852                        .start
853                        .line
854                        .cmp(&a.range.start.line)
855                        .then(b.range.start.character.cmp(&a.range.start.character))
856                });
857
858                for edit in sorted_edits {
859                    let line_idx = edit.range.start.line as usize;
860                    if line_idx < lines.len() {
861                        let line = &mut lines[line_idx];
862                        let start = edit.range.start.character as usize;
863                        let end = edit.range.end.character as usize;
864
865                        if end <= line.len() {
866                            line.replace_range(start..end, &edit.new_text);
867                        }
868                    }
869                }
870
871                fs::write(&file_path, lines.join("\n"))?;
872            }
873        }
874
875        Ok(())
876    }
877}
878
879// ============================================================================
880// 辅助函数
881// ============================================================================
882
883/// 从指定位置提取符号名
884fn extract_symbol_at_position(line: &str, column: usize) -> String {
885    let line_bytes = line.as_bytes();
886    if column == 0 || column > line.len() {
887        return String::new();
888    }
889
890    // 找到符号的起始位置
891    let start = line_bytes[..column - 1]
892        .iter()
893        .rposition(|&b| !is_identifier_char(b))
894        .map(|p| p + 1)
895        .unwrap_or(0);
896
897    // 找到符号的结束位置
898    let end = line_bytes[column - 1..]
899        .iter()
900        .position(|&b| !is_identifier_char(b))
901        .map(|p| column - 1 + p)
902        .unwrap_or(line.len());
903
904    line[start..end].to_string()
905}
906
907/// 检查是否是标识符字符
908fn is_identifier_char(b: u8) -> bool {
909    b.is_ascii_alphanumeric() || b == b'_' || b == b'-' || b == b':'
910}
911
912/// 检查是否是定义行
913fn is_definition_line(line: &str, symbol: &str) -> bool {
914    let patterns = [
915        Regex::new(&format!(r"\bfn\s+{}\s*\(", symbol)).ok(),
916        Regex::new(&format!(r"\bdef\s+{}\s*\(", symbol)).ok(),
917        Regex::new(&format!(r"\bclass\s+{}", symbol)).ok(),
918        Regex::new(&format!(r"\bstruct\s+{}", symbol)).ok(),
919        Regex::new(&format!(r"\benum\s+{}", symbol)).ok(),
920        Regex::new(&format!(r"\bimpl\s+{}", symbol)).ok(),
921        Regex::new(&format!(r"\btrait\s+{}", symbol)).ok(),
922        Regex::new(&format!(r"\binterface\s+{}", symbol)).ok(),
923        Regex::new(&format!(r"\btype\s+{}\s*=", symbol)).ok(),
924        Regex::new(&format!(r"\bconst\s+{}", symbol)).ok(),
925        Regex::new(&format!(r"\blet\s+{}\s*=", symbol)).ok(),
926        Regex::new(&format!(r"\bvar\s+{}\s*=", symbol)).ok(),
927        Regex::new(&format!(r"\bpublic\s+{}\s*\(", symbol)).ok(),
928        Regex::new(&format!(r"\bprivate\s+{}\s*\(", symbol)).ok(),
929    ];
930
931    patterns
932        .iter()
933        .any(|p| p.as_ref().is_some_and(|r| r.is_match(line)))
934}
935
936/// 根据文件类型获取定义匹配模式
937fn get_definition_patterns(file_ext: &str, symbol: &str) -> Vec<String> {
938    match file_ext {
939        "rs" => vec![
940            format!(r"\bfn\s+{}\s*[<(]", symbol),
941            format!(r"\bstruct\s+{}\s*[{{<\s]", symbol),
942            format!(r"\benum\s+{}\s*[{{<\s]", symbol),
943            format!(r"\btrait\s+{}\s*[{{<\s]", symbol),
944            format!(r"\bimpl\s+(?:\w+\s+for\s+)?{}|impl\s+{}", symbol, symbol),
945            format!(r"\btype\s+{}\s*=", symbol),
946            format!(r"\bconst\s+{}\s*:", symbol),
947            format!(r"\bstatic\s+{}\s*:", symbol),
948            format!(r"\bmacro_rules!\s+{}", symbol),
949        ],
950        "py" => vec![
951            format!(r"\bdef\s+{}\s*\(", symbol),
952            format!(r"\bclass\s+{}\s*[:\(]", symbol),
953            format!(r"\basync\s+def\s+{}\s*\(", symbol),
954        ],
955        "js" | "ts" | "tsx" => vec![
956            format!(r"\bfunction\s+{}\s*\(", symbol),
957            format!(r"\bclass\s+{}\s*[{{extends\s]", symbol),
958            format!(r"\bconst\s+{}\s*=", symbol),
959            format!(r"\blet\s+{}\s*=", symbol),
960            format!(r"\bvar\s+{}\s*=", symbol),
961            format!(r"\binterface\s+{}\s*[{{extends\s]", symbol),
962            format!(r"\btype\s+{}\s*=", symbol),
963            format!(r"\bexport\s+(?:default\s+)?(?:function|class)\s+{}", symbol),
964        ],
965        "java" | "kt" => vec![
966            format!(r"\bclass\s+{}\s*[{{extends\s]", symbol),
967            format!(r"\binterface\s+{}\s*[{{extends\s]", symbol),
968            format!(
969                r"\b(?:public|private|protected)\s+(?:static\s+)?(?:\w+\s+)?{}\s*\(",
970                symbol
971            ),
972            format!(r"\benum\s+{}\s*[{{]", symbol),
973        ],
974        "go" => vec![
975            format!(r"\bfunc\s+{}\s*\(", symbol),
976            format!(r"\bfunc\s+\(\w+\s*\*?\w*\)\s+{}\s*\(", symbol),
977            format!(r"\btype\s+{}\s+struct", symbol),
978            format!(r"\btype\s+{}\s+interface", symbol),
979            format!(r"\bvar\s+{}\s*=", symbol),
980            format!(r"\bconst\s+{}\s*=", symbol),
981        ],
982        "c" | "cpp" | "h" | "hpp" => vec![
983            format!(
984                r"\b(?:void|int|char|float|double|auto|struct|class)\s+{}\s*\(",
985                symbol
986            ),
987            format!(r"\bstruct\s+{}\s*[{{]", symbol),
988            format!(r"\bclass\s+{}\s*[{{:]", symbol),
989            format!(r"\btypedef\s+.*\s+{}\s*;", symbol),
990        ],
991        _ => vec![
992            format!(r"\b{}\s*[:=]", symbol),
993            format!(r"\b{}\s*\(", symbol),
994        ],
995    }
996}
997
998#[cfg(test)]
999mod tests {
1000    use super::*;
1001    use serde_json::json;
1002
1003    #[test]
1004    fn test_goto_definition_category() {
1005        let tool = GoToDefinitionTool;
1006        assert_eq!(tool.category(), ToolCategory::CodeAnalysis);
1007    }
1008
1009    #[test]
1010    fn test_find_references_category() {
1011        let tool = FindReferencesTool;
1012        assert_eq!(tool.category(), ToolCategory::CodeAnalysis);
1013    }
1014
1015    #[test]
1016    fn test_get_hover_category() {
1017        let tool = GetHoverTool;
1018        assert_eq!(tool.category(), ToolCategory::CodeAnalysis);
1019    }
1020
1021    #[test]
1022    fn test_rename_symbol_category() {
1023        let tool = RenameSymbolTool;
1024        assert_eq!(tool.category(), ToolCategory::CodeAnalysis);
1025        assert!(tool.requires_confirmation());
1026    }
1027
1028    #[test]
1029    fn test_extract_symbol() {
1030        let line = "let my_variable = 42;";
1031        let symbol = extract_symbol_at_position(line, 10);
1032        assert_eq!(symbol, "my_variable");
1033    }
1034
1035    #[test]
1036    fn test_extract_symbol_empty() {
1037        let line = "    = 42;";
1038        let symbol = extract_symbol_at_position(line, 5);
1039        assert_eq!(symbol, "");
1040    }
1041
1042    #[test]
1043    fn test_is_definition_line() {
1044        assert!(is_definition_line("fn my_func() {", "my_func"));
1045        assert!(is_definition_line("struct MyStruct {", "MyStruct"));
1046        assert!(!is_definition_line("my_func();", "my_func"));
1047    }
1048
1049    #[test]
1050    fn test_get_definition_patterns_rust() {
1051        let patterns = get_definition_patterns("rs", "foo");
1052        assert!(patterns.iter().any(|p| p.contains("fn")));
1053        assert!(patterns.iter().any(|p| p.contains("struct")));
1054    }
1055
1056    #[tokio::test]
1057    async fn test_goto_definition_missing_file() {
1058        let tool = GoToDefinitionTool;
1059        let result = tool
1060            .execute(json!({"file": "nonexistent.rs", "line": 1, "column": 1}))
1061            .await;
1062        // Should fail due to missing file (regex fallback also fails)
1063        assert!(result.is_err());
1064    }
1065
1066    #[tokio::test]
1067    async fn test_find_references_missing_file() {
1068        let tool = FindReferencesTool;
1069        let result = tool
1070            .execute(json!({"file": "nonexistent.rs", "line": 1, "column": 1}))
1071            .await;
1072        // Should fail due to missing file (regex fallback also fails)
1073        assert!(result.is_err());
1074    }
1075}