1use async_trait::async_trait;
8use serde_json::{Value, json};
9
10use super::{AgentTool, AgentToolResult, LspAction, ToolContext, ToolError};
11
12pub struct LspTool;
17
18#[async_trait]
19impl AgentTool for LspTool {
20 fn name(&self) -> &str {
21 "lsp"
22 }
23
24 fn label(&self) -> &str {
25 "LSP"
26 }
27
28 fn essential(&self) -> bool {
29 false
30 }
31
32 fn description(&self) -> &str {
33 "Language Server Protocol operations: diagnostics, definition, references, \
34 hover, rename, symbols. Requires LSP enabled in settings."
35 }
36
37 fn parameters_schema(&self) -> Value {
38 json!({
39 "type": "object",
40 "properties": {
41 "action": {
42 "type": "string",
43 "enum": ["diagnostics", "definition", "references", "hover", "rename", "symbols", "status"]
44 },
45 "file": {"type": "string", "description": "File path"},
46 "line": {"type": "integer", "description": "1-based line number"},
47 "symbol": {"type": "string", "description": "Symbol name (optional, for disambiguation)"},
48 "new_name": {"type": "string", "description": "New name for rename"},
49 "apply": {"type": "boolean", "description": "Apply changes (vs preview)"},
50 "query": {"type": "string", "description": "Search query for symbols"}
51 },
52 "required": ["action"]
53 })
54 }
55
56 async fn execute(
57 &self,
58 _tool_call_id: &str,
59 params: Value,
60 _signal: Option<tokio::sync::oneshot::Receiver<()>>,
61 ctx: &ToolContext,
62 ) -> Result<AgentToolResult, ToolError> {
63 let provider = ctx
64 .lsp
65 .as_ref()
66 .ok_or("LSP not configured. Enable LSP in settings or install language servers.")?;
67
68 let action = parse_lsp_action(¶ms)?;
69
70 let result = provider.execute_action(&action).await?;
71
72 Ok(AgentToolResult::success(result))
73 }
74}
75
76fn parse_lsp_action(params: &Value) -> Result<LspAction, ToolError> {
78 let action = params
79 .get("action")
80 .and_then(|v| v.as_str())
81 .ok_or("Missing required parameter: action")?;
82
83 let file = params
84 .get("file")
85 .and_then(|v| v.as_str())
86 .unwrap_or("")
87 .to_string();
88
89 let line = params
90 .get("line")
91 .and_then(|v| v.as_u64())
92 .map(|n| n as u32)
93 .unwrap_or(1);
94
95 let symbol = params
96 .get("symbol")
97 .and_then(|v| v.as_str())
98 .map(String::from);
99
100 match action {
101 "diagnostics" => Ok(LspAction::Diagnostics { file }),
102 "definition" => Ok(LspAction::Definition { file, line, symbol }),
103 "references" => Ok(LspAction::References { file, line, symbol }),
104 "hover" => Ok(LspAction::Hover { file, line, symbol }),
105 "rename" => {
106 let new_name = params
107 .get("new_name")
108 .and_then(|v| v.as_str())
109 .ok_or("rename requires 'new_name'")?
110 .to_string();
111 let sym = symbol.clone().unwrap_or_default();
112 let apply = params
113 .get("apply")
114 .and_then(|v| v.as_bool())
115 .unwrap_or(false);
116 Ok(LspAction::Rename {
117 file,
118 line,
119 symbol: sym,
120 new_name,
121 apply,
122 })
123 }
124 "symbols" => {
125 let query = params
126 .get("query")
127 .and_then(|v| v.as_str())
128 .map(String::from);
129 Ok(LspAction::Symbols { file, query })
130 }
131 "status" => Ok(LspAction::Status),
132 other => Err(format!("Unknown LSP action: '{}'", other)),
133 }
134}