1use crate::assets::VERSION_TEMPLATE;
2use crate::config::YamlConfig;
3use crate::constants::{self, CONTAIN_SEARCH_SECTIONS, config_key, section};
4use crate::{error, info, md, usage};
5use colored::Colorize;
6
7pub fn handle_version() {
9 let text = VERSION_TEMPLATE
10 .replace("{version}", constants::VERSION)
11 .replace("{os}", std::env::consts::OS)
12 .replace("{arch}", std::env::consts::ARCH);
13 md!("{}", text);
14}
15
16pub fn handle_exit() {
18 info!("Bye~ See you again 😭");
19 std::process::exit(0);
20}
21
22pub fn handle_log(key: &str, value: &str, config: &mut YamlConfig) {
24 if key == config_key::MODE {
25 let mode = if value == config_key::VERBOSE {
26 config_key::VERBOSE
27 } else {
28 config_key::CONCISE
29 };
30 config.set_property(section::LOG, config_key::MODE, mode);
31 info!("✅ 日志模式已切换为: {}", mode);
32 } else {
33 usage!("j log mode <verbose|concise>");
34 }
35}
36
37pub fn handle_clear() {
39 print!("\x1B[2J\x1B[1;1H");
41}
42
43pub fn handle_contain(alias: &str, containers: Option<&str>, config: &YamlConfig) {
46 let sections: Vec<&str> = match containers {
47 Some(c) => c.split(',').collect(),
48 None => CONTAIN_SEARCH_SECTIONS.to_vec(),
49 };
50
51 let mut found = Vec::new();
52
53 for section in §ions {
54 if config.contains(section, alias) {
55 if let Some(value) = config.get_property(section, alias) {
56 found.push(format!(
57 "{} {}: {}",
58 format!("[{}]", section).green(),
59 alias,
60 value
61 ));
62 }
63 }
64 }
65
66 if found.is_empty() {
67 info!("nothing found 😢");
68 } else {
69 info!("找到 {} 条结果 😊", found.len().to_string().green());
70 for line in &found {
71 info!("{}", line);
72 }
73 }
74}
75
76pub fn handle_change(part: &str, field: &str, value: &str, config: &mut YamlConfig) {
79 if config.get_section(part).is_none() {
80 error!("❌ 在配置文件中未找到该 section:{}", part);
81 return;
82 }
83
84 let old_value = config.get_property(part, field).cloned();
85 config.set_property(part, field, value);
86
87 match old_value {
88 Some(old) => {
89 info!(
90 "✅ 已修改 {}.{} 的值为 {},旧值为 {}",
91 part, field, value, old
92 );
93 }
94 None => {
95 info!("✅ 已新增 {}.{} = {}", part, field, value);
96 }
97 }
98 info!(
99 "🚧 此命令可能会导致配置文件属性错乱而使 Copilot 无法正常使用,请确保在您清楚在做什么的情况下使用"
100 );
101}
102
103pub fn handle_completion(shell_type: Option<&str>, config: &YamlConfig) {
108 let shell = shell_type.unwrap_or("zsh");
109
110 match shell {
111 "zsh" => generate_zsh_completion(config),
112 "bash" => generate_bash_completion(config),
113 _ => {
114 error!("❌ 不支持的 shell 类型: {},可选: zsh, bash", shell);
115 usage!("j completion [zsh|bash]");
116 }
117 }
118}
119
120fn generate_zsh_completion(config: &YamlConfig) {
122 let mut all_aliases = Vec::new();
124 for s in constants::ALIAS_EXISTS_SECTIONS {
125 if let Some(map) = config.get_section(s) {
126 for key in map.keys() {
127 if !all_aliases.contains(key) {
128 all_aliases.push(key.clone());
129 }
130 }
131 }
132 }
133 all_aliases.sort();
134
135 let editor_aliases: Vec<String> = config
137 .get_section(section::EDITOR)
138 .map(|m| m.keys().cloned().collect())
139 .unwrap_or_default();
140
141 let browser_aliases: Vec<String> = config
143 .get_section(section::BROWSER)
144 .map(|m| m.keys().cloned().collect())
145 .unwrap_or_default();
146
147 let keywords = constants::cmd::all_keywords();
149
150 let subcmds = keywords.iter().map(|s| *s).collect::<Vec<_>>();
152 let subcmds_str = subcmds.join(" ");
153
154 let aliases_str = all_aliases.join(" ");
156
157 let editor_pattern = if editor_aliases.is_empty() {
159 String::new()
160 } else {
161 editor_aliases.join("|")
162 };
163
164 let browser_pattern = if browser_aliases.is_empty() {
166 String::new()
167 } else {
168 browser_aliases.join("|")
169 };
170
171 let mut script = String::new();
173 script.push_str("#compdef j\n");
174 script.push_str("# Zsh completion for j (work-copilot)\n");
175 script.push_str("# 生成方式: eval \"$(j completion zsh)\"\n");
176 script.push_str(
177 "# 或: j completion zsh > ~/.zsh/completions/_j && fpath=(~/.zsh/completions $fpath)\n\n",
178 );
179 script.push_str("_j() {\n");
180 script.push_str(" local curcontext=\"$curcontext\" state line\n");
181 script.push_str(" typeset -A opt_args\n\n");
182
183 script.push_str(&format!(" local -a subcmds=({})\n", subcmds_str));
185 script.push_str(&format!(" local -a aliases=({})\n", aliases_str));
186
187 if !editor_pattern.is_empty() {
189 script.push_str(&format!(
190 " local -a editor_aliases=({})\n",
191 editor_aliases.join(" ")
192 ));
193 }
194
195 script.push_str("\n _arguments -C \\\n");
196 script.push_str(" '1: :->cmd' \\\n");
197 script.push_str(" '*: :->args'\n\n");
198
199 script.push_str(" case $state in\n");
200 script.push_str(" cmd)\n");
201 script.push_str(" _describe 'command' subcmds\n");
202 script.push_str(" _describe 'alias' aliases\n");
203 script.push_str(" ;;\n");
204 script.push_str(" args)\n");
205 script.push_str(" case $words[2] in\n");
206
207 script.push_str(" set|s|modify|mf)\n");
209 script.push_str(" if (( CURRENT == 3 )); then\n");
210 script.push_str(" _describe 'alias' aliases\n");
211 script.push_str(" else\n");
212 script.push_str(" _files\n");
213 script.push_str(" fi\n");
214 script.push_str(" ;;\n");
215
216 script.push_str(" rm|remove|rename|rn|note|nt|denote|dnt|contain|find)\n");
218 script.push_str(" _describe 'alias' aliases\n");
219 script.push_str(" ;;\n");
220
221 let sections_str = constants::ALL_SECTIONS.join(" ");
223 script.push_str(&format!(" ls|list)\n"));
224 script.push_str(&format!(
225 " local -a sections=(all {})\n",
226 sections_str
227 ));
228 script.push_str(" _describe 'section' sections\n");
229 script.push_str(" ;;\n");
230
231 script.push_str(" reportctl|rctl)\n");
233 script
234 .push_str(" local -a rctl_actions=(new sync push pull set-url open)\n");
235 script.push_str(" _describe 'action' rctl_actions\n");
236 script.push_str(" ;;\n");
237
238 script.push_str(" log)\n");
240 script.push_str(" if (( CURRENT == 3 )); then\n");
241 script.push_str(" local -a log_keys=(mode)\n");
242 script.push_str(" _describe 'key' log_keys\n");
243 script.push_str(" else\n");
244 script.push_str(" local -a log_values=(verbose concise)\n");
245 script.push_str(" _describe 'value' log_values\n");
246 script.push_str(" fi\n");
247 script.push_str(" ;;\n");
248
249 script.push_str(&format!(" change|chg)\n"));
251 script.push_str(&format!(
252 " local -a sections=({})\n",
253 sections_str
254 ));
255 script.push_str(" _describe 'section' sections\n");
256 script.push_str(" ;;\n");
257
258 script.push_str(" time)\n");
260 script.push_str(" local -a time_funcs=(countdown)\n");
261 script.push_str(" _describe 'function' time_funcs\n");
262 script.push_str(" ;;\n");
263
264 script.push_str(" completion)\n");
266 script.push_str(" local -a shells=(zsh bash)\n");
267 script.push_str(" _describe 'shell' shells\n");
268 script.push_str(" ;;\n");
269
270 if !editor_pattern.is_empty() {
272 script.push_str(&format!(" {})\n", editor_pattern));
273 script.push_str(" _files\n");
274 script.push_str(" ;;\n");
275 }
276
277 if !browser_pattern.is_empty() {
279 script.push_str(&format!(" {})\n", browser_pattern));
280 script.push_str(" _describe 'alias' aliases\n");
281 script.push_str(" _files\n");
282 script.push_str(" ;;\n");
283 }
284
285 script.push_str(" *)\n");
287 script.push_str(" _files\n");
288 script.push_str(" _describe 'alias' aliases\n");
289 script.push_str(" ;;\n");
290
291 script.push_str(" esac\n");
292 script.push_str(" ;;\n");
293 script.push_str(" esac\n");
294 script.push_str("}\n\n");
295 script.push_str("_j \"$@\"\n");
296
297 print!("{}", script);
298}
299
300fn generate_bash_completion(config: &YamlConfig) {
302 let mut all_aliases = Vec::new();
304 for s in constants::ALIAS_EXISTS_SECTIONS {
305 if let Some(map) = config.get_section(s) {
306 for key in map.keys() {
307 if !all_aliases.contains(key) {
308 all_aliases.push(key.clone());
309 }
310 }
311 }
312 }
313 all_aliases.sort();
314
315 let keywords = constants::cmd::all_keywords();
316 let all_completions: Vec<String> = keywords
317 .iter()
318 .map(|s| s.to_string())
319 .chain(all_aliases.iter().cloned())
320 .collect();
321
322 let editor_aliases: Vec<String> = config
324 .get_section(section::EDITOR)
325 .map(|m| m.keys().cloned().collect())
326 .unwrap_or_default();
327
328 let mut script = String::new();
329 script.push_str("# Bash completion for j (work-copilot)\n");
330 script.push_str("# 生成方式: eval \"$(j completion bash)\"\n");
331 script.push_str("# 或: j completion bash > /etc/bash_completion.d/j\n\n");
332 script.push_str("_j_completion() {\n");
333 script.push_str(" local cur prev words cword\n");
334 script.push_str(" _init_completion || return\n\n");
335
336 script.push_str(&format!(
337 " local commands=\"{}\"\n",
338 all_completions.join(" ")
339 ));
340 script.push_str(&format!(
341 " local aliases=\"{}\"\n",
342 all_aliases.join(" ")
343 ));
344
345 if !editor_aliases.is_empty() {
346 script.push_str(&format!(
347 " local editor_aliases=\"{}\"\n",
348 editor_aliases.join(" ")
349 ));
350 }
351
352 script.push_str("\n if [[ $cword -eq 1 ]]; then\n");
353 script.push_str(" COMPREPLY=( $(compgen -W \"$commands\" -- \"$cur\") )\n");
354 script.push_str(" return\n");
355 script.push_str(" fi\n\n");
356
357 script.push_str(" case \"${words[1]}\" in\n");
358 script.push_str(" set|s|modify|mf)\n");
359 script.push_str(" if [[ $cword -eq 2 ]]; then\n");
360 script.push_str(" COMPREPLY=( $(compgen -W \"$aliases\" -- \"$cur\") )\n");
361 script.push_str(" else\n");
362 script.push_str(" _filedir\n");
363 script.push_str(" fi\n");
364 script.push_str(" ;;\n");
365 script.push_str(" rm|remove|rename|rn|note|nt|denote|dnt|contain|find)\n");
366 script.push_str(" COMPREPLY=( $(compgen -W \"$aliases\" -- \"$cur\") )\n");
367 script.push_str(" ;;\n");
368 script.push_str(" reportctl|rctl)\n");
369 script.push_str(
370 " COMPREPLY=( $(compgen -W \"new sync push pull set-url open\" -- \"$cur\") )\n",
371 );
372 script.push_str(" ;;\n");
373
374 if !editor_aliases.is_empty() {
376 for alias in &editor_aliases {
377 script.push_str(&format!(" {})\n", alias));
378 script.push_str(" _filedir\n");
379 script.push_str(" ;;\n");
380 }
381 }
382
383 script.push_str(" *)\n");
384 script.push_str(" _filedir\n");
385 script.push_str(" COMPREPLY+=( $(compgen -W \"$aliases\" -- \"$cur\") )\n");
386 script.push_str(" ;;\n");
387 script.push_str(" esac\n");
388 script.push_str("}\n\n");
389 script.push_str("complete -F _j_completion j\n");
390
391 print!("{}", script);
392}