1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
//! Search command - Text search
//!
//! Searches files for regex patterns with context.
//! Auto-routes through daemon when available for ~35x speedup.
use std::collections::HashSet;
use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
use tldr_core::{search, IgnoreSpec, SearchMatch};
use crate::commands::daemon_router::{params_with_pattern, try_daemon_route};
use crate::output::{format_search_text, OutputFormat, OutputWriter};
/// Search files for regex pattern
#[derive(Debug, Args)]
pub struct SearchArgs {
/// Regex pattern to search for
pub pattern: String,
/// Directory to search in (default: current directory)
#[arg(default_value = ".")]
pub path: PathBuf,
/// Filter by file extensions (e.g., --ext .py --ext .rs)
#[arg(long = "ext", short = 'e')]
pub extensions: Vec<String>,
/// Number of context lines before and after each match
#[arg(long, short = 'C', default_value = "0")]
pub context: usize,
/// Maximum number of matches to return
#[arg(long, short = 'm', default_value = "100")]
pub max_results: usize,
/// Maximum number of files to search
#[arg(long, default_value = "1000")]
pub max_files: usize,
}
impl SearchArgs {
/// Run the search command
pub fn run(&self, format: OutputFormat, quiet: bool) -> Result<()> {
let writer = OutputWriter::new(format, quiet);
// Try daemon first for cached result
if let Some(matches) = try_daemon_route::<Vec<SearchMatch>>(
&self.path,
"search",
params_with_pattern(&self.pattern, Some(self.max_results)),
) {
// Output based on format
if writer.is_text() {
let text = format_search_text(&matches);
writer.write_text(&text)?;
return Ok(());
} else {
writer.write(&matches)?;
return Ok(());
}
}
// Fallback to direct compute
writer.progress(&format!(
"Searching for '{}' in {}...",
self.pattern,
self.path.display()
));
// Build extensions set if provided
let extensions: Option<HashSet<String>> = if self.extensions.is_empty() {
None
} else {
Some(
self.extensions
.iter()
.map(|s| {
if s.starts_with('.') {
s.clone()
} else {
format!(".{}", s)
}
})
.collect(),
)
};
// Search
let matches = search(
&self.pattern,
&self.path,
extensions.as_ref(),
self.context,
self.max_results,
self.max_files,
Some(&IgnoreSpec::default()),
)?;
// Output based on format
if writer.is_text() {
let text = format_search_text(&matches);
writer.write_text(&text)?;
} else {
writer.write(&matches)?;
}
Ok(())
}
}