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