//! Get completion suggestions from LSP. //! //! Sends a `textDocument/completion` request to the connected language //! server for a given file position. use crate::tools::{Tool, ToolCtx}; use anyhow::Result; use serde_json::{json, Value}; use tracing::{info, error, instrument}; /// Tool that requests code completion suggestions from an LSP server. /// /// Flow: parse language/path/line/character → lock LSP manager → find client /// → send `textDocument/completion` → return pretty-printed JSON response. pub struct LspCompletion; impl Tool for LspCompletion { fn name(&self) -> &'static str { "lsp_completion" } fn description(&self) -> &'static str { "Get completion suggestions at a position" } fn parameters(&self) -> Value { json!({ "type": "object", "properties": { "language": { "type": "string", "description": "Language identifier" }, "path": { "type": "string", "description": "File path" }, "line": { "type": "integer", "description": "Line number (0-based)" }, "character": { "type": "integer", "description": "Character offset (0-based)" } }, "required": ["language", "path", "line", "character"] }) } #[instrument(skip(self, ctx, args))] fn run(&self, ctx: &ToolCtx, args: &Value) -> Result { let language = crate::tools::arg_str(args, "language")?; let path = crate::tools::arg_str(args, "path")?; let line = args.get("line").and_then(|v| v.as_i64()).unwrap_or(0); let character = args.get("character").and_then(|v| v.as_i64()).unwrap_or(0); info!(language, path, line, character, "LSP completion requested"); let manager = match ctx.lsp_manager.lock() { Ok(g) => g, Err(poisoned) => { error!("LSP manager mutex poisoned, recovering"); poisoned.into_inner() } }; if let Some(client) = manager.get_client(&language) { let result = client.send_request("textDocument/completion", &json!({ "textDocument": { "uri": format!("file://{}", path) }, "position": { "line": line, "character": character } }))?; Ok(serde_json::to_string_pretty(&result)?) } else { anyhow::bail!("no LSP client connected for '{language}'") } } }