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