greppy/output/
human.rs

1//! Human-readable output formatting
2
3use crate::search::SearchResponse;
4
5/// Format results for human consumption
6pub fn format(results: &SearchResponse) -> String {
7    let mut output = String::new();
8
9    if results.results.is_empty() {
10        output.push_str(&format!(
11            "No results found for '{}' ({:.1}ms)\n",
12            results.query, results.elapsed_ms
13        ));
14        return output;
15    }
16
17    output.push_str(&format!(
18        "Found {} results for '{}' ({:.1}ms)\n\n",
19        results.results.len(),
20        results.query,
21        results.elapsed_ms
22    ));
23
24    for (i, result) in results.results.iter().enumerate() {
25        // Header: path:lines (score)
26        output.push_str(&format!(
27            "{}. {}:{}-{} ({:.2})\n",
28            i + 1,
29            result.path,
30            result.start_line,
31            result.end_line,
32            result.score
33        ));
34
35        // Symbol info if available
36        if let (Some(name), Some(stype)) = (&result.symbol_name, &result.symbol_type) {
37            output.push_str(&format!("   {} {}\n", stype, name));
38        }
39
40        // Content preview (first 3 lines, truncated)
41        let preview_lines: Vec<&str> = result.content.lines().take(3).collect();
42        for line in preview_lines {
43            let truncated: String = if line.len() > 80 {
44                format!("{}...", &line[..77])
45            } else {
46                line.to_string()
47            };
48            output.push_str(&format!("   {}\n", truncated));
49        }
50
51        if result.content.lines().count() > 3 {
52            output.push_str("   ...\n");
53        }
54
55        output.push('\n');
56    }
57
58    output
59}