use crate::assets::version_template;
use crate::config::YamlConfig;
use crate::constants::{
ALIAS_EXISTS_SECTIONS, ALL_SECTIONS, CONTAIN_SEARCH_SECTIONS, INSTALL_SOURCE, VERSION,
cmd::all_keywords, config_key, section,
};
use crate::{error, info, md, usage};
use colored::Colorize;
pub fn handle_version() {
let text = version_template()
.replace("{version}", VERSION)
.replace("{os}", std::env::consts::OS)
.replace("{arch}", std::env::consts::ARCH)
.replace(
"{data_dir}",
YamlConfig::data_dir().to_str().unwrap_or_default(),
)
.replace("{install_source}", INSTALL_SOURCE);
md!("{}", text);
}
pub fn handle_exit() {
info!("Bye~ 👋");
std::process::exit(0);
}
pub fn handle_log(key: &str, value: &str, config: &mut YamlConfig) {
if key == config_key::MODE {
let mode = if value == config_key::VERBOSE {
config_key::VERBOSE
} else {
config_key::CONCISE
};
config.set_property(section::LOG, config_key::MODE, mode);
info!("☑️ 日志模式已切换为: {}", mode);
} else {
usage!("j log mode <verbose|concise>");
}
}
pub fn handle_clear() {
print!("\x1B[2J\x1B[1;1H");
}
pub fn handle_contain(alias: &str, containers: Option<&str>, config: &YamlConfig) {
let sections: Vec<&str> = match containers {
Some(c) => c.split(',').collect(),
None => CONTAIN_SEARCH_SECTIONS.to_vec(),
};
let mut found = Vec::new();
for section in §ions {
if config.contains(section, alias)
&& let Some(value) = config.get_property(section, alias)
{
found.push(format!(
"{} {}: {}",
format!("[{}]", section).green(),
alias,
value
));
}
}
if found.is_empty() {
info!("nothing found 😢");
} else {
info!("找到 {} 条结果 😊", found.len().to_string().green());
for line in &found {
info!("{}", line);
}
}
}
pub fn handle_config(part: &str, field: &str, value: &str, config: &mut YamlConfig) {
if config.get_section(part).is_none() {
error!("✖️ 在配置文件中未找到该 section:{}", part);
return;
}
let old_value = config.get_property(part, field).cloned();
config.set_property(part, field, value);
match old_value {
Some(old) => {
info!(
"☑️ 已修改 {}.{} 的值为 {},旧值为 {}",
part, field, value, old
);
}
None => {
info!("☑️ 已新增 {}.{} = {}", part, field, value);
}
}
info!(
"🚧 此命令可能会导致配置文件属性错乱而使 Copilot 无法正常使用,请确保在您清楚在做什么的情况下使用"
);
}
pub fn handle_completion(shell_type: Option<&str>, config: &YamlConfig) {
let shell = shell_type.unwrap_or("zsh");
match shell {
"zsh" => generate_zsh_completion(config),
"bash" => generate_bash_completion(config),
_ => {
error!("✖️ 不支持的 shell 类型: {},可选: zsh, bash", shell);
usage!("j completion [zsh|bash]");
}
}
}
fn generate_zsh_completion(config: &YamlConfig) {
let mut all_aliases = Vec::new();
for s in ALIAS_EXISTS_SECTIONS {
if let Some(map) = config.get_section(s) {
for key in map.keys() {
if !all_aliases.contains(key) {
all_aliases.push(key.clone());
}
}
}
}
all_aliases.sort();
let editor_aliases: Vec<String> = config
.get_section(section::EDITOR)
.map(|m| m.keys().cloned().collect())
.unwrap_or_default();
let browser_aliases: Vec<String> = config
.get_section(section::BROWSER)
.map(|m| m.keys().cloned().collect())
.unwrap_or_default();
let keywords = all_keywords();
let subcmds = keywords.to_vec();
let subcmds_str = subcmds.join(" ");
let aliases_str = all_aliases.join(" ");
let editor_pattern = if editor_aliases.is_empty() {
String::new()
} else {
editor_aliases.join("|")
};
let browser_pattern = if browser_aliases.is_empty() {
String::new()
} else {
browser_aliases.join("|")
};
let mut script = String::new();
script.push_str("#compdef j\n");
script.push_str("# Zsh completion for j (work-copilot)\n");
script.push_str("# 生成方式: eval \"$(j completion zsh)\"\n");
script.push_str(
"# 或: j completion zsh > ~/.zsh/completions/_j && fpath=(~/.zsh/completions $fpath)\n\n",
);
script.push_str("_j() {\n");
script.push_str(" local curcontext=\"$curcontext\" state line\n");
script.push_str(" typeset -A opt_args\n\n");
script.push_str(&format!(" local -a subcmds=({})\n", subcmds_str));
script.push_str(&format!(" local -a aliases=({})\n", aliases_str));
if !editor_pattern.is_empty() {
script.push_str(&format!(
" local -a editor_aliases=({})\n",
editor_aliases.join(" ")
));
}
script.push_str("\n _arguments -C \\\n");
script.push_str(" '1: :->cmd' \\\n");
script.push_str(" '*: :->args'\n\n");
script.push_str(" case $state in\n");
script.push_str(" cmd)\n");
script.push_str(" _describe 'command' subcmds\n");
script.push_str(" _describe 'alias' aliases\n");
script.push_str(" ;;\n");
script.push_str(" args)\n");
script.push_str(" case $words[2] in\n");
script.push_str(" set|s|modify|mf)\n");
script.push_str(" if (( CURRENT == 3 )); then\n");
script.push_str(" _describe 'alias' aliases\n");
script.push_str(" else\n");
script.push_str(" _files\n");
script.push_str(" fi\n");
script.push_str(" ;;\n");
script.push_str(" rm|remove|rename|rn|tag|t|untag|ut|contain|find)\n");
script.push_str(" _describe 'alias' aliases\n");
script.push_str(" ;;\n");
let sections_str = ALL_SECTIONS.join(" ");
script.push_str(" ls|list)\n");
script.push_str(&format!(
" local -a sections=(all {})\n",
sections_str
));
script.push_str(" _describe 'section' sections\n");
script.push_str(" ;;\n");
script.push_str(" reportctl|rctl)\n");
script
.push_str(" local -a rctl_actions=(new sync push pull set-url open)\n");
script.push_str(" _describe 'action' rctl_actions\n");
script.push_str(" ;;\n");
script.push_str(" log)\n");
script.push_str(" if (( CURRENT == 3 )); then\n");
script.push_str(" local -a log_keys=(mode)\n");
script.push_str(" _describe 'key' log_keys\n");
script.push_str(" else\n");
script.push_str(" local -a log_values=(verbose concise)\n");
script.push_str(" _describe 'value' log_values\n");
script.push_str(" fi\n");
script.push_str(" ;;\n");
script.push_str(" config|cfg)\n");
script.push_str(&format!(
" local -a sections=({})\n",
sections_str
));
script.push_str(" _describe 'section' sections\n");
script.push_str(" ;;\n");
script.push_str(" time)\n");
script.push_str(" local -a time_funcs=(countdown)\n");
script.push_str(" _describe 'function' time_funcs\n");
script.push_str(" ;;\n");
script.push_str(" completion)\n");
script.push_str(" local -a shells=(zsh bash)\n");
script.push_str(" _describe 'shell' shells\n");
script.push_str(" ;;\n");
if !editor_pattern.is_empty() {
script.push_str(&format!(" {})\n", editor_pattern));
script.push_str(" _files\n");
script.push_str(" ;;\n");
}
if !browser_pattern.is_empty() {
script.push_str(&format!(" {})\n", browser_pattern));
script.push_str(" _describe 'alias' aliases\n");
script.push_str(" _files\n");
script.push_str(" ;;\n");
}
script.push_str(" *)\n");
script.push_str(" _files\n");
script.push_str(" _describe 'alias' aliases\n");
script.push_str(" ;;\n");
script.push_str(" esac\n");
script.push_str(" ;;\n");
script.push_str(" esac\n");
script.push_str("}\n\n");
script.push_str("_j \"$@\"\n");
print!("{}", script);
}
fn generate_bash_completion(config: &YamlConfig) {
let mut all_aliases = Vec::new();
for s in ALIAS_EXISTS_SECTIONS {
if let Some(map) = config.get_section(s) {
for key in map.keys() {
if !all_aliases.contains(key) {
all_aliases.push(key.clone());
}
}
}
}
all_aliases.sort();
let keywords = all_keywords();
let all_completions: Vec<String> = keywords
.iter()
.map(|s| s.to_string())
.chain(all_aliases.iter().cloned())
.collect();
let editor_aliases: Vec<String> = config
.get_section(section::EDITOR)
.map(|m| m.keys().cloned().collect())
.unwrap_or_default();
let mut script = String::new();
script.push_str("# Bash completion for j (work-copilot)\n");
script.push_str("# 生成方式: eval \"$(j completion bash)\"\n");
script.push_str("# 或: j completion bash > /etc/bash_completion.d/j\n\n");
script.push_str("_j_completion() {\n");
script.push_str(" local cur prev words cword\n");
script.push_str(" _init_completion || return\n\n");
script.push_str(&format!(
" local commands=\"{}\"\n",
all_completions.join(" ")
));
script.push_str(&format!(
" local aliases=\"{}\"\n",
all_aliases.join(" ")
));
if !editor_aliases.is_empty() {
script.push_str(&format!(
" local editor_aliases=\"{}\"\n",
editor_aliases.join(" ")
));
}
script.push_str("\n if [[ $cword -eq 1 ]]; then\n");
script.push_str(" COMPREPLY=( $(compgen -W \"$commands\" -- \"$cur\") )\n");
script.push_str(" return\n");
script.push_str(" fi\n\n");
script.push_str(" case \"${words[1]}\" in\n");
script.push_str(" set|s|modify|mf)\n");
script.push_str(" if [[ $cword -eq 2 ]]; then\n");
script.push_str(" COMPREPLY=( $(compgen -W \"$aliases\" -- \"$cur\") )\n");
script.push_str(" else\n");
script.push_str(" _filedir\n");
script.push_str(" fi\n");
script.push_str(" ;;\n");
script.push_str(" rm|remove|rename|rn|tag|t|untag|ut|contain|find)\n");
script.push_str(" COMPREPLY=( $(compgen -W \"$aliases\" -- \"$cur\") )\n");
script.push_str(" ;;\n");
script.push_str(" reportctl|rctl)\n");
script.push_str(
" COMPREPLY=( $(compgen -W \"new sync push pull set-url open\" -- \"$cur\") )\n",
);
script.push_str(" ;;\n");
if !editor_aliases.is_empty() {
for alias in &editor_aliases {
script.push_str(&format!(" {})\n", alias));
script.push_str(" _filedir\n");
script.push_str(" ;;\n");
}
}
script.push_str(" *)\n");
script.push_str(" _filedir\n");
script.push_str(" COMPREPLY+=( $(compgen -W \"$aliases\" -- \"$cur\") )\n");
script.push_str(" ;;\n");
script.push_str(" esac\n");
script.push_str("}\n\n");
script.push_str("complete -F _j_completion j\n");
print!("{}", script);
}