1use crate::core::cache::FileCache;
4use crate::core::walker::FileInfo;
5use crate::utils::file_ext::FileType;
6use anyhow::Result;
7use std::collections::HashMap;
8use std::path::Path;
9use std::sync::Arc;
10
11#[derive(Debug, Clone)]
13pub struct ContextOptions {
14 pub max_tokens: Option<usize>,
16 pub include_tree: bool,
18 pub include_stats: bool,
20 pub group_by_type: bool,
22 pub sort_by_priority: bool,
24 pub file_header_template: String,
26 pub doc_header_template: String,
28 pub include_toc: bool,
30 pub enhanced_context: bool,
32}
33
34impl ContextOptions {
35 pub fn from_config(config: &crate::cli::Config) -> Result<Self> {
37 Ok(ContextOptions {
38 max_tokens: config.get_effective_context_tokens(),
39 include_tree: true,
40 include_stats: true,
41 group_by_type: false,
42 sort_by_priority: true,
43 file_header_template: "## {path}".to_string(),
44 doc_header_template: "# Code Context: {directory}".to_string(),
45 include_toc: true,
46 enhanced_context: config.enhanced_context,
47 })
48 }
49}
50
51impl Default for ContextOptions {
52 fn default() -> Self {
53 ContextOptions {
54 max_tokens: None,
55 include_tree: true,
56 include_stats: true,
57 group_by_type: false,
58 sort_by_priority: true,
59 file_header_template: "## {path}".to_string(),
60 doc_header_template: "# Code Context: {directory}".to_string(),
61 include_toc: true,
62 enhanced_context: false,
63 }
64 }
65}
66
67fn estimate_output_size(files: &[FileInfo], options: &ContextOptions, cache: &FileCache) -> usize {
69 let mut size = 0;
70
71 if !options.doc_header_template.is_empty() {
73 size += options.doc_header_template.len() + 50; }
75
76 if options.include_stats {
78 size += 500; size += files.len() * 50; }
81
82 if options.include_tree {
84 size += 100; size += files.len() * 100; }
87
88 if options.include_toc {
90 size += 50; size += files.len() * 100; }
93
94 for file in files {
96 size +=
98 options.file_header_template.len() + file.relative_path.to_string_lossy().len() + 20;
99
100 if let Ok(content) = cache.get_or_load(&file.path) {
102 size += content.len() + 20; } else {
104 size += file.size as usize; }
106 }
107
108 size + (size / 5)
110}
111
112pub fn generate_markdown(
114 files: Vec<FileInfo>,
115 options: ContextOptions,
116 cache: Arc<FileCache>,
117) -> Result<String> {
118 let estimated_size = estimate_output_size(&files, &options, &cache);
120 let mut output = String::with_capacity(estimated_size);
121
122 if !options.doc_header_template.is_empty() {
124 let header = options.doc_header_template.replace("{directory}", ".");
125 output.push_str(&header);
126 output.push_str("\n\n");
127 }
128
129 if options.include_stats {
131 let stats = generate_statistics(&files);
132 output.push_str(&stats);
133 output.push_str("\n\n");
134 }
135
136 if options.include_tree {
138 let tree = generate_file_tree(&files, &options);
139 output.push_str("## File Structure\n\n");
140 output.push_str("```\n");
141 output.push_str(&tree);
142 output.push_str("```\n\n");
143 }
144
145 let mut files = files;
147 if options.sort_by_priority {
148 files.sort_by(|a, b| {
149 b.priority
150 .partial_cmp(&a.priority)
151 .unwrap_or(std::cmp::Ordering::Equal)
152 .then_with(|| a.relative_path.cmp(&b.relative_path))
153 });
154 }
155
156 if options.include_toc {
158 output.push_str("## Table of Contents\n\n");
159 for file in &files {
160 let anchor = path_to_anchor(&file.relative_path);
161 output.push_str(&format!(
162 "- [{path}](#{anchor})\n",
163 path = file.relative_path.display(),
164 anchor = anchor
165 ));
166 }
167 output.push('\n');
168 }
169
170 if options.group_by_type {
172 let grouped = group_files_by_type(files);
173 for (file_type, group_files) in grouped {
174 output.push_str(&format!("## {} Files\n\n", file_type_display(&file_type)));
175 for file in group_files {
176 append_file_content(&mut output, &file, &options, &cache)?;
177 }
178 }
179 } else {
180 for file in files {
182 append_file_content(&mut output, &file, &options, &cache)?;
183 }
184 }
185
186 Ok(output)
187}
188
189fn append_file_content(
191 output: &mut String,
192 file: &FileInfo,
193 options: &ContextOptions,
194 cache: &FileCache,
195) -> Result<()> {
196 let content = match cache.get_or_load(&file.path) {
198 Ok(content) => content,
199 Err(e) => {
200 eprintln!(
201 "Warning: Could not read file {}: {}",
202 file.path.display(),
203 e
204 );
205 return Ok(());
206 }
207 };
208
209 let path_with_metadata = if options.enhanced_context {
211 format!(
212 "{} ({}, {})",
213 file.relative_path.display(),
214 format_size(file.size),
215 file_type_display(&file.file_type)
216 )
217 } else {
218 file.relative_path.display().to_string()
219 };
220
221 let header = options
222 .file_header_template
223 .replace("{path}", &path_with_metadata);
224 output.push_str(&header);
225 output.push_str("\n\n");
226
227 if !file.imports.is_empty() {
229 output.push_str("Imports: ");
230 let import_names: Vec<String> = file
231 .imports
232 .iter()
233 .map(|p| {
234 let filename = p.file_name().and_then(|n| n.to_str()).unwrap_or("");
235
236 if filename == "__init__.py" {
238 p.parent()
239 .and_then(|parent| parent.file_name())
240 .and_then(|n| n.to_str())
241 .unwrap_or("unknown")
242 .to_string()
243 } else {
244 filename
246 .strip_suffix(".py")
247 .or_else(|| filename.strip_suffix(".rs"))
248 .or_else(|| filename.strip_suffix(".js"))
249 .or_else(|| filename.strip_suffix(".ts"))
250 .unwrap_or(filename)
251 .to_string()
252 }
253 })
254 .collect();
255 output.push_str(&format!("{}\n\n", import_names.join(", ")));
256 }
257
258 if !file.imported_by.is_empty() {
259 output.push_str("Imported by: ");
260 let imported_by_names: Vec<String> = file
261 .imported_by
262 .iter()
263 .map(|p| {
264 p.file_name()
265 .and_then(|n| n.to_str())
266 .unwrap_or_else(|| p.to_str().unwrap_or("unknown"))
267 .to_string()
268 })
269 .collect();
270 output.push_str(&format!("{}\n\n", imported_by_names.join(", ")));
271 }
272
273 if !file.function_calls.is_empty() {
274 output.push_str("Function calls: ");
275 let function_names: Vec<String> = file
276 .function_calls
277 .iter()
278 .map(|fc| {
279 if let Some(module) = &fc.module {
280 format!("{}.{}", module, fc.name)
281 } else {
282 fc.name.clone()
283 }
284 })
285 .collect();
286 output.push_str(&format!("{}\n\n", function_names.join(", ")));
287 }
288
289 if !file.type_references.is_empty() {
290 output.push_str("Type references: ");
291 let type_names: Vec<String> = file
292 .type_references
293 .iter()
294 .map(|tr| {
295 if let Some(module) = &tr.module {
296 format!("{}.{}", module, tr.name)
297 } else {
298 tr.name.clone()
299 }
300 })
301 .collect();
302 output.push_str(&format!("{}\n\n", type_names.join(", ")));
303 }
304
305 let language = get_language_hint(&file.file_type);
307 output.push_str(&format!("```{language}\n"));
308 output.push_str(&content);
309 if !content.ends_with('\n') {
310 output.push('\n');
311 }
312 output.push_str("```\n\n");
313
314 Ok(())
315}
316
317fn generate_statistics(files: &[FileInfo]) -> String {
319 let total_files = files.len();
320 let total_size: u64 = files.iter().map(|f| f.size).sum();
321
322 let mut type_counts: HashMap<FileType, usize> = HashMap::new();
324 for file in files {
325 *type_counts.entry(file.file_type.clone()).or_insert(0) += 1;
326 }
327
328 let mut stats = String::with_capacity(500 + type_counts.len() * 50);
330 stats.push_str("## Statistics\n\n");
331 stats.push_str(&format!("- Total files: {total_files}\n"));
332 stats.push_str(&format!(
333 "- Total size: {} bytes\n",
334 format_size(total_size)
335 ));
336 stats.push_str("\n### Files by type:\n");
337
338 let mut types: Vec<_> = type_counts.into_iter().collect();
339 types.sort_by_key(|(_, count)| std::cmp::Reverse(*count));
340
341 for (file_type, count) in types {
342 stats.push_str(&format!("- {}: {}\n", file_type_display(&file_type), count));
343 }
344
345 stats
346}
347
348fn generate_file_tree(files: &[FileInfo], options: &ContextOptions) -> String {
350 use std::collections::{BTreeMap, HashMap};
351
352 #[derive(Default)]
353 struct TreeNode {
354 files: Vec<String>,
355 dirs: BTreeMap<String, TreeNode>,
356 }
357
358 let mut root = TreeNode::default();
359
360 let file_lookup: HashMap<String, &FileInfo> = files
362 .iter()
363 .map(|f| (f.relative_path.to_string_lossy().to_string(), f))
364 .collect();
365
366 for file in files {
368 let parts: Vec<_> = file
369 .relative_path
370 .components()
371 .map(|c| c.as_os_str().to_string_lossy().to_string())
372 .collect();
373
374 let mut current = &mut root;
375 for (i, part) in parts.iter().enumerate() {
376 if i == parts.len() - 1 {
377 current.files.push(part.clone());
379 } else {
380 current = current.dirs.entry(part.clone()).or_default();
382 }
383 }
384 }
385
386 fn render_tree(
388 node: &TreeNode,
389 prefix: &str,
390 _is_last: bool,
391 current_path: &str,
392 file_lookup: &HashMap<String, &FileInfo>,
393 options: &ContextOptions,
394 ) -> String {
395 let estimated_size = (node.dirs.len() + node.files.len()) * 100;
397 let mut output = String::with_capacity(estimated_size);
398
399 let dir_count = node.dirs.len();
401 for (i, (name, child)) in node.dirs.iter().enumerate() {
402 let is_last_dir = i == dir_count - 1 && node.files.is_empty();
403 let connector = if is_last_dir {
404 "└── "
405 } else {
406 "├── "
407 };
408 let extension = if is_last_dir { " " } else { "│ " };
409
410 output.push_str(&format!("{prefix}{connector}{name}/\n"));
411 let child_path = if current_path.is_empty() {
412 name.clone()
413 } else {
414 format!("{current_path}/{name}")
415 };
416 output.push_str(&render_tree(
417 child,
418 &format!("{prefix}{extension}"),
419 is_last_dir,
420 &child_path,
421 file_lookup,
422 options,
423 ));
424 }
425
426 let file_count = node.files.len();
428 for (i, name) in node.files.iter().enumerate() {
429 let is_last_file = i == file_count - 1;
430 let connector = if is_last_file {
431 "└── "
432 } else {
433 "├── "
434 };
435
436 let file_path = if current_path.is_empty() {
437 name.clone()
438 } else {
439 format!("{current_path}/{name}")
440 };
441
442 let display_name = if options.enhanced_context {
444 if let Some(file_info) = file_lookup.get(&file_path) {
445 format!(
446 "{} ({}, {})",
447 name,
448 format_size(file_info.size),
449 file_type_display(&file_info.file_type)
450 )
451 } else {
452 name.clone()
453 }
454 } else {
455 name.clone()
456 };
457
458 output.push_str(&format!("{prefix}{connector}{display_name}\n"));
459 }
460
461 output
462 }
463
464 let mut output = String::with_capacity(files.len() * 100 + 10);
466 output.push_str(".\n");
467 output.push_str(&render_tree(&root, "", true, "", &file_lookup, options));
468 output
469}
470
471fn group_files_by_type(files: Vec<FileInfo>) -> Vec<(FileType, Vec<FileInfo>)> {
473 let mut groups: HashMap<FileType, Vec<FileInfo>> = HashMap::new();
474
475 for file in files {
476 groups.entry(file.file_type.clone()).or_default().push(file);
477 }
478
479 let mut result: Vec<_> = groups.into_iter().collect();
480 result.sort_by_key(|(file_type, _)| file_type_priority(file_type));
481 result
482}
483
484fn file_type_display(file_type: &FileType) -> &'static str {
486 match file_type {
487 FileType::Rust => "Rust",
488 FileType::Python => "Python",
489 FileType::JavaScript => "JavaScript",
490 FileType::TypeScript => "TypeScript",
491 FileType::Go => "Go",
492 FileType::Java => "Java",
493 FileType::Cpp => "C++",
494 FileType::C => "C",
495 FileType::CSharp => "C#",
496 FileType::Ruby => "Ruby",
497 FileType::Php => "PHP",
498 FileType::Swift => "Swift",
499 FileType::Kotlin => "Kotlin",
500 FileType::Scala => "Scala",
501 FileType::Haskell => "Haskell",
502 FileType::Dart => "Dart",
503 FileType::Lua => "Lua",
504 FileType::R => "R",
505 FileType::Julia => "Julia",
506 FileType::Elixir => "Elixir",
507 FileType::Elm => "Elm",
508 FileType::Markdown => "Markdown",
509 FileType::Json => "JSON",
510 FileType::Yaml => "YAML",
511 FileType::Toml => "TOML",
512 FileType::Xml => "XML",
513 FileType::Html => "HTML",
514 FileType::Css => "CSS",
515 FileType::Text => "Text",
516 FileType::Other => "Other",
517 }
518}
519
520fn get_language_hint(file_type: &FileType) -> &'static str {
522 match file_type {
523 FileType::Rust => "rust",
524 FileType::Python => "python",
525 FileType::JavaScript => "javascript",
526 FileType::TypeScript => "typescript",
527 FileType::Go => "go",
528 FileType::Java => "java",
529 FileType::Cpp => "cpp",
530 FileType::C => "c",
531 FileType::CSharp => "csharp",
532 FileType::Ruby => "ruby",
533 FileType::Php => "php",
534 FileType::Swift => "swift",
535 FileType::Kotlin => "kotlin",
536 FileType::Scala => "scala",
537 FileType::Haskell => "haskell",
538 FileType::Dart => "dart",
539 FileType::Lua => "lua",
540 FileType::R => "r",
541 FileType::Julia => "julia",
542 FileType::Elixir => "elixir",
543 FileType::Elm => "elm",
544 FileType::Markdown => "markdown",
545 FileType::Json => "json",
546 FileType::Yaml => "yaml",
547 FileType::Toml => "toml",
548 FileType::Xml => "xml",
549 FileType::Html => "html",
550 FileType::Css => "css",
551 FileType::Text => "text",
552 FileType::Other => "",
553 }
554}
555
556fn file_type_priority(file_type: &FileType) -> u8 {
558 match file_type {
559 FileType::Rust => 1,
560 FileType::Python => 2,
561 FileType::JavaScript => 3,
562 FileType::TypeScript => 3,
563 FileType::Go => 4,
564 FileType::Java => 5,
565 FileType::Cpp => 6,
566 FileType::C => 7,
567 FileType::CSharp => 8,
568 FileType::Ruby => 9,
569 FileType::Php => 10,
570 FileType::Swift => 11,
571 FileType::Kotlin => 12,
572 FileType::Scala => 13,
573 FileType::Haskell => 14,
574 FileType::Dart => 15,
575 FileType::Lua => 16,
576 FileType::R => 17,
577 FileType::Julia => 18,
578 FileType::Elixir => 19,
579 FileType::Elm => 20,
580 FileType::Markdown => 21,
581 FileType::Json => 22,
582 FileType::Yaml => 23,
583 FileType::Toml => 24,
584 FileType::Xml => 25,
585 FileType::Html => 26,
586 FileType::Css => 27,
587 FileType::Text => 28,
588 FileType::Other => 29,
589 }
590}
591
592fn path_to_anchor(path: &Path) -> String {
594 path.display()
595 .to_string()
596 .replace(['/', '\\', '.', ' '], "-")
597 .to_lowercase()
598}
599
600fn format_size(size: u64) -> String {
602 const UNITS: &[&str] = &["B", "KB", "MB", "GB"];
603 let mut size = size as f64;
604 let mut unit_index = 0;
605
606 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
607 size /= 1024.0;
608 unit_index += 1;
609 }
610
611 if unit_index == 0 {
612 format!("{} {}", size as u64, UNITS[unit_index])
613 } else {
614 format!("{:.2} {}", size, UNITS[unit_index])
615 }
616}
617
618#[cfg(test)]
619mod tests {
620 use super::*;
621 use std::path::PathBuf;
622
623 fn create_test_cache() -> Arc<FileCache> {
624 Arc::new(FileCache::new())
625 }
626
627 #[test]
628 fn test_format_size() {
629 assert_eq!(format_size(512), "512 B");
630 assert_eq!(format_size(1024), "1.00 KB");
631 assert_eq!(format_size(1536), "1.50 KB");
632 assert_eq!(format_size(1048576), "1.00 MB");
633 }
634
635 #[test]
636 fn test_path_to_anchor() {
637 assert_eq!(path_to_anchor(Path::new("src/main.rs")), "src-main-rs");
638 assert_eq!(path_to_anchor(Path::new("test file.txt")), "test-file-txt");
639 }
640
641 #[test]
642 fn test_file_type_display() {
643 assert_eq!(file_type_display(&FileType::Rust), "Rust");
644 assert_eq!(file_type_display(&FileType::Python), "Python");
645 }
646
647 #[test]
648 fn test_generate_statistics() {
649 let files = vec![
650 FileInfo {
651 path: PathBuf::from("test1.rs"),
652 relative_path: PathBuf::from("test1.rs"),
653 size: 100,
654 file_type: FileType::Rust,
655 priority: 1.0,
656 imports: Vec::new(),
657 imported_by: Vec::new(),
658 function_calls: Vec::new(),
659 type_references: Vec::new(),
660 },
661 FileInfo {
662 path: PathBuf::from("test2.py"),
663 relative_path: PathBuf::from("test2.py"),
664 size: 200,
665 file_type: FileType::Python,
666 priority: 0.9,
667 imports: Vec::new(),
668 imported_by: Vec::new(),
669 function_calls: Vec::new(),
670 type_references: Vec::new(),
671 },
672 ];
673
674 let stats = generate_statistics(&files);
675 assert!(stats.contains("Total files: 2"));
676 assert!(stats.contains("Total size: 300 B"));
677 assert!(stats.contains("Rust: 1"));
678 assert!(stats.contains("Python: 1"));
679 }
680
681 #[test]
682 fn test_generate_statistics_empty() {
683 let files = vec![];
684 let stats = generate_statistics(&files);
685 assert!(stats.contains("Total files: 0"));
686 assert!(stats.contains("Total size: 0 B"));
687 }
688
689 #[test]
690 fn test_generate_statistics_large_files() {
691 let files = vec![
692 FileInfo {
693 path: PathBuf::from("large.rs"),
694 relative_path: PathBuf::from("large.rs"),
695 size: 2_000_000, file_type: FileType::Rust,
697 priority: 1.0,
698 imports: Vec::new(),
699 imported_by: Vec::new(),
700 function_calls: Vec::new(),
701 type_references: Vec::new(),
702 },
703 FileInfo {
704 path: PathBuf::from("huge.py"),
705 relative_path: PathBuf::from("huge.py"),
706 size: 50_000_000, file_type: FileType::Python,
708 priority: 0.9,
709 imports: Vec::new(),
710 imported_by: Vec::new(),
711 function_calls: Vec::new(),
712 type_references: Vec::new(),
713 },
714 ];
715
716 let stats = generate_statistics(&files);
717 assert!(stats.contains("Total files: 2"));
718 assert!(stats.contains("MB bytes")); assert!(stats.contains("Python: 1"));
720 assert!(stats.contains("Rust: 1"));
721 }
722
723 #[test]
724 fn test_generate_file_tree_with_grouping() {
725 let files = vec![
726 FileInfo {
727 path: PathBuf::from("src/main.rs"),
728 relative_path: PathBuf::from("src/main.rs"),
729 size: 1000,
730 file_type: FileType::Rust,
731 priority: 1.5,
732 imports: Vec::new(),
733 imported_by: Vec::new(),
734 function_calls: Vec::new(),
735 type_references: Vec::new(),
736 },
737 FileInfo {
738 path: PathBuf::from("src/lib.rs"),
739 relative_path: PathBuf::from("src/lib.rs"),
740 size: 2000,
741 file_type: FileType::Rust,
742 priority: 1.2,
743 imports: Vec::new(),
744 imported_by: Vec::new(),
745 function_calls: Vec::new(),
746 type_references: Vec::new(),
747 },
748 FileInfo {
749 path: PathBuf::from("tests/test.rs"),
750 relative_path: PathBuf::from("tests/test.rs"),
751 size: 500,
752 file_type: FileType::Rust,
753 priority: 0.8,
754 imports: Vec::new(),
755 imported_by: Vec::new(),
756 function_calls: Vec::new(),
757 type_references: Vec::new(),
758 },
759 ];
760
761 let options = ContextOptions::default();
762 let tree = generate_file_tree(&files, &options);
763 assert!(tree.contains("src/"));
764 assert!(tree.contains("tests/"));
765 assert!(tree.contains("main.rs"));
766 assert!(tree.contains("lib.rs"));
767 assert!(tree.contains("test.rs"));
768 }
769
770 #[test]
771 fn test_context_options_from_config() {
772 use crate::cli::Config;
773 use tempfile::TempDir;
774
775 let temp_dir = TempDir::new().unwrap();
776 let config = Config {
777 paths: Some(vec![temp_dir.path().to_path_buf()]),
778 max_tokens: Some(100000),
779 ..Config::default()
780 };
781
782 let options = ContextOptions::from_config(&config).unwrap();
783 assert_eq!(options.max_tokens, Some(100000));
784 assert!(options.include_tree);
785 assert!(options.include_stats);
786 assert!(!options.group_by_type); }
788
789 #[test]
790 fn test_generate_markdown_structure_headers() {
791 let files = vec![];
792
793 let options = ContextOptions {
794 max_tokens: None,
795 include_tree: true,
796 include_stats: true,
797 group_by_type: true,
798 sort_by_priority: true,
799 file_header_template: "## {path}".to_string(),
800 doc_header_template: "# Code Context".to_string(),
801 include_toc: true,
802 enhanced_context: false,
803 };
804
805 let cache = create_test_cache();
806 let markdown = generate_markdown(files, options, cache).unwrap();
807
808 assert!(markdown.contains("# Code Context"));
810 assert!(markdown.contains("## Statistics"));
811 }
812
813 #[test]
814 fn test_enhanced_tree_generation_with_metadata() {
815 use crate::core::walker::FileInfo;
816 use crate::utils::file_ext::FileType;
817 use std::path::PathBuf;
818
819 let files = vec![
820 FileInfo {
821 path: PathBuf::from("src/main.rs"),
822 relative_path: PathBuf::from("src/main.rs"),
823 size: 145,
824 file_type: FileType::Rust,
825 priority: 1.5,
826 imports: Vec::new(),
827 imported_by: Vec::new(),
828 function_calls: Vec::new(),
829 type_references: Vec::new(),
830 },
831 FileInfo {
832 path: PathBuf::from("src/lib.rs"),
833 relative_path: PathBuf::from("src/lib.rs"),
834 size: 89,
835 file_type: FileType::Rust,
836 priority: 1.2,
837 imports: Vec::new(),
838 imported_by: Vec::new(),
839 function_calls: Vec::new(),
840 type_references: Vec::new(),
841 },
842 ];
843
844 let options = ContextOptions {
845 max_tokens: None,
846 include_tree: true,
847 include_stats: true,
848 group_by_type: false,
849 sort_by_priority: true,
850 file_header_template: "## {path}".to_string(),
851 doc_header_template: "# Code Context".to_string(),
852 include_toc: true,
853 enhanced_context: true,
854 };
855
856 let cache = create_test_cache();
857 let markdown = generate_markdown(files, options, cache).unwrap();
858
859 assert!(markdown.contains("main.rs (145 B, Rust)"));
861 assert!(markdown.contains("lib.rs (89 B, Rust)"));
862 }
863
864 #[test]
865 fn test_enhanced_file_headers_with_metadata() {
866 use crate::core::walker::FileInfo;
867 use crate::utils::file_ext::FileType;
868 use std::path::PathBuf;
869
870 let files = vec![FileInfo {
871 path: PathBuf::from("src/main.rs"),
872 relative_path: PathBuf::from("src/main.rs"),
873 size: 145,
874 file_type: FileType::Rust,
875 priority: 1.5,
876 imports: Vec::new(),
877 imported_by: Vec::new(),
878 function_calls: Vec::new(),
879 type_references: Vec::new(),
880 }];
881
882 let options = ContextOptions {
883 max_tokens: None,
884 include_tree: true,
885 include_stats: true,
886 group_by_type: false,
887 sort_by_priority: true,
888 file_header_template: "## {path}".to_string(),
889 doc_header_template: "# Code Context".to_string(),
890 include_toc: true,
891 enhanced_context: true,
892 };
893
894 let cache = create_test_cache();
895 let markdown = generate_markdown(files, options, cache).unwrap();
896
897 assert!(markdown.contains("## src/main.rs (145 B, Rust)"));
899 }
900
901 #[test]
902 fn test_basic_mode_unchanged() {
903 use crate::core::walker::FileInfo;
904 use crate::utils::file_ext::FileType;
905 use std::path::PathBuf;
906
907 let files = vec![FileInfo {
908 path: PathBuf::from("src/main.rs"),
909 relative_path: PathBuf::from("src/main.rs"),
910 size: 145,
911 file_type: FileType::Rust,
912 priority: 1.5,
913 imports: Vec::new(),
914 imported_by: Vec::new(),
915 function_calls: Vec::new(),
916 type_references: Vec::new(),
917 }];
918
919 let options = ContextOptions {
920 max_tokens: None,
921 include_tree: true,
922 include_stats: true,
923 group_by_type: false,
924 sort_by_priority: true,
925 file_header_template: "## {path}".to_string(),
926 doc_header_template: "# Code Context".to_string(),
927 include_toc: true,
928 enhanced_context: false,
929 };
930
931 let cache = create_test_cache();
932 let markdown = generate_markdown(files, options, cache).unwrap();
933
934 assert!(markdown.contains("## src/main.rs"));
936 assert!(!markdown.contains("## src/main.rs (145 B, Rust)"));
937 assert!(markdown.contains("main.rs") && !markdown.contains("main.rs (145 B, Rust)"));
938 }
939}