1use std::path::Path;
27
28pub fn complete_files_with_ext(prefix: &str, exts: &[&str], strip_ext: bool) -> Vec<String> {
42 let (dir, file_prefix) = split_prefix(prefix);
43 let search = if dir.is_empty() {
44 Path::new(".")
45 } else {
46 Path::new(&dir)
47 };
48
49 let Ok(rd) = std::fs::read_dir(search) else {
50 return Vec::new();
51 };
52
53 let mut out = Vec::new();
54 for entry in rd.flatten() {
55 let name = entry.file_name();
56 let name = name.to_string_lossy();
57 if !name.starts_with(&*file_prefix) {
58 continue;
59 }
60
61 let path = entry.path();
62 if path.is_dir() {
63 let full = join_prefix(&dir, &format!("{}/", name));
64 out.push(full);
65 continue;
66 }
67
68 if !exts.is_empty() {
69 let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
70 if !exts.iter().any(|&e| e.eq_ignore_ascii_case(ext)) {
71 continue;
72 }
73 }
74
75 let display = if strip_ext {
76 path.file_stem()
77 .and_then(|s| s.to_str())
78 .unwrap_or(&name)
79 .to_string()
80 } else {
81 name.to_string()
82 };
83
84 out.push(join_prefix(&dir, &display));
85 }
86 out.sort();
87 out
88}
89
90pub fn complete_dirs(prefix: &str) -> Vec<String> {
92 let (dir, file_prefix) = split_prefix(prefix);
93 let search = if dir.is_empty() {
94 Path::new(".")
95 } else {
96 Path::new(&dir)
97 };
98
99 let Ok(rd) = std::fs::read_dir(search) else {
100 return Vec::new();
101 };
102
103 let mut out: Vec<String> = rd
104 .flatten()
105 .filter(|e| e.path().is_dir())
106 .map(|e| e.file_name().to_string_lossy().to_string())
107 .filter(|n| n.starts_with(&*file_prefix))
108 .map(|n| join_prefix(&dir, &format!("{n}/")))
109 .collect();
110 out.sort();
111 out
112}
113
114pub fn complete_files(prefix: &str) -> Vec<String> {
116 complete_files_with_ext(prefix, &[], false)
117}
118
119pub fn complete_files_from_glob(prefix: &str, pattern: &str) -> Vec<String> {
122 let exts = extract_exts_from_glob(pattern);
123 let refs: Vec<&str> = exts.iter().map(|s| s.as_str()).collect();
124 complete_files_with_ext(prefix, &refs, false)
125}
126
127fn split_prefix(prefix: &str) -> (String, String) {
133 if prefix.is_empty() {
134 return (String::new(), String::new());
135 }
136 if prefix.ends_with('/') {
137 return (prefix.trim_end_matches('/').to_string(), String::new());
138 }
139 let p = std::path::Path::new(prefix);
140 let dir = p
141 .parent()
142 .map(|d| d.to_string_lossy().to_string())
143 .unwrap_or_default();
144 let file = p
145 .file_name()
146 .map(|f| f.to_string_lossy().to_string())
147 .unwrap_or_default();
148 (dir, file)
149}
150
151fn join_prefix(dir: &str, name: &str) -> String {
152 if dir.is_empty() || dir == "." {
153 name.to_string()
154 } else {
155 format!("{}/{}", dir.trim_end_matches('/'), name)
156 }
157}
158
159fn extract_exts_from_glob(pattern: &str) -> Vec<String> {
160 if let Some(dot) = pattern.rfind('.') {
161 let after = &pattern[dot + 1..];
162 if after.starts_with('{') && after.ends_with('}') {
163 return after[1..after.len() - 1]
164 .split(',')
165 .map(|s| s.trim().to_string())
166 .collect();
167 }
168 if !after.chars().any(|c| "*?[]{".contains(c)) {
169 return vec![after.to_string()];
170 }
171 }
172 Vec::new()
173}
174
175pub fn print_values(values: &[String]) {
181 for v in values {
182 println!("{v}");
183 }
184}
185
186pub fn print_values_filtered(values: &[&str], word: &str) {
188 for v in values {
189 if v.starts_with(word) {
190 println!("{v}");
191 }
192 }
193}