1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
21pub struct Position {
22 pub line: u32,
24 pub character: u32,
26}
27
28impl Position {
29 pub fn new(line: u32, character: u32) -> Self {
31 Self { line, character }
32 }
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37pub struct Range {
38 pub start: Position,
40 pub end: Position,
42}
43
44impl Range {
45 pub fn new(start: Position, end: Position) -> Self {
47 Self { start, end }
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct Location {
54 pub path: PathBuf,
56 pub range: Range,
58}
59
60impl Location {
61 pub fn new(path: impl Into<PathBuf>, range: Range) -> Self {
63 Self {
64 path: path.into(),
65 range,
66 }
67 }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct HoverInfo {
73 pub contents: String,
75 pub range: Option<Range>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct CompletionItem {
82 pub label: String,
84 pub kind: Option<CompletionItemKind>,
86 pub detail: Option<String>,
88 pub documentation: Option<String>,
90 pub insert_text: Option<String>,
92}
93
94#[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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct Diagnostic {
138 pub range: Range,
140 pub severity: Option<DiagnosticSeverity>,
142 pub code: Option<String>,
144 pub source: Option<String>,
146 pub message: String,
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
152#[serde(rename_all = "snake_case")]
153pub enum LspOperation {
154 Definition,
156 References,
158 Hover,
160 Completion,
162 Diagnostics,
164 DocumentSymbol,
166 WorkspaceSymbol,
168 Implementation,
170 PrepareCallHierarchy,
172 IncomingCalls,
174 OutgoingCalls,
176}
177
178#[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 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#[derive(Debug, Clone, Serialize, Deserialize)]
247pub struct DocumentSymbol {
248 pub name: String,
250 pub detail: Option<String>,
252 pub kind: SymbolKind,
254 pub range: Range,
256 pub selection_range: Range,
258 pub children: Vec<DocumentSymbol>,
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize)]
264pub struct WorkspaceSymbol {
265 pub name: String,
267 pub kind: SymbolKind,
269 pub location: Location,
271 pub container_name: Option<String>,
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct CallHierarchyItem {
278 pub name: String,
280 pub kind: SymbolKind,
282 pub detail: Option<String>,
284 pub uri: String,
286 pub range: Range,
288 pub selection_range: Range,
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct CallHierarchyIncomingCall {
295 pub from: CallHierarchyItem,
297 pub from_ranges: Vec<Range>,
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct CallHierarchyOutgoingCall {
304 pub to: CallHierarchyItem,
306 pub from_ranges: Vec<Range>,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
312#[serde(tag = "type", rename_all = "snake_case")]
313pub enum LspResult {
314 Definition { locations: Vec<Location> },
316 References { locations: Vec<Location> },
318 Hover { info: Option<HoverInfo> },
320 Completion { items: Vec<CompletionItem> },
322 Diagnostics { diagnostics: Vec<Diagnostic> },
324 DocumentSymbol { symbols: Vec<DocumentSymbol> },
326 WorkspaceSymbol { symbols: Vec<WorkspaceSymbol> },
328 Implementation { locations: Vec<Location> },
330 CallHierarchy { items: Vec<CallHierarchyItem> },
332 IncomingCalls {
334 calls: Vec<CallHierarchyIncomingCall>,
335 },
336 OutgoingCalls {
338 calls: Vec<CallHierarchyOutgoingCall>,
339 },
340}
341
342pub 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
356pub struct LspTool {
367 callback: Option<LspCallback>,
369 supported_extensions: Vec<String>,
371}
372
373impl Default for LspTool {
374 fn default() -> Self {
375 Self::new()
376 }
377}
378
379impl LspTool {
380 pub fn new() -> Self {
385 Self {
386 callback: None,
387 supported_extensions: Vec::new(),
388 }
389 }
390
391 pub fn with_callback(mut self, callback: LspCallback) -> Self {
393 self.callback = Some(callback);
394 self
395 }
396
397 pub fn with_supported_extensions(mut self, extensions: Vec<String>) -> Self {
399 self.supported_extensions = extensions;
400 self
401 }
402
403 pub fn has_callback(&self) -> bool {
405 self.callback.is_some()
406 }
407
408 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let result = self.execute_operation(operation, &path, position).await?;
739
740 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 PermissionCheckResult::allow()
756 }
757}
758
759fn 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
980fn 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
989fn 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 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 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 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 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 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 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 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(¶ms, &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")); }
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 #[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}