Skip to main content

aster/tools/
lsp.rs

1//! LSP Tool Implementation
2//!
3//! Provides Language Server Protocol integration for code intelligence features.
4//! Supports go-to-definition, find-references, hover, completion, and diagnostics.
5//!
6//! Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6
7
8use async_trait::async_trait;
9use serde::{Deserialize, Serialize};
10use std::future::Future;
11use std::path::{Path, PathBuf};
12use std::pin::Pin;
13use std::sync::Arc;
14
15use crate::tools::base::{PermissionCheckResult, Tool};
16use crate::tools::context::{ToolContext, ToolResult};
17use crate::tools::error::ToolError;
18
19/// Position in a text document (0-indexed)
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
21pub struct Position {
22    /// Line number (0-indexed)
23    pub line: u32,
24    /// Character offset (0-indexed)
25    pub character: u32,
26}
27
28impl Position {
29    /// Create a new position
30    pub fn new(line: u32, character: u32) -> Self {
31        Self { line, character }
32    }
33}
34
35/// Range in a text document
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37pub struct Range {
38    /// Start position
39    pub start: Position,
40    /// End position
41    pub end: Position,
42}
43
44impl Range {
45    /// Create a new range
46    pub fn new(start: Position, end: Position) -> Self {
47        Self { start, end }
48    }
49}
50
51/// Location in a document
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct Location {
54    /// File path
55    pub path: PathBuf,
56    /// Range in the file
57    pub range: Range,
58}
59
60impl Location {
61    /// Create a new location
62    pub fn new(path: impl Into<PathBuf>, range: Range) -> Self {
63        Self {
64            path: path.into(),
65            range,
66        }
67    }
68}
69
70/// Hover information
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct HoverInfo {
73    /// The hover contents (markdown or plain text)
74    pub contents: String,
75    /// Optional range the hover applies to
76    pub range: Option<Range>,
77}
78
79/// Completion item
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct CompletionItem {
82    /// The label of this completion item
83    pub label: String,
84    /// The kind of this completion item
85    pub kind: Option<CompletionItemKind>,
86    /// A human-readable string with additional information
87    pub detail: Option<String>,
88    /// A human-readable string that represents a doc-comment
89    pub documentation: Option<String>,
90    /// The text to insert when selecting this completion
91    pub insert_text: Option<String>,
92}
93
94/// Completion item kind
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
96#[serde(rename_all = "snake_case")]
97pub enum CompletionItemKind {
98    Text,
99    Method,
100    Function,
101    Constructor,
102    Field,
103    Variable,
104    Class,
105    Interface,
106    Module,
107    Property,
108    Unit,
109    Value,
110    Enum,
111    Keyword,
112    Snippet,
113    Color,
114    File,
115    Reference,
116    Folder,
117    EnumMember,
118    Constant,
119    Struct,
120    Event,
121    Operator,
122    TypeParameter,
123}
124
125/// Diagnostic severity
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
127#[serde(rename_all = "snake_case")]
128pub enum DiagnosticSeverity {
129    Error,
130    Warning,
131    Information,
132    Hint,
133}
134
135/// A diagnostic message
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct Diagnostic {
138    /// The range at which the message applies
139    pub range: Range,
140    /// The diagnostic's severity
141    pub severity: Option<DiagnosticSeverity>,
142    /// The diagnostic's code
143    pub code: Option<String>,
144    /// A human-readable string describing the source of this diagnostic
145    pub source: Option<String>,
146    /// The diagnostic's message
147    pub message: String,
148}
149
150/// LSP operation type
151#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
152#[serde(rename_all = "snake_case")]
153pub enum LspOperation {
154    /// Go to definition
155    Definition,
156    /// Find references
157    References,
158    /// Get hover information
159    Hover,
160    /// Get completions
161    Completion,
162    /// Get diagnostics
163    Diagnostics,
164    /// Get document symbols
165    DocumentSymbol,
166    /// Search workspace symbols
167    WorkspaceSymbol,
168    /// Go to implementation
169    Implementation,
170    /// Prepare call hierarchy
171    PrepareCallHierarchy,
172    /// Get incoming calls
173    IncomingCalls,
174    /// Get outgoing calls
175    OutgoingCalls,
176}
177
178/// Symbol kind for document/workspace symbols
179#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
180#[serde(rename_all = "snake_case")]
181pub enum SymbolKind {
182    File,
183    Module,
184    Namespace,
185    Package,
186    Class,
187    Method,
188    Property,
189    Field,
190    Constructor,
191    Enum,
192    Interface,
193    Function,
194    Variable,
195    Constant,
196    String,
197    Number,
198    Boolean,
199    Array,
200    Object,
201    Key,
202    Null,
203    EnumMember,
204    Struct,
205    Event,
206    Operator,
207    TypeParameter,
208}
209
210impl SymbolKind {
211    /// Convert from LSP symbol kind number
212    pub fn from_lsp(kind: u32) -> Self {
213        match kind {
214            1 => SymbolKind::File,
215            2 => SymbolKind::Module,
216            3 => SymbolKind::Namespace,
217            4 => SymbolKind::Package,
218            5 => SymbolKind::Class,
219            6 => SymbolKind::Method,
220            7 => SymbolKind::Property,
221            8 => SymbolKind::Field,
222            9 => SymbolKind::Constructor,
223            10 => SymbolKind::Enum,
224            11 => SymbolKind::Interface,
225            12 => SymbolKind::Function,
226            13 => SymbolKind::Variable,
227            14 => SymbolKind::Constant,
228            15 => SymbolKind::String,
229            16 => SymbolKind::Number,
230            17 => SymbolKind::Boolean,
231            18 => SymbolKind::Array,
232            19 => SymbolKind::Object,
233            20 => SymbolKind::Key,
234            21 => SymbolKind::Null,
235            22 => SymbolKind::EnumMember,
236            23 => SymbolKind::Struct,
237            24 => SymbolKind::Event,
238            25 => SymbolKind::Operator,
239            26 => SymbolKind::TypeParameter,
240            _ => SymbolKind::Variable,
241        }
242    }
243}
244
245/// Document symbol information
246#[derive(Debug, Clone, Serialize, Deserialize)]
247pub struct DocumentSymbol {
248    /// The name of this symbol
249    pub name: String,
250    /// More detail for this symbol (e.g., signature)
251    pub detail: Option<String>,
252    /// The kind of this symbol
253    pub kind: SymbolKind,
254    /// The range enclosing this symbol
255    pub range: Range,
256    /// The range that should be selected when navigating to this symbol
257    pub selection_range: Range,
258    /// Children of this symbol (e.g., methods of a class)
259    pub children: Vec<DocumentSymbol>,
260}
261
262/// Workspace symbol information
263#[derive(Debug, Clone, Serialize, Deserialize)]
264pub struct WorkspaceSymbol {
265    /// The name of this symbol
266    pub name: String,
267    /// The kind of this symbol
268    pub kind: SymbolKind,
269    /// The location of this symbol
270    pub location: Location,
271    /// Container name (e.g., class name for methods)
272    pub container_name: Option<String>,
273}
274
275/// Call hierarchy item
276#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct CallHierarchyItem {
278    /// The name of this item
279    pub name: String,
280    /// The kind of this item
281    pub kind: SymbolKind,
282    /// More detail for this item (e.g., signature)
283    pub detail: Option<String>,
284    /// The resource identifier of this item
285    pub uri: String,
286    /// The range enclosing this symbol
287    pub range: Range,
288    /// The range that should be selected when navigating to this item
289    pub selection_range: Range,
290}
291
292/// Incoming call hierarchy item
293#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct CallHierarchyIncomingCall {
295    /// The item that makes the call
296    pub from: CallHierarchyItem,
297    /// The ranges at which the calls appear
298    pub from_ranges: Vec<Range>,
299}
300
301/// Outgoing call hierarchy item
302#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct CallHierarchyOutgoingCall {
304    /// The item that is called
305    pub to: CallHierarchyItem,
306    /// The ranges at which the calls appear
307    pub from_ranges: Vec<Range>,
308}
309
310/// Result of an LSP operation
311#[derive(Debug, Clone, Serialize, Deserialize)]
312#[serde(tag = "type", rename_all = "snake_case")]
313pub enum LspResult {
314    /// Definition locations
315    Definition { locations: Vec<Location> },
316    /// Reference locations
317    References { locations: Vec<Location> },
318    /// Hover information
319    Hover { info: Option<HoverInfo> },
320    /// Completion items
321    Completion { items: Vec<CompletionItem> },
322    /// Diagnostics
323    Diagnostics { diagnostics: Vec<Diagnostic> },
324    /// Document symbols
325    DocumentSymbol { symbols: Vec<DocumentSymbol> },
326    /// Workspace symbols
327    WorkspaceSymbol { symbols: Vec<WorkspaceSymbol> },
328    /// Implementation locations
329    Implementation { locations: Vec<Location> },
330    /// Call hierarchy items
331    CallHierarchy { items: Vec<CallHierarchyItem> },
332    /// Incoming calls
333    IncomingCalls {
334        calls: Vec<CallHierarchyIncomingCall>,
335    },
336    /// Outgoing calls
337    OutgoingCalls {
338        calls: Vec<CallHierarchyOutgoingCall>,
339    },
340}
341
342/// Callback type for LSP operations
343///
344/// The callback receives the operation type, file path, and position,
345/// and returns the LSP result.
346pub type LspCallback = Arc<
347    dyn Fn(
348            LspOperation,
349            PathBuf,
350            Option<Position>,
351        ) -> Pin<Box<dyn Future<Output = Result<LspResult, String>> + Send>>
352        + Send
353        + Sync,
354>;
355
356/// LSP Tool for code intelligence
357///
358/// Provides access to Language Server Protocol features:
359/// - Go to definition
360/// - Find references
361/// - Hover information
362/// - Code completion
363/// - Diagnostics
364///
365/// Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6
366pub struct LspTool {
367    /// Callback for LSP operations
368    callback: Option<LspCallback>,
369    /// Supported file extensions (empty means all)
370    supported_extensions: Vec<String>,
371}
372
373impl Default for LspTool {
374    fn default() -> Self {
375        Self::new()
376    }
377}
378
379impl LspTool {
380    /// Create a new LspTool without a callback
381    ///
382    /// Note: Without a callback, the tool will return an error when executed.
383    /// Use `with_callback` to set up the LSP handler.
384    pub fn new() -> Self {
385        Self {
386            callback: None,
387            supported_extensions: Vec::new(),
388        }
389    }
390
391    /// Set the callback for LSP operations
392    pub fn with_callback(mut self, callback: LspCallback) -> Self {
393        self.callback = Some(callback);
394        self
395    }
396
397    /// Set supported file extensions
398    pub fn with_supported_extensions(mut self, extensions: Vec<String>) -> Self {
399        self.supported_extensions = extensions;
400        self
401    }
402
403    /// Check if a callback is configured
404    pub fn has_callback(&self) -> bool {
405        self.callback.is_some()
406    }
407
408    /// Check if a file extension is supported
409    pub fn is_extension_supported(&self, path: &Path) -> bool {
410        if self.supported_extensions.is_empty() {
411            return true;
412        }
413        path.extension()
414            .and_then(|ext| ext.to_str())
415            .map(|ext| {
416                self.supported_extensions
417                    .iter()
418                    .any(|supported| supported.eq_ignore_ascii_case(ext))
419            })
420            .unwrap_or(false)
421    }
422
423    /// Execute an LSP operation
424    pub async fn execute_operation(
425        &self,
426        operation: LspOperation,
427        path: &Path,
428        position: Option<Position>,
429    ) -> Result<LspResult, ToolError> {
430        let callback = self
431            .callback
432            .as_ref()
433            .ok_or_else(|| ToolError::execution_failed("LSP server is not available"))?;
434
435        callback(operation, path.to_path_buf(), position)
436            .await
437            .map_err(ToolError::execution_failed)
438    }
439
440    /// Go to definition
441    ///
442    /// Requirements: 7.1
443    pub async fn goto_definition(
444        &self,
445        path: &Path,
446        position: Position,
447    ) -> Result<Vec<Location>, ToolError> {
448        match self
449            .execute_operation(LspOperation::Definition, path, Some(position))
450            .await?
451        {
452            LspResult::Definition { locations } => Ok(locations),
453            _ => Err(ToolError::execution_failed("Unexpected LSP result type")),
454        }
455    }
456
457    /// Find references
458    ///
459    /// Requirements: 7.2
460    pub async fn find_references(
461        &self,
462        path: &Path,
463        position: Position,
464    ) -> Result<Vec<Location>, ToolError> {
465        match self
466            .execute_operation(LspOperation::References, path, Some(position))
467            .await?
468        {
469            LspResult::References { locations } => Ok(locations),
470            _ => Err(ToolError::execution_failed("Unexpected LSP result type")),
471        }
472    }
473
474    /// Get hover information
475    ///
476    /// Requirements: 7.3
477    pub async fn hover(
478        &self,
479        path: &Path,
480        position: Position,
481    ) -> Result<Option<HoverInfo>, ToolError> {
482        match self
483            .execute_operation(LspOperation::Hover, path, Some(position))
484            .await?
485        {
486            LspResult::Hover { info } => Ok(info),
487            _ => Err(ToolError::execution_failed("Unexpected LSP result type")),
488        }
489    }
490
491    /// Get completions
492    ///
493    /// Requirements: 7.4
494    pub async fn completions(
495        &self,
496        path: &Path,
497        position: Position,
498    ) -> Result<Vec<CompletionItem>, ToolError> {
499        match self
500            .execute_operation(LspOperation::Completion, path, Some(position))
501            .await?
502        {
503            LspResult::Completion { items } => Ok(items),
504            _ => Err(ToolError::execution_failed("Unexpected LSP result type")),
505        }
506    }
507
508    /// Get diagnostics
509    ///
510    /// Requirements: 7.5
511    pub async fn diagnostics(&self, path: &Path) -> Result<Vec<Diagnostic>, ToolError> {
512        match self
513            .execute_operation(LspOperation::Diagnostics, path, None)
514            .await?
515        {
516            LspResult::Diagnostics { diagnostics } => Ok(diagnostics),
517            _ => Err(ToolError::execution_failed("Unexpected LSP result type")),
518        }
519    }
520
521    /// Get document symbols
522    ///
523    /// Requirements: 7.6
524    pub async fn document_symbols(&self, path: &Path) -> Result<Vec<DocumentSymbol>, ToolError> {
525        match self
526            .execute_operation(LspOperation::DocumentSymbol, path, None)
527            .await?
528        {
529            LspResult::DocumentSymbol { symbols } => Ok(symbols),
530            _ => Err(ToolError::execution_failed("Unexpected LSP result type")),
531        }
532    }
533
534    /// Search workspace symbols
535    ///
536    /// Requirements: 7.7
537    pub async fn workspace_symbols(&self, path: &Path) -> Result<Vec<WorkspaceSymbol>, ToolError> {
538        match self
539            .execute_operation(LspOperation::WorkspaceSymbol, path, None)
540            .await?
541        {
542            LspResult::WorkspaceSymbol { symbols } => Ok(symbols),
543            _ => Err(ToolError::execution_failed("Unexpected LSP result type")),
544        }
545    }
546
547    /// Go to implementation
548    ///
549    /// Requirements: 7.8
550    pub async fn goto_implementation(
551        &self,
552        path: &Path,
553        position: Position,
554    ) -> Result<Vec<Location>, ToolError> {
555        match self
556            .execute_operation(LspOperation::Implementation, path, Some(position))
557            .await?
558        {
559            LspResult::Implementation { locations } => Ok(locations),
560            _ => Err(ToolError::execution_failed("Unexpected LSP result type")),
561        }
562    }
563
564    /// Prepare call hierarchy
565    ///
566    /// Requirements: 7.9
567    pub async fn prepare_call_hierarchy(
568        &self,
569        path: &Path,
570        position: Position,
571    ) -> Result<Vec<CallHierarchyItem>, ToolError> {
572        match self
573            .execute_operation(LspOperation::PrepareCallHierarchy, path, Some(position))
574            .await?
575        {
576            LspResult::CallHierarchy { items } => Ok(items),
577            _ => Err(ToolError::execution_failed("Unexpected LSP result type")),
578        }
579    }
580
581    /// Get incoming calls
582    ///
583    /// Requirements: 7.10
584    pub async fn incoming_calls(
585        &self,
586        path: &Path,
587        position: Position,
588    ) -> Result<Vec<CallHierarchyIncomingCall>, ToolError> {
589        match self
590            .execute_operation(LspOperation::IncomingCalls, path, Some(position))
591            .await?
592        {
593            LspResult::IncomingCalls { calls } => Ok(calls),
594            _ => Err(ToolError::execution_failed("Unexpected LSP result type")),
595        }
596    }
597
598    /// Get outgoing calls
599    ///
600    /// Requirements: 7.11
601    pub async fn outgoing_calls(
602        &self,
603        path: &Path,
604        position: Position,
605    ) -> Result<Vec<CallHierarchyOutgoingCall>, ToolError> {
606        match self
607            .execute_operation(LspOperation::OutgoingCalls, path, Some(position))
608            .await?
609        {
610            LspResult::OutgoingCalls { calls } => Ok(calls),
611            _ => Err(ToolError::execution_failed("Unexpected LSP result type")),
612        }
613    }
614}
615
616#[async_trait]
617impl Tool for LspTool {
618    fn name(&self) -> &str {
619        "lsp"
620    }
621
622    fn description(&self) -> &str {
623        "Access Language Server Protocol features for code intelligence. \
624         Supports go-to-definition, find-references, hover information, \
625         code completion, and diagnostics retrieval."
626    }
627
628    fn input_schema(&self) -> serde_json::Value {
629        serde_json::json!({
630            "type": "object",
631            "properties": {
632                "operation": {
633                    "type": "string",
634                    "enum": [
635                        "definition", "references", "hover", "completion", "diagnostics",
636                        "document_symbol", "workspace_symbol", "implementation",
637                        "prepare_call_hierarchy", "incoming_calls", "outgoing_calls"
638                    ],
639                    "description": "The LSP operation to perform"
640                },
641                "path": {
642                    "type": "string",
643                    "description": "Path to the file"
644                },
645                "line": {
646                    "type": "integer",
647                    "description": "Line number (0-indexed, required for position-based operations)"
648                },
649                "character": {
650                    "type": "integer",
651                    "description": "Character offset (0-indexed, required for position-based operations)"
652                }
653            },
654            "required": ["operation", "path"]
655        })
656    }
657
658    async fn execute(
659        &self,
660        params: serde_json::Value,
661        context: &ToolContext,
662    ) -> Result<ToolResult, ToolError> {
663        // Parse operation
664        let operation_str = params
665            .get("operation")
666            .and_then(|v| v.as_str())
667            .ok_or_else(|| ToolError::invalid_params("Missing required parameter: operation"))?;
668
669        let operation = match operation_str {
670            "definition" => LspOperation::Definition,
671            "references" => LspOperation::References,
672            "hover" => LspOperation::Hover,
673            "completion" => LspOperation::Completion,
674            "diagnostics" => LspOperation::Diagnostics,
675            "document_symbol" => LspOperation::DocumentSymbol,
676            "workspace_symbol" => LspOperation::WorkspaceSymbol,
677            "implementation" => LspOperation::Implementation,
678            "prepare_call_hierarchy" => LspOperation::PrepareCallHierarchy,
679            "incoming_calls" => LspOperation::IncomingCalls,
680            "outgoing_calls" => LspOperation::OutgoingCalls,
681            _ => return Err(ToolError::invalid_params(format!(
682                "Invalid operation: {}. Must be one of: definition, references, hover, completion, diagnostics, \
683                 document_symbol, workspace_symbol, implementation, prepare_call_hierarchy, incoming_calls, outgoing_calls",
684                operation_str
685            ))),
686        };
687
688        // Parse path
689        let path_str = params
690            .get("path")
691            .and_then(|v| v.as_str())
692            .ok_or_else(|| ToolError::invalid_params("Missing required parameter: path"))?;
693
694        let path = if Path::new(path_str).is_absolute() {
695            PathBuf::from(path_str)
696        } else {
697            context.working_directory.join(path_str)
698        };
699
700        // Check file extension support
701        if !self.is_extension_supported(&path) {
702            return Err(ToolError::invalid_params(format!(
703                "File extension not supported: {}",
704                path.display()
705            )));
706        }
707
708        // Parse position (required for most operations)
709        let needs_position = !matches!(
710            operation,
711            LspOperation::Diagnostics
712                | LspOperation::DocumentSymbol
713                | LspOperation::WorkspaceSymbol
714        );
715
716        let position = if needs_position {
717            let line = params.get("line").and_then(|v| v.as_u64()).ok_or_else(|| {
718                ToolError::invalid_params(
719                    "Missing required parameter: line (required for this operation)",
720                )
721            })? as u32;
722
723            let character = params
724                .get("character")
725                .and_then(|v| v.as_u64())
726                .ok_or_else(|| {
727                    ToolError::invalid_params(
728                        "Missing required parameter: character (required for this operation)",
729                    )
730                })? as u32;
731
732            Some(Position::new(line, character))
733        } else {
734            None
735        };
736
737        // Execute the operation
738        let result = self.execute_operation(operation, &path, position).await?;
739
740        // Format the output
741        let output = format_lsp_result(&result, &path);
742
743        Ok(ToolResult::success(output)
744            .with_metadata("operation", serde_json::json!(operation_str))
745            .with_metadata("path", serde_json::json!(path.display().to_string()))
746            .with_metadata("result", serde_json::to_value(&result).unwrap_or_default()))
747    }
748
749    async fn check_permissions(
750        &self,
751        _params: &serde_json::Value,
752        _context: &ToolContext,
753    ) -> PermissionCheckResult {
754        // LSP operations are read-only, so they're always allowed
755        PermissionCheckResult::allow()
756    }
757}
758
759/// Format LSP result for human-readable output
760fn format_lsp_result(result: &LspResult, query_path: &Path) -> String {
761    match result {
762        LspResult::Definition { locations } => {
763            if locations.is_empty() {
764                "No definition found".to_string()
765            } else {
766                let mut output = format!("Found {} definition(s):\n", locations.len());
767                for loc in locations {
768                    output.push_str(&format!(
769                        "  {}:{}:{}\n",
770                        loc.path.display(),
771                        loc.range.start.line + 1,
772                        loc.range.start.character + 1
773                    ));
774                }
775                output
776            }
777        }
778        LspResult::References { locations } => {
779            if locations.is_empty() {
780                "No references found".to_string()
781            } else {
782                let mut output = format!("Found {} reference(s):\n", locations.len());
783                for loc in locations {
784                    output.push_str(&format!(
785                        "  {}:{}:{}\n",
786                        loc.path.display(),
787                        loc.range.start.line + 1,
788                        loc.range.start.character + 1
789                    ));
790                }
791                output
792            }
793        }
794        LspResult::Hover { info } => match info {
795            Some(hover) => {
796                let mut output = "Hover information:\n".to_string();
797                output.push_str(&hover.contents);
798                output
799            }
800            None => "No hover information available".to_string(),
801        },
802        LspResult::Completion { items } => {
803            if items.is_empty() {
804                "No completions available".to_string()
805            } else {
806                let mut output = format!("Found {} completion(s):\n", items.len());
807                for item in items.iter().take(20) {
808                    let kind_str = item.kind.map(|k| format!(" ({:?})", k)).unwrap_or_default();
809                    output.push_str(&format!("  {}{}\n", item.label, kind_str));
810                    if let Some(detail) = &item.detail {
811                        output.push_str(&format!("    {}\n", detail));
812                    }
813                }
814                if items.len() > 20 {
815                    output.push_str(&format!("  ... and {} more\n", items.len() - 20));
816                }
817                output
818            }
819        }
820        LspResult::Diagnostics { diagnostics } => {
821            if diagnostics.is_empty() {
822                format!("No diagnostics for {}", query_path.display())
823            } else {
824                let mut output = format!(
825                    "Found {} diagnostic(s) in {}:\n",
826                    diagnostics.len(),
827                    query_path.display()
828                );
829                for diag in diagnostics {
830                    let severity = diag
831                        .severity
832                        .map(|s| format!("{:?}", s))
833                        .unwrap_or_else(|| "Unknown".to_string());
834                    output.push_str(&format!(
835                        "  [{}] {}:{}: {}\n",
836                        severity,
837                        diag.range.start.line + 1,
838                        diag.range.start.character + 1,
839                        diag.message
840                    ));
841                }
842                output
843            }
844        }
845        LspResult::DocumentSymbol { symbols } => {
846            if symbols.is_empty() {
847                "No symbols found in document".to_string()
848            } else {
849                let count = count_document_symbols(symbols);
850                let mut output = format!("Found {} symbol(s) in document:\n", count);
851                format_document_symbols(&mut output, symbols, 0);
852                output
853            }
854        }
855        LspResult::WorkspaceSymbol { symbols } => {
856            if symbols.is_empty() {
857                "No symbols found in workspace".to_string()
858            } else {
859                let mut output = format!("Found {} symbol(s) in workspace:\n", symbols.len());
860                for sym in symbols {
861                    let container = sym
862                        .container_name
863                        .as_ref()
864                        .map(|c| format!(" in {}", c))
865                        .unwrap_or_default();
866                    output.push_str(&format!(
867                        "  {} ({:?}) - {}:{}:{}{}\n",
868                        sym.name,
869                        sym.kind,
870                        sym.location.path.display(),
871                        sym.location.range.start.line + 1,
872                        sym.location.range.start.character + 1,
873                        container
874                    ));
875                }
876                output
877            }
878        }
879        LspResult::Implementation { locations } => {
880            if locations.is_empty() {
881                "No implementation found".to_string()
882            } else {
883                let mut output = format!("Found {} implementation(s):\n", locations.len());
884                for loc in locations {
885                    output.push_str(&format!(
886                        "  {}:{}:{}\n",
887                        loc.path.display(),
888                        loc.range.start.line + 1,
889                        loc.range.start.character + 1
890                    ));
891                }
892                output
893            }
894        }
895        LspResult::CallHierarchy { items } => {
896            if items.is_empty() {
897                "No call hierarchy item found at this position".to_string()
898            } else {
899                let mut output = format!("Found {} call hierarchy item(s):\n", items.len());
900                for item in items {
901                    let detail = item
902                        .detail
903                        .as_ref()
904                        .map(|d| format!(" [{}]", d))
905                        .unwrap_or_default();
906                    output.push_str(&format!(
907                        "  {} ({:?}) - {}:{}:{}{}\n",
908                        item.name,
909                        item.kind,
910                        item.uri,
911                        item.range.start.line + 1,
912                        item.range.start.character + 1,
913                        detail
914                    ));
915                }
916                output
917            }
918        }
919        LspResult::IncomingCalls { calls } => {
920            if calls.is_empty() {
921                "No incoming calls found (nothing calls this function)".to_string()
922            } else {
923                let mut output = format!("Found {} caller(s):\n", calls.len());
924                for call in calls {
925                    let ranges: Vec<String> = call
926                        .from_ranges
927                        .iter()
928                        .map(|r| format!("{}:{}", r.start.line + 1, r.start.character + 1))
929                        .collect();
930                    let ranges_str = if ranges.is_empty() {
931                        String::new()
932                    } else {
933                        format!(" [calls at: {}]", ranges.join(", "))
934                    };
935                    output.push_str(&format!(
936                        "  {} ({:?}) - {}:{}:{}{}\n",
937                        call.from.name,
938                        call.from.kind,
939                        call.from.uri,
940                        call.from.range.start.line + 1,
941                        call.from.range.start.character + 1,
942                        ranges_str
943                    ));
944                }
945                output
946            }
947        }
948        LspResult::OutgoingCalls { calls } => {
949            if calls.is_empty() {
950                "No outgoing calls found (this function calls nothing)".to_string()
951            } else {
952                let mut output = format!("Found {} callee(s):\n", calls.len());
953                for call in calls {
954                    let ranges: Vec<String> = call
955                        .from_ranges
956                        .iter()
957                        .map(|r| format!("{}:{}", r.start.line + 1, r.start.character + 1))
958                        .collect();
959                    let ranges_str = if ranges.is_empty() {
960                        String::new()
961                    } else {
962                        format!(" [called from: {}]", ranges.join(", "))
963                    };
964                    output.push_str(&format!(
965                        "  {} ({:?}) - {}:{}:{}{}\n",
966                        call.to.name,
967                        call.to.kind,
968                        call.to.uri,
969                        call.to.range.start.line + 1,
970                        call.to.range.start.character + 1,
971                        ranges_str
972                    ));
973                }
974                output
975            }
976        }
977    }
978}
979
980/// Count document symbols recursively
981fn count_document_symbols(symbols: &[DocumentSymbol]) -> usize {
982    let mut count = symbols.len();
983    for sym in symbols {
984        count += count_document_symbols(&sym.children);
985    }
986    count
987}
988
989/// Format document symbols with indentation
990fn format_document_symbols(output: &mut String, symbols: &[DocumentSymbol], indent: usize) {
991    let prefix = "  ".repeat(indent + 1);
992    for sym in symbols {
993        let detail = sym
994            .detail
995            .as_ref()
996            .map(|d| format!(" - {}", d))
997            .unwrap_or_default();
998        output.push_str(&format!(
999            "{}{} ({:?}) - Line {}{}\n",
1000            prefix,
1001            sym.name,
1002            sym.kind,
1003            sym.range.start.line + 1,
1004            detail
1005        ));
1006        if !sym.children.is_empty() {
1007            format_document_symbols(output, &sym.children, indent + 1);
1008        }
1009    }
1010}
1011
1012#[cfg(test)]
1013mod tests {
1014    use super::*;
1015
1016    /// Create a mock callback that returns definition locations
1017    fn mock_definition_callback(locations: Vec<Location>) -> LspCallback {
1018        Arc::new(move |op, _path, _pos| {
1019            let locs = locations.clone();
1020            Box::pin(async move {
1021                match op {
1022                    LspOperation::Definition => Ok(LspResult::Definition { locations: locs }),
1023                    _ => Err("Unexpected operation".to_string()),
1024                }
1025            })
1026        })
1027    }
1028
1029    /// Create a mock callback that returns references
1030    fn mock_references_callback(locations: Vec<Location>) -> LspCallback {
1031        Arc::new(move |op, _path, _pos| {
1032            let locs = locations.clone();
1033            Box::pin(async move {
1034                match op {
1035                    LspOperation::References => Ok(LspResult::References { locations: locs }),
1036                    _ => Err("Unexpected operation".to_string()),
1037                }
1038            })
1039        })
1040    }
1041
1042    /// Create a mock callback that returns hover info
1043    fn mock_hover_callback(info: Option<HoverInfo>) -> LspCallback {
1044        Arc::new(move |op, _path, _pos| {
1045            let hover_info = info.clone();
1046            Box::pin(async move {
1047                match op {
1048                    LspOperation::Hover => Ok(LspResult::Hover { info: hover_info }),
1049                    _ => Err("Unexpected operation".to_string()),
1050                }
1051            })
1052        })
1053    }
1054
1055    /// Create a mock callback that returns completions
1056    fn mock_completion_callback(items: Vec<CompletionItem>) -> LspCallback {
1057        Arc::new(move |op, _path, _pos| {
1058            let completion_items = items.clone();
1059            Box::pin(async move {
1060                match op {
1061                    LspOperation::Completion => Ok(LspResult::Completion {
1062                        items: completion_items,
1063                    }),
1064                    _ => Err("Unexpected operation".to_string()),
1065                }
1066            })
1067        })
1068    }
1069
1070    /// Create a mock callback that returns diagnostics
1071    fn mock_diagnostics_callback(diagnostics: Vec<Diagnostic>) -> LspCallback {
1072        Arc::new(move |op, _path, _pos| {
1073            let diags = diagnostics.clone();
1074            Box::pin(async move {
1075                match op {
1076                    LspOperation::Diagnostics => Ok(LspResult::Diagnostics { diagnostics: diags }),
1077                    _ => Err("Unexpected operation".to_string()),
1078                }
1079            })
1080        })
1081    }
1082
1083    /// Create a mock callback that returns an error
1084    fn mock_error_callback(error: &str) -> LspCallback {
1085        let err = error.to_string();
1086        Arc::new(move |_op, _path, _pos| {
1087            let e = err.clone();
1088            Box::pin(async move { Err(e) })
1089        })
1090    }
1091
1092    /// Create a mock callback that handles all operations
1093    fn mock_all_operations_callback() -> LspCallback {
1094        Arc::new(|op, path, pos| {
1095            Box::pin(async move {
1096                match op {
1097                    LspOperation::Definition => Ok(LspResult::Definition {
1098                        locations: vec![Location::new(
1099                            path,
1100                            Range::new(Position::new(10, 5), Position::new(10, 15)),
1101                        )],
1102                    }),
1103                    LspOperation::References => Ok(LspResult::References {
1104                        locations: vec![
1105                            Location::new(
1106                                path.clone(),
1107                                Range::new(Position::new(10, 5), Position::new(10, 15)),
1108                            ),
1109                            Location::new(
1110                                path,
1111                                Range::new(Position::new(20, 10), Position::new(20, 20)),
1112                            ),
1113                        ],
1114                    }),
1115                    LspOperation::Hover => Ok(LspResult::Hover {
1116                        info: Some(HoverInfo {
1117                            contents: "fn example() -> String".to_string(),
1118                            range: pos
1119                                .map(|p| Range::new(p, Position::new(p.line, p.character + 7))),
1120                        }),
1121                    }),
1122                    LspOperation::Completion => Ok(LspResult::Completion {
1123                        items: vec![CompletionItem {
1124                            label: "example".to_string(),
1125                            kind: Some(CompletionItemKind::Function),
1126                            detail: Some("fn example() -> String".to_string()),
1127                            documentation: Some("An example function".to_string()),
1128                            insert_text: Some("example()".to_string()),
1129                        }],
1130                    }),
1131                    LspOperation::Diagnostics => Ok(LspResult::Diagnostics {
1132                        diagnostics: vec![Diagnostic {
1133                            range: Range::new(Position::new(5, 0), Position::new(5, 10)),
1134                            severity: Some(DiagnosticSeverity::Error),
1135                            code: Some("E0001".to_string()),
1136                            source: Some("rustc".to_string()),
1137                            message: "unused variable".to_string(),
1138                        }],
1139                    }),
1140                    LspOperation::DocumentSymbol => Ok(LspResult::DocumentSymbol {
1141                        symbols: vec![DocumentSymbol {
1142                            name: "main".to_string(),
1143                            detail: Some("fn main()".to_string()),
1144                            kind: SymbolKind::Function,
1145                            range: Range::new(Position::new(0, 0), Position::new(10, 1)),
1146                            selection_range: Range::new(Position::new(0, 3), Position::new(0, 7)),
1147                            children: vec![],
1148                        }],
1149                    }),
1150                    LspOperation::WorkspaceSymbol => Ok(LspResult::WorkspaceSymbol {
1151                        symbols: vec![WorkspaceSymbol {
1152                            name: "MyStruct".to_string(),
1153                            kind: SymbolKind::Struct,
1154                            location: Location::new(
1155                                path,
1156                                Range::new(Position::new(5, 0), Position::new(15, 1)),
1157                            ),
1158                            container_name: Some("module".to_string()),
1159                        }],
1160                    }),
1161                    LspOperation::Implementation => Ok(LspResult::Implementation {
1162                        locations: vec![Location::new(
1163                            path,
1164                            Range::new(Position::new(20, 0), Position::new(30, 1)),
1165                        )],
1166                    }),
1167                    LspOperation::PrepareCallHierarchy => Ok(LspResult::CallHierarchy {
1168                        items: vec![CallHierarchyItem {
1169                            name: "process".to_string(),
1170                            kind: SymbolKind::Function,
1171                            detail: Some("fn process()".to_string()),
1172                            uri: path.to_string_lossy().to_string(),
1173                            range: Range::new(Position::new(10, 0), Position::new(20, 1)),
1174                            selection_range: Range::new(
1175                                Position::new(10, 3),
1176                                Position::new(10, 10),
1177                            ),
1178                        }],
1179                    }),
1180                    LspOperation::IncomingCalls => Ok(LspResult::IncomingCalls {
1181                        calls: vec![CallHierarchyIncomingCall {
1182                            from: CallHierarchyItem {
1183                                name: "caller".to_string(),
1184                                kind: SymbolKind::Function,
1185                                detail: None,
1186                                uri: path.to_string_lossy().to_string(),
1187                                range: Range::new(Position::new(30, 0), Position::new(40, 1)),
1188                                selection_range: Range::new(
1189                                    Position::new(30, 3),
1190                                    Position::new(30, 9),
1191                                ),
1192                            },
1193                            from_ranges: vec![Range::new(
1194                                Position::new(35, 4),
1195                                Position::new(35, 11),
1196                            )],
1197                        }],
1198                    }),
1199                    LspOperation::OutgoingCalls => Ok(LspResult::OutgoingCalls {
1200                        calls: vec![CallHierarchyOutgoingCall {
1201                            to: CallHierarchyItem {
1202                                name: "callee".to_string(),
1203                                kind: SymbolKind::Function,
1204                                detail: None,
1205                                uri: path.to_string_lossy().to_string(),
1206                                range: Range::new(Position::new(50, 0), Position::new(60, 1)),
1207                                selection_range: Range::new(
1208                                    Position::new(50, 3),
1209                                    Position::new(50, 9),
1210                                ),
1211                            },
1212                            from_ranges: vec![Range::new(
1213                                Position::new(15, 4),
1214                                Position::new(15, 10),
1215                            )],
1216                        }],
1217                    }),
1218                }
1219            })
1220        })
1221    }
1222
1223    #[test]
1224    fn test_position_new() {
1225        let pos = Position::new(10, 5);
1226        assert_eq!(pos.line, 10);
1227        assert_eq!(pos.character, 5);
1228    }
1229
1230    #[test]
1231    fn test_range_new() {
1232        let range = Range::new(Position::new(1, 0), Position::new(1, 10));
1233        assert_eq!(range.start.line, 1);
1234        assert_eq!(range.end.character, 10);
1235    }
1236
1237    #[test]
1238    fn test_location_new() {
1239        let loc = Location::new(
1240            "/path/to/file.rs",
1241            Range::new(Position::new(0, 0), Position::new(0, 5)),
1242        );
1243        assert_eq!(loc.path, PathBuf::from("/path/to/file.rs"));
1244    }
1245
1246    #[test]
1247    fn test_lsp_tool_new() {
1248        let tool = LspTool::new();
1249        assert!(!tool.has_callback());
1250        assert!(tool.supported_extensions.is_empty());
1251    }
1252
1253    #[test]
1254    fn test_lsp_tool_with_callback() {
1255        let callback = mock_definition_callback(vec![]);
1256        let tool = LspTool::new().with_callback(callback);
1257        assert!(tool.has_callback());
1258    }
1259
1260    #[test]
1261    fn test_lsp_tool_with_supported_extensions() {
1262        let tool =
1263            LspTool::new().with_supported_extensions(vec!["rs".to_string(), "py".to_string()]);
1264        assert_eq!(tool.supported_extensions.len(), 2);
1265    }
1266
1267    #[test]
1268    fn test_is_extension_supported_empty() {
1269        let tool = LspTool::new();
1270        assert!(tool.is_extension_supported(Path::new("file.rs")));
1271        assert!(tool.is_extension_supported(Path::new("file.py")));
1272        assert!(tool.is_extension_supported(Path::new("file")));
1273    }
1274
1275    #[test]
1276    fn test_is_extension_supported_filtered() {
1277        let tool =
1278            LspTool::new().with_supported_extensions(vec!["rs".to_string(), "py".to_string()]);
1279        assert!(tool.is_extension_supported(Path::new("file.rs")));
1280        assert!(tool.is_extension_supported(Path::new("file.RS")));
1281        assert!(tool.is_extension_supported(Path::new("file.py")));
1282        assert!(!tool.is_extension_supported(Path::new("file.js")));
1283        assert!(!tool.is_extension_supported(Path::new("file")));
1284    }
1285
1286    #[tokio::test]
1287    async fn test_goto_definition_success() {
1288        let locations = vec![Location::new(
1289            "/path/to/file.rs",
1290            Range::new(Position::new(10, 0), Position::new(10, 10)),
1291        )];
1292        let callback = mock_definition_callback(locations.clone());
1293        let tool = LspTool::new().with_callback(callback);
1294
1295        let result = tool
1296            .goto_definition(Path::new("/path/to/file.rs"), Position::new(5, 10))
1297            .await
1298            .unwrap();
1299
1300        assert_eq!(result.len(), 1);
1301        assert_eq!(result[0].path, PathBuf::from("/path/to/file.rs"));
1302    }
1303
1304    #[tokio::test]
1305    async fn test_goto_definition_empty() {
1306        let callback = mock_definition_callback(vec![]);
1307        let tool = LspTool::new().with_callback(callback);
1308
1309        let result = tool
1310            .goto_definition(Path::new("/path/to/file.rs"), Position::new(5, 10))
1311            .await
1312            .unwrap();
1313
1314        assert!(result.is_empty());
1315    }
1316
1317    #[tokio::test]
1318    async fn test_find_references_success() {
1319        let locations = vec![
1320            Location::new(
1321                "/path/to/file.rs",
1322                Range::new(Position::new(10, 0), Position::new(10, 10)),
1323            ),
1324            Location::new(
1325                "/path/to/other.rs",
1326                Range::new(Position::new(20, 5), Position::new(20, 15)),
1327            ),
1328        ];
1329        let callback = mock_references_callback(locations);
1330        let tool = LspTool::new().with_callback(callback);
1331
1332        let result = tool
1333            .find_references(Path::new("/path/to/file.rs"), Position::new(5, 10))
1334            .await
1335            .unwrap();
1336
1337        assert_eq!(result.len(), 2);
1338    }
1339
1340    #[tokio::test]
1341    async fn test_hover_success() {
1342        let info = HoverInfo {
1343            contents: "fn example() -> String".to_string(),
1344            range: Some(Range::new(Position::new(5, 0), Position::new(5, 7))),
1345        };
1346        let callback = mock_hover_callback(Some(info));
1347        let tool = LspTool::new().with_callback(callback);
1348
1349        let result = tool
1350            .hover(Path::new("/path/to/file.rs"), Position::new(5, 3))
1351            .await
1352            .unwrap();
1353
1354        assert!(result.is_some());
1355        assert!(result.unwrap().contents.contains("example"));
1356    }
1357
1358    #[tokio::test]
1359    async fn test_hover_none() {
1360        let callback = mock_hover_callback(None);
1361        let tool = LspTool::new().with_callback(callback);
1362
1363        let result = tool
1364            .hover(Path::new("/path/to/file.rs"), Position::new(5, 3))
1365            .await
1366            .unwrap();
1367
1368        assert!(result.is_none());
1369    }
1370
1371    #[tokio::test]
1372    async fn test_completions_success() {
1373        let items = vec![
1374            CompletionItem {
1375                label: "example".to_string(),
1376                kind: Some(CompletionItemKind::Function),
1377                detail: Some("fn example()".to_string()),
1378                documentation: None,
1379                insert_text: Some("example()".to_string()),
1380            },
1381            CompletionItem {
1382                label: "example2".to_string(),
1383                kind: Some(CompletionItemKind::Variable),
1384                detail: None,
1385                documentation: None,
1386                insert_text: None,
1387            },
1388        ];
1389        let callback = mock_completion_callback(items);
1390        let tool = LspTool::new().with_callback(callback);
1391
1392        let result = tool
1393            .completions(Path::new("/path/to/file.rs"), Position::new(5, 3))
1394            .await
1395            .unwrap();
1396
1397        assert_eq!(result.len(), 2);
1398        assert_eq!(result[0].label, "example");
1399    }
1400
1401    #[tokio::test]
1402    async fn test_diagnostics_success() {
1403        let diagnostics = vec![Diagnostic {
1404            range: Range::new(Position::new(5, 0), Position::new(5, 10)),
1405            severity: Some(DiagnosticSeverity::Error),
1406            code: Some("E0001".to_string()),
1407            source: Some("rustc".to_string()),
1408            message: "unused variable".to_string(),
1409        }];
1410        let callback = mock_diagnostics_callback(diagnostics);
1411        let tool = LspTool::new().with_callback(callback);
1412
1413        let result = tool
1414            .diagnostics(Path::new("/path/to/file.rs"))
1415            .await
1416            .unwrap();
1417
1418        assert_eq!(result.len(), 1);
1419        assert_eq!(result[0].message, "unused variable");
1420    }
1421
1422    #[tokio::test]
1423    async fn test_lsp_without_callback() {
1424        let tool = LspTool::new();
1425        let result = tool
1426            .goto_definition(Path::new("/path/to/file.rs"), Position::new(5, 10))
1427            .await;
1428
1429        assert!(result.is_err());
1430        assert!(matches!(result.unwrap_err(), ToolError::ExecutionFailed(_)));
1431    }
1432
1433    #[tokio::test]
1434    async fn test_lsp_callback_error() {
1435        let callback = mock_error_callback("LSP server crashed");
1436        let tool = LspTool::new().with_callback(callback);
1437
1438        let result = tool
1439            .goto_definition(Path::new("/path/to/file.rs"), Position::new(5, 10))
1440            .await;
1441
1442        assert!(result.is_err());
1443        let err = result.unwrap_err();
1444        assert!(matches!(err, ToolError::ExecutionFailed(_)));
1445    }
1446
1447    #[tokio::test]
1448    async fn test_lsp_tool_trait_name() {
1449        let tool = LspTool::new();
1450        assert_eq!(tool.name(), "lsp");
1451    }
1452
1453    #[tokio::test]
1454    async fn test_lsp_tool_trait_description() {
1455        let tool = LspTool::new();
1456        assert!(tool.description().contains("Language Server Protocol"));
1457    }
1458
1459    #[tokio::test]
1460    async fn test_lsp_tool_trait_input_schema() {
1461        let tool = LspTool::new();
1462        let schema = tool.input_schema();
1463
1464        assert_eq!(schema["type"], "object");
1465        assert!(schema["properties"]["operation"].is_object());
1466        assert!(schema["properties"]["path"].is_object());
1467        assert!(schema["properties"]["line"].is_object());
1468        assert!(schema["properties"]["character"].is_object());
1469    }
1470
1471    #[tokio::test]
1472    async fn test_lsp_tool_execute_definition() {
1473        let callback = mock_all_operations_callback();
1474        let tool = LspTool::new().with_callback(callback);
1475        let context = ToolContext::new(PathBuf::from("/tmp"));
1476
1477        let params = serde_json::json!({
1478            "operation": "definition",
1479            "path": "file.rs",
1480            "line": 5,
1481            "character": 10
1482        });
1483
1484        let result = tool.execute(params, &context).await.unwrap();
1485        assert!(result.is_success());
1486        assert!(result.output.unwrap().contains("definition"));
1487    }
1488
1489    #[tokio::test]
1490    async fn test_lsp_tool_execute_references() {
1491        let callback = mock_all_operations_callback();
1492        let tool = LspTool::new().with_callback(callback);
1493        let context = ToolContext::new(PathBuf::from("/tmp"));
1494
1495        let params = serde_json::json!({
1496            "operation": "references",
1497            "path": "file.rs",
1498            "line": 5,
1499            "character": 10
1500        });
1501
1502        let result = tool.execute(params, &context).await.unwrap();
1503        assert!(result.is_success());
1504        assert!(result.output.unwrap().contains("reference"));
1505    }
1506
1507    #[tokio::test]
1508    async fn test_lsp_tool_execute_hover() {
1509        let callback = mock_all_operations_callback();
1510        let tool = LspTool::new().with_callback(callback);
1511        let context = ToolContext::new(PathBuf::from("/tmp"));
1512
1513        let params = serde_json::json!({
1514            "operation": "hover",
1515            "path": "file.rs",
1516            "line": 5,
1517            "character": 10
1518        });
1519
1520        let result = tool.execute(params, &context).await.unwrap();
1521        assert!(result.is_success());
1522        assert!(result.output.unwrap().contains("Hover"));
1523    }
1524
1525    #[tokio::test]
1526    async fn test_lsp_tool_execute_completion() {
1527        let callback = mock_all_operations_callback();
1528        let tool = LspTool::new().with_callback(callback);
1529        let context = ToolContext::new(PathBuf::from("/tmp"));
1530
1531        let params = serde_json::json!({
1532            "operation": "completion",
1533            "path": "file.rs",
1534            "line": 5,
1535            "character": 10
1536        });
1537
1538        let result = tool.execute(params, &context).await.unwrap();
1539        assert!(result.is_success());
1540        assert!(result.output.unwrap().contains("completion"));
1541    }
1542
1543    #[tokio::test]
1544    async fn test_lsp_tool_execute_diagnostics() {
1545        let callback = mock_all_operations_callback();
1546        let tool = LspTool::new().with_callback(callback);
1547        let context = ToolContext::new(PathBuf::from("/tmp"));
1548
1549        let params = serde_json::json!({
1550            "operation": "diagnostics",
1551            "path": "file.rs"
1552        });
1553
1554        let result = tool.execute(params, &context).await.unwrap();
1555        assert!(result.is_success());
1556        assert!(result.output.unwrap().contains("diagnostic"));
1557    }
1558
1559    #[tokio::test]
1560    async fn test_lsp_tool_execute_missing_operation() {
1561        let callback = mock_all_operations_callback();
1562        let tool = LspTool::new().with_callback(callback);
1563        let context = ToolContext::new(PathBuf::from("/tmp"));
1564
1565        let params = serde_json::json!({
1566            "path": "file.rs"
1567        });
1568
1569        let result = tool.execute(params, &context).await;
1570        assert!(result.is_err());
1571        assert!(matches!(result.unwrap_err(), ToolError::InvalidParams(_)));
1572    }
1573
1574    #[tokio::test]
1575    async fn test_lsp_tool_execute_invalid_operation() {
1576        let callback = mock_all_operations_callback();
1577        let tool = LspTool::new().with_callback(callback);
1578        let context = ToolContext::new(PathBuf::from("/tmp"));
1579
1580        let params = serde_json::json!({
1581            "operation": "invalid",
1582            "path": "file.rs"
1583        });
1584
1585        let result = tool.execute(params, &context).await;
1586        assert!(result.is_err());
1587        assert!(matches!(result.unwrap_err(), ToolError::InvalidParams(_)));
1588    }
1589
1590    #[tokio::test]
1591    async fn test_lsp_tool_execute_missing_position() {
1592        let callback = mock_all_operations_callback();
1593        let tool = LspTool::new().with_callback(callback);
1594        let context = ToolContext::new(PathBuf::from("/tmp"));
1595
1596        let params = serde_json::json!({
1597            "operation": "definition",
1598            "path": "file.rs"
1599        });
1600
1601        let result = tool.execute(params, &context).await;
1602        assert!(result.is_err());
1603        assert!(matches!(result.unwrap_err(), ToolError::InvalidParams(_)));
1604    }
1605
1606    #[tokio::test]
1607    async fn test_lsp_tool_execute_unsupported_extension() {
1608        let callback = mock_all_operations_callback();
1609        let tool = LspTool::new()
1610            .with_callback(callback)
1611            .with_supported_extensions(vec!["rs".to_string()]);
1612        let context = ToolContext::new(PathBuf::from("/tmp"));
1613
1614        let params = serde_json::json!({
1615            "operation": "diagnostics",
1616            "path": "file.py"
1617        });
1618
1619        let result = tool.execute(params, &context).await;
1620        assert!(result.is_err());
1621        assert!(matches!(result.unwrap_err(), ToolError::InvalidParams(_)));
1622    }
1623
1624    #[tokio::test]
1625    async fn test_lsp_tool_check_permissions() {
1626        let tool = LspTool::new();
1627        let context = ToolContext::new(PathBuf::from("/tmp"));
1628        let params = serde_json::json!({"operation": "definition", "path": "file.rs"});
1629
1630        let result = tool.check_permissions(&params, &context).await;
1631        assert!(result.is_allowed());
1632    }
1633
1634    #[test]
1635    fn test_format_lsp_result_definition_empty() {
1636        let result = LspResult::Definition { locations: vec![] };
1637        let output = format_lsp_result(&result, Path::new("file.rs"));
1638        assert!(output.contains("No definition found"));
1639    }
1640
1641    #[test]
1642    fn test_format_lsp_result_definition_found() {
1643        let result = LspResult::Definition {
1644            locations: vec![Location::new(
1645                "/path/to/file.rs",
1646                Range::new(Position::new(10, 5), Position::new(10, 15)),
1647            )],
1648        };
1649        let output = format_lsp_result(&result, Path::new("file.rs"));
1650        assert!(output.contains("1 definition"));
1651        assert!(output.contains("11:6")); // 1-indexed
1652    }
1653
1654    #[test]
1655    fn test_format_lsp_result_hover_none() {
1656        let result = LspResult::Hover { info: None };
1657        let output = format_lsp_result(&result, Path::new("file.rs"));
1658        assert!(output.contains("No hover information"));
1659    }
1660
1661    #[test]
1662    fn test_format_lsp_result_diagnostics_empty() {
1663        let result = LspResult::Diagnostics {
1664            diagnostics: vec![],
1665        };
1666        let output = format_lsp_result(&result, Path::new("file.rs"));
1667        assert!(output.contains("No diagnostics"));
1668    }
1669
1670    #[test]
1671    fn test_completion_item_kind_serialization() {
1672        let kind = CompletionItemKind::Function;
1673        let json = serde_json::to_string(&kind).unwrap();
1674        assert_eq!(json, "\"function\"");
1675    }
1676
1677    #[test]
1678    fn test_diagnostic_severity_serialization() {
1679        let severity = DiagnosticSeverity::Error;
1680        let json = serde_json::to_string(&severity).unwrap();
1681        assert_eq!(json, "\"error\"");
1682    }
1683
1684    #[test]
1685    fn test_lsp_operation_serialization() {
1686        let op = LspOperation::Definition;
1687        let json = serde_json::to_string(&op).unwrap();
1688        assert_eq!(json, "\"definition\"");
1689    }
1690
1691    #[test]
1692    fn test_lsp_result_serialization() {
1693        let result = LspResult::Definition {
1694            locations: vec![Location::new(
1695                "/path/to/file.rs",
1696                Range::new(Position::new(0, 0), Position::new(0, 5)),
1697            )],
1698        };
1699        let json = serde_json::to_string(&result).unwrap();
1700        assert!(json.contains("definition"));
1701        assert!(json.contains("locations"));
1702    }
1703
1704    // Tests for new operations
1705
1706    #[test]
1707    fn test_symbol_kind_from_lsp() {
1708        assert_eq!(SymbolKind::from_lsp(1), SymbolKind::File);
1709        assert_eq!(SymbolKind::from_lsp(5), SymbolKind::Class);
1710        assert_eq!(SymbolKind::from_lsp(12), SymbolKind::Function);
1711        assert_eq!(SymbolKind::from_lsp(999), SymbolKind::Variable);
1712    }
1713
1714    #[tokio::test]
1715    async fn test_document_symbols_success() {
1716        let callback = mock_all_operations_callback();
1717        let tool = LspTool::new().with_callback(callback);
1718
1719        let result = tool
1720            .document_symbols(Path::new("/path/to/file.rs"))
1721            .await
1722            .unwrap();
1723        assert_eq!(result.len(), 1);
1724        assert_eq!(result[0].name, "main");
1725    }
1726
1727    #[tokio::test]
1728    async fn test_workspace_symbols_success() {
1729        let callback = mock_all_operations_callback();
1730        let tool = LspTool::new().with_callback(callback);
1731
1732        let result = tool
1733            .workspace_symbols(Path::new("/path/to/file.rs"))
1734            .await
1735            .unwrap();
1736        assert_eq!(result.len(), 1);
1737        assert_eq!(result[0].name, "MyStruct");
1738    }
1739
1740    #[tokio::test]
1741    async fn test_goto_implementation_success() {
1742        let callback = mock_all_operations_callback();
1743        let tool = LspTool::new().with_callback(callback);
1744
1745        let result = tool
1746            .goto_implementation(Path::new("/path/to/file.rs"), Position::new(5, 10))
1747            .await
1748            .unwrap();
1749        assert_eq!(result.len(), 1);
1750    }
1751
1752    #[tokio::test]
1753    async fn test_prepare_call_hierarchy_success() {
1754        let callback = mock_all_operations_callback();
1755        let tool = LspTool::new().with_callback(callback);
1756
1757        let result = tool
1758            .prepare_call_hierarchy(Path::new("/path/to/file.rs"), Position::new(5, 10))
1759            .await
1760            .unwrap();
1761        assert_eq!(result.len(), 1);
1762        assert_eq!(result[0].name, "process");
1763    }
1764
1765    #[tokio::test]
1766    async fn test_incoming_calls_success() {
1767        let callback = mock_all_operations_callback();
1768        let tool = LspTool::new().with_callback(callback);
1769
1770        let result = tool
1771            .incoming_calls(Path::new("/path/to/file.rs"), Position::new(5, 10))
1772            .await
1773            .unwrap();
1774        assert_eq!(result.len(), 1);
1775        assert_eq!(result[0].from.name, "caller");
1776    }
1777
1778    #[tokio::test]
1779    async fn test_outgoing_calls_success() {
1780        let callback = mock_all_operations_callback();
1781        let tool = LspTool::new().with_callback(callback);
1782
1783        let result = tool
1784            .outgoing_calls(Path::new("/path/to/file.rs"), Position::new(5, 10))
1785            .await
1786            .unwrap();
1787        assert_eq!(result.len(), 1);
1788        assert_eq!(result[0].to.name, "callee");
1789    }
1790
1791    #[tokio::test]
1792    async fn test_lsp_tool_execute_document_symbol() {
1793        let callback = mock_all_operations_callback();
1794        let tool = LspTool::new().with_callback(callback);
1795        let context = ToolContext::new(PathBuf::from("/tmp"));
1796
1797        let params = serde_json::json!({
1798            "operation": "document_symbol",
1799            "path": "file.rs"
1800        });
1801
1802        let result = tool.execute(params, &context).await.unwrap();
1803        assert!(result.is_success());
1804        assert!(result.output.unwrap().contains("symbol"));
1805    }
1806
1807    #[tokio::test]
1808    async fn test_lsp_tool_execute_workspace_symbol() {
1809        let callback = mock_all_operations_callback();
1810        let tool = LspTool::new().with_callback(callback);
1811        let context = ToolContext::new(PathBuf::from("/tmp"));
1812
1813        let params = serde_json::json!({
1814            "operation": "workspace_symbol",
1815            "path": "file.rs"
1816        });
1817
1818        let result = tool.execute(params, &context).await.unwrap();
1819        assert!(result.is_success());
1820        assert!(result.output.unwrap().contains("symbol"));
1821    }
1822
1823    #[tokio::test]
1824    async fn test_lsp_tool_execute_implementation() {
1825        let callback = mock_all_operations_callback();
1826        let tool = LspTool::new().with_callback(callback);
1827        let context = ToolContext::new(PathBuf::from("/tmp"));
1828
1829        let params = serde_json::json!({
1830            "operation": "implementation",
1831            "path": "file.rs",
1832            "line": 5,
1833            "character": 10
1834        });
1835
1836        let result = tool.execute(params, &context).await.unwrap();
1837        assert!(result.is_success());
1838        assert!(result.output.unwrap().contains("implementation"));
1839    }
1840
1841    #[tokio::test]
1842    async fn test_lsp_tool_execute_prepare_call_hierarchy() {
1843        let callback = mock_all_operations_callback();
1844        let tool = LspTool::new().with_callback(callback);
1845        let context = ToolContext::new(PathBuf::from("/tmp"));
1846
1847        let params = serde_json::json!({
1848            "operation": "prepare_call_hierarchy",
1849            "path": "file.rs",
1850            "line": 5,
1851            "character": 10
1852        });
1853
1854        let result = tool.execute(params, &context).await.unwrap();
1855        assert!(result.is_success());
1856        assert!(result.output.unwrap().contains("call hierarchy"));
1857    }
1858
1859    #[tokio::test]
1860    async fn test_lsp_tool_execute_incoming_calls() {
1861        let callback = mock_all_operations_callback();
1862        let tool = LspTool::new().with_callback(callback);
1863        let context = ToolContext::new(PathBuf::from("/tmp"));
1864
1865        let params = serde_json::json!({
1866            "operation": "incoming_calls",
1867            "path": "file.rs",
1868            "line": 5,
1869            "character": 10
1870        });
1871
1872        let result = tool.execute(params, &context).await.unwrap();
1873        assert!(result.is_success());
1874        assert!(result.output.unwrap().contains("caller"));
1875    }
1876
1877    #[tokio::test]
1878    async fn test_lsp_tool_execute_outgoing_calls() {
1879        let callback = mock_all_operations_callback();
1880        let tool = LspTool::new().with_callback(callback);
1881        let context = ToolContext::new(PathBuf::from("/tmp"));
1882
1883        let params = serde_json::json!({
1884            "operation": "outgoing_calls",
1885            "path": "file.rs",
1886            "line": 5,
1887            "character": 10
1888        });
1889
1890        let result = tool.execute(params, &context).await.unwrap();
1891        assert!(result.is_success());
1892        assert!(result.output.unwrap().contains("callee"));
1893    }
1894
1895    #[test]
1896    fn test_format_lsp_result_document_symbol_empty() {
1897        let result = LspResult::DocumentSymbol { symbols: vec![] };
1898        let output = format_lsp_result(&result, Path::new("file.rs"));
1899        assert!(output.contains("No symbols found"));
1900    }
1901
1902    #[test]
1903    fn test_format_lsp_result_document_symbol_found() {
1904        let result = LspResult::DocumentSymbol {
1905            symbols: vec![DocumentSymbol {
1906                name: "test_fn".to_string(),
1907                detail: Some("fn test_fn()".to_string()),
1908                kind: SymbolKind::Function,
1909                range: Range::new(Position::new(0, 0), Position::new(5, 1)),
1910                selection_range: Range::new(Position::new(0, 3), Position::new(0, 10)),
1911                children: vec![],
1912            }],
1913        };
1914        let output = format_lsp_result(&result, Path::new("file.rs"));
1915        assert!(output.contains("1 symbol"));
1916        assert!(output.contains("test_fn"));
1917    }
1918
1919    #[test]
1920    fn test_format_lsp_result_workspace_symbol_empty() {
1921        let result = LspResult::WorkspaceSymbol { symbols: vec![] };
1922        let output = format_lsp_result(&result, Path::new("file.rs"));
1923        assert!(output.contains("No symbols found"));
1924    }
1925
1926    #[test]
1927    fn test_format_lsp_result_implementation_empty() {
1928        let result = LspResult::Implementation { locations: vec![] };
1929        let output = format_lsp_result(&result, Path::new("file.rs"));
1930        assert!(output.contains("No implementation found"));
1931    }
1932
1933    #[test]
1934    fn test_format_lsp_result_call_hierarchy_empty() {
1935        let result = LspResult::CallHierarchy { items: vec![] };
1936        let output = format_lsp_result(&result, Path::new("file.rs"));
1937        assert!(output.contains("No call hierarchy item"));
1938    }
1939
1940    #[test]
1941    fn test_format_lsp_result_incoming_calls_empty() {
1942        let result = LspResult::IncomingCalls { calls: vec![] };
1943        let output = format_lsp_result(&result, Path::new("file.rs"));
1944        assert!(output.contains("No incoming calls"));
1945    }
1946
1947    #[test]
1948    fn test_format_lsp_result_outgoing_calls_empty() {
1949        let result = LspResult::OutgoingCalls { calls: vec![] };
1950        let output = format_lsp_result(&result, Path::new("file.rs"));
1951        assert!(output.contains("No outgoing calls"));
1952    }
1953
1954    #[test]
1955    fn test_symbol_kind_serialization() {
1956        let kind = SymbolKind::Function;
1957        let json = serde_json::to_string(&kind).unwrap();
1958        assert_eq!(json, "\"function\"");
1959    }
1960
1961    #[test]
1962    fn test_document_symbol_serialization() {
1963        let sym = DocumentSymbol {
1964            name: "test".to_string(),
1965            detail: None,
1966            kind: SymbolKind::Function,
1967            range: Range::new(Position::new(0, 0), Position::new(1, 0)),
1968            selection_range: Range::new(Position::new(0, 0), Position::new(0, 4)),
1969            children: vec![],
1970        };
1971        let json = serde_json::to_string(&sym).unwrap();
1972        assert!(json.contains("test"));
1973        assert!(json.contains("function"));
1974    }
1975
1976    #[test]
1977    fn test_call_hierarchy_item_serialization() {
1978        let item = CallHierarchyItem {
1979            name: "process".to_string(),
1980            kind: SymbolKind::Function,
1981            detail: Some("fn process()".to_string()),
1982            uri: "file:///path/to/file.rs".to_string(),
1983            range: Range::new(Position::new(0, 0), Position::new(10, 1)),
1984            selection_range: Range::new(Position::new(0, 3), Position::new(0, 10)),
1985        };
1986        let json = serde_json::to_string(&item).unwrap();
1987        assert!(json.contains("process"));
1988        assert!(json.contains("uri"));
1989    }
1990
1991    #[test]
1992    fn test_new_lsp_operation_serialization() {
1993        assert_eq!(
1994            serde_json::to_string(&LspOperation::DocumentSymbol).unwrap(),
1995            "\"document_symbol\""
1996        );
1997        assert_eq!(
1998            serde_json::to_string(&LspOperation::WorkspaceSymbol).unwrap(),
1999            "\"workspace_symbol\""
2000        );
2001        assert_eq!(
2002            serde_json::to_string(&LspOperation::Implementation).unwrap(),
2003            "\"implementation\""
2004        );
2005        assert_eq!(
2006            serde_json::to_string(&LspOperation::PrepareCallHierarchy).unwrap(),
2007            "\"prepare_call_hierarchy\""
2008        );
2009        assert_eq!(
2010            serde_json::to_string(&LspOperation::IncomingCalls).unwrap(),
2011            "\"incoming_calls\""
2012        );
2013        assert_eq!(
2014            serde_json::to_string(&LspOperation::OutgoingCalls).unwrap(),
2015            "\"outgoing_calls\""
2016        );
2017    }
2018}