coderlib 0.1.0

A Rust library for AI-powered code assistance and agentic system
Documentation
//! Hover information tool using LSP

use async_trait::async_trait;
use serde::Deserialize;
use std::path::PathBuf;
use std::sync::Arc;

use crate::integration::HostIntegration;
use crate::lsp::{LspService, Hover, Position};
use crate::tools::{Tool, ToolError, ToolResponse, Permission};

/// Tool for getting hover information from LSP servers
pub struct HoverTool {
    lsp_service: Option<Arc<dyn LspService>>,
}

/// Parameters for the hover tool
#[derive(Debug, Deserialize)]
struct HoverParams {
    /// File path to get hover information for
    file_path: PathBuf,
    /// Line number (0-based)
    line: u32,
    /// Column number (0-based)
    character: u32,
}

impl HoverTool {
    /// Create a new hover tool
    pub fn new() -> Self {
        Self {
            lsp_service: None,
        }
    }
    
    /// Set the LSP service for this tool
    pub fn set_lsp_service(&mut self, lsp_service: Arc<dyn LspService>) {
        self.lsp_service = Some(lsp_service);
    }
    
    /// Format hover information for display
    fn format_hover(&self, hover: &Hover, file_path: &std::path::Path, position: &Position) -> String {
        let mut result = String::new();
        
        result.push_str(&format!("Hover information for {}:{}:{}\n\n", 
                                file_path.display(), position.line + 1, position.character + 1));
        
        // Add the hover contents
        result.push_str(&hover.contents);
        
        // Add range information if available
        if let Some(range) = &hover.range {
            result.push_str(&format!("\n\nRange: {}:{} to {}:{}", 
                                   range.start.line + 1, range.start.character + 1,
                                   range.end.line + 1, range.end.character + 1));
        }
        
        result
    }
}

impl Default for HoverTool {
    fn default() -> Self {
        Self::new()
    }
}

#[async_trait]
impl Tool for HoverTool {
    async fn execute(
        &self,
        parameters: serde_json::Value,
        _host: &dyn HostIntegration,
    ) -> Result<ToolResponse, ToolError> {
        let params: HoverParams = serde_json::from_value(parameters)
            .map_err(|e| ToolError::InvalidParameters(format!("Invalid parameters: {}", e)))?;
        
        // Check if LSP service is available
        let lsp_service = self.lsp_service.as_ref()
            .ok_or_else(|| ToolError::ExecutionFailed("LSP service not available".to_string()))?;
        
        // Check if the file is supported by LSP
        if !lsp_service.supports_file(&params.file_path) {
            return Ok(ToolResponse::success(format!(
                "LSP support not available for file: {}",
                params.file_path.display()
            )));
        }
        
        let position = Position {
            line: params.line,
            character: params.character,
        };
        
        // Get hover information from LSP
        let hover = lsp_service.get_hover(&params.file_path, position).await
            .map_err(|e| ToolError::ExecutionFailed(format!("Failed to get hover information: {}", e)))?;
        
        let formatted = if let Some(hover) = hover {
            self.format_hover(&hover, &params.file_path, &position)
        } else {
            format!("No hover information available at {}:{}:{}", 
                   params.file_path.display(), params.line + 1, params.character + 1)
        };
        
        Ok(ToolResponse::success(formatted))
    }
    
    fn name(&self) -> &str {
        "hover"
    }
    
    fn description(&self) -> &str {
        "Get hover information (documentation, type info) at a specific position using LSP"
    }
    
    fn requires_permission(&self) -> Permission {
        Permission::None // Hover information is read-only
    }
    
    fn parameter_schema(&self) -> serde_json::Value {
        serde_json::json!({
            "type": "object",
            "properties": {
                "file_path": {
                    "type": "string",
                    "description": "Path to the file to get hover information for"
                },
                "line": {
                    "type": "integer",
                    "description": "Line number (0-based) where to get hover information",
                    "minimum": 0
                },
                "character": {
                    "type": "integer", 
                    "description": "Column number (0-based) where to get hover information",
                    "minimum": 0
                }
            },
            "required": ["file_path", "line", "character"]
        })
    }
    
    fn clone_box(&self) -> Box<dyn Tool> {
        Box::new(Self {
            lsp_service: self.lsp_service.clone(),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::lsp::types::Range;
    
    #[test]
    fn test_tool_creation() {
        let tool = HoverTool::new();
        assert_eq!(tool.name(), "hover");
        assert!(!tool.description().is_empty());
    }
    
    #[test]
    fn test_parameter_schema() {
        let tool = HoverTool::new();
        let schema = tool.parameter_schema();
        
        assert!(schema.get("type").is_some());
        assert!(schema.get("properties").is_some());
        assert!(schema.get("required").is_some());
    }
    
    #[test]
    fn test_format_hover_basic() {
        let tool = HoverTool::new();
        let hover = Hover {
            contents: "fn main() -> ()\nThe main function".to_string(),
            range: None,
        };
        let position = Position { line: 0, character: 3 };
        let file_path = std::path::Path::new("main.rs");
        
        let formatted = tool.format_hover(&hover, file_path, &position);
        
        assert!(formatted.contains("main.rs:1:4"));
        assert!(formatted.contains("fn main() -> ()"));
        assert!(formatted.contains("The main function"));
    }
    
    #[test]
    fn test_format_hover_with_range() {
        let tool = HoverTool::new();
        let hover = Hover {
            contents: "struct String".to_string(),
            range: Some(Range {
                start: Position { line: 5, character: 10 },
                end: Position { line: 5, character: 16 },
            }),
        };
        let position = Position { line: 5, character: 12 };
        let file_path = std::path::Path::new("lib.rs");
        
        let formatted = tool.format_hover(&hover, file_path, &position);
        
        assert!(formatted.contains("lib.rs:6:13"));
        assert!(formatted.contains("struct String"));
        assert!(formatted.contains("Range: 6:11 to 6:17"));
    }
}