Rust · 3992 bytes Raw Blame History
1 use std::path::PathBuf;
2
3 use crate::db::Database;
4 use crate::matcher::Matcher;
5
6 use super::Result;
7
8 /// Query the database for matching directories using fuzzy matching.
9 ///
10 /// Returns the best match by default, or all matches with --all.
11 /// Matches are ranked by combined frecency + fuzzy score.
12 pub fn run(terms: Vec<String>, show_score: bool, show_all: bool, cwd_mode: bool) -> Result<()> {
13 let mut matcher = Matcher::new();
14
15 // Require terms unless --all is specified
16 if terms.is_empty() && !show_all {
17 eprintln!("gump: no search terms provided");
18 std::process::exit(1);
19 }
20
21 if cwd_mode {
22 return run_cwd_mode(&mut matcher, &terms, show_all);
23 }
24
25 let db = Database::open()?;
26
27 // If no terms, just list all entries sorted by frecency
28 if terms.is_empty() {
29 let mut entries: Vec<_> = db.entries().collect();
30 entries.sort_by(|a, b| b.1.frecency().partial_cmp(&a.1.frecency()).unwrap_or(std::cmp::Ordering::Equal));
31
32 if entries.is_empty() {
33 std::process::exit(1);
34 }
35
36 for (path, entry) in entries {
37 if show_score {
38 println!("{:>8.2} {}", entry.frecency(), path.display());
39 } else {
40 println!("{}", path.display());
41 }
42 }
43 return Ok(());
44 }
45
46 // Collect paths with their frecency scores
47 let paths = db.entries().map(|(path, entry)| (path.as_path(), entry.frecency()));
48
49 // Get ranked matches
50 let matches = matcher.rank(paths, &terms);
51
52 if matches.is_empty() {
53 // Exit with code 1 to signal no match (used by shell integration)
54 std::process::exit(1);
55 }
56
57 if show_all {
58 for m in &matches {
59 if show_score {
60 println!("{:>8.2} {}", m.combined_score, m.path.display());
61 } else {
62 println!("{}", m.path.display());
63 }
64 }
65 } else {
66 // Just print the best match
67 let best = &matches[0];
68 if show_score {
69 println!("{:>8.2} {}", best.combined_score, best.path.display());
70 } else {
71 println!("{}", best.path.display());
72 }
73 }
74
75 Ok(())
76 }
77
78 /// Match against directories in the current working directory.
79 /// Matches against directory NAMES only (not full paths) to avoid false positives
80 /// from parent directory names appearing in the path.
81 fn run_cwd_mode(matcher: &mut Matcher, terms: &[String], show_all: bool) -> Result<()> {
82 let cwd = std::env::current_dir()?;
83
84 // Read directory entries, filter to directories only
85 // Store both the full path (for result) and just the name (for matching)
86 let dirs: Vec<(PathBuf, PathBuf)> = std::fs::read_dir(&cwd)?
87 .filter_map(|entry| entry.ok())
88 .filter(|entry| entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false))
89 .map(|entry| {
90 let full_path = entry.path();
91 let name_only = PathBuf::from(entry.file_name());
92 (full_path, name_only)
93 })
94 .collect();
95
96 if dirs.is_empty() {
97 std::process::exit(1);
98 }
99
100 // Match against directory NAMES only, not full paths
101 let paths = dirs.iter().map(|(_, name)| (name.as_path(), 1.0));
102
103 let matches = matcher.rank(paths, terms);
104
105 if matches.is_empty() {
106 std::process::exit(1);
107 }
108
109 // Find the full path for each matched name
110 if show_all {
111 for m in &matches {
112 // Find the full path that corresponds to this matched name
113 if let Some((full_path, _)) = dirs.iter().find(|(_, name)| name.as_path() == m.path) {
114 println!("{}", full_path.display());
115 }
116 }
117 } else {
118 // Find the full path for the best match
119 let matched_name = matches[0].path;
120 if let Some((full_path, _)) = dirs.iter().find(|(_, name)| name.as_path() == matched_name) {
121 println!("{}", full_path.display());
122 }
123 }
124
125 Ok(())
126 }
127