Skip to main content

j_cli/interactive/
shell.rs

1use crate::config::YamlConfig;
2use crate::constants::shell;
3use crate::{error, info};
4use colored::Colorize;
5
6/// 进入交互式 shell 子进程
7pub fn enter_interactive_shell(config: &YamlConfig) {
8    let os = std::env::consts::OS;
9
10    let shell_path = if os == shell::WINDOWS_OS {
11        shell::WINDOWS_CMD.to_string()
12    } else {
13        std::env::var("SHELL").unwrap_or_else(|_| shell::BASH_PATH.to_string())
14    };
15
16    info!("进入 shell 模式 ({}), 输入 exit 返回 copilot", shell_path);
17
18    let mut command = std::process::Command::new(&shell_path);
19
20    for (key, value) in config.collect_alias_envs() {
21        command.env(&key, &value);
22    }
23
24    let mut cleanup_path: Option<std::path::PathBuf> = None;
25
26    if os != shell::WINDOWS_OS {
27        let is_zsh = shell_path.contains("zsh");
28        let is_bash = shell_path.contains("bash");
29
30        if is_zsh {
31            let pid = std::process::id();
32            let tmp_dir = std::path::PathBuf::from(format!("/tmp/j_shell_zsh_{}", pid));
33            let _ = std::fs::create_dir_all(&tmp_dir);
34
35            let home = std::env::var("HOME").unwrap_or_else(|_| "~".to_string());
36            let zshrc_content = format!(
37                "# j shell 临时配置 - 自动生成,退出后自动清理\n\
38                 export ZDOTDIR=\"{home}\"\n\
39                 if [ -f \"{home}/.zshrc\" ]; then\n\
40                   source \"{home}/.zshrc\"\n\
41                 fi\n\
42                 PROMPT='%F{{green}}shell%f (%F{{cyan}}%~%f) %F{{green}}>%f '\n",
43                home = home,
44            );
45
46            let zshrc_path = tmp_dir.join(".zshrc");
47            if let Err(e) = std::fs::write(&zshrc_path, &zshrc_content) {
48                error!("创建临时 .zshrc 失败: {}", e);
49                command.env("PROMPT", "%F{green}shell%f (%F{cyan}%~%f) %F{green}>%f ");
50            } else {
51                command.env("ZDOTDIR", tmp_dir.to_str().unwrap_or("/tmp"));
52                cleanup_path = Some(tmp_dir);
53            }
54        } else if is_bash {
55            let pid = std::process::id();
56            let tmp_rc = std::path::PathBuf::from(format!("/tmp/j_shell_bashrc_{}", pid));
57
58            let home = std::env::var("HOME").unwrap_or_else(|_| "~".to_string());
59            let bashrc_content = format!(
60                "# j shell 临时配置 - 自动生成,退出后自动清理\n\
61                 if [ -f \"{home}/.bashrc\" ]; then\n\
62                   source \"{home}/.bashrc\"\n\
63                 fi\n\
64                 PS1='\\[\\033[32m\\]shell\\[\\033[0m\\] (\\[\\033[36m\\]\\w\\[\\033[0m\\]) \\[\\033[32m\\]>\\[\\033[0m\\] '\n",
65                home = home,
66            );
67
68            if let Err(e) = std::fs::write(&tmp_rc, &bashrc_content) {
69                error!("创建临时 bashrc 失败: {}", e);
70                command.env("PS1", "\\[\\033[32m\\]shell\\[\\033[0m\\] (\\[\\033[36m\\]\\w\\[\\033[0m\\]) \\[\\033[32m\\]>\\[\\033[0m\\] ");
71            } else {
72                command.arg("--rcfile");
73                command.arg(tmp_rc.to_str().unwrap_or("/tmp/j_shell_bashrc"));
74                cleanup_path = Some(tmp_rc);
75            }
76        } else {
77            command.env(
78                "PS1",
79                "\x1b[32mshell\x1b[0m (\x1b[36m\\w\x1b[0m) \x1b[32m>\x1b[0m ",
80            );
81            command.env(
82                "PROMPT",
83                "\x1b[32mshell\x1b[0m (\x1b[36m%~\x1b[0m) \x1b[32m>\x1b[0m ",
84            );
85        }
86    }
87
88    command
89        .stdin(std::process::Stdio::inherit())
90        .stdout(std::process::Stdio::inherit())
91        .stderr(std::process::Stdio::inherit());
92
93    match command.status() {
94        Ok(status) => {
95            if !status.success() {
96                if let Some(code) = status.code() {
97                    error!("shell 退出码: {}", code);
98                }
99            }
100        }
101        Err(e) => {
102            error!("启动 shell 失败: {}", e);
103        }
104    }
105
106    if let Some(path) = cleanup_path {
107        if path.is_dir() {
108            let _ = std::fs::remove_dir_all(&path);
109        } else {
110            let _ = std::fs::remove_file(&path);
111        }
112    }
113
114    info!("{}", "已返回 copilot 交互模式 🚀".green());
115}
116
117/// 执行 shell 命令(交互模式下 ! 前缀触发)
118pub fn execute_shell_command(cmd: &str, config: &YamlConfig) {
119    if cmd.is_empty() {
120        return;
121    }
122
123    let os = std::env::consts::OS;
124    let mut command = if os == shell::WINDOWS_OS {
125        let mut c = std::process::Command::new(shell::WINDOWS_CMD);
126        c.args([shell::WINDOWS_CMD_FLAG, cmd]);
127        c
128    } else {
129        let mut c = std::process::Command::new(shell::BASH_PATH);
130        c.args([shell::BASH_CMD_FLAG, cmd]);
131        c
132    };
133
134    for (key, value) in config.collect_alias_envs() {
135        command.env(&key, &value);
136    }
137
138    match command.status() {
139        Ok(status) => {
140            if !status.success() {
141                if let Some(code) = status.code() {
142                    error!("命令退出码: {}", code);
143                }
144            }
145        }
146        Err(e) => {
147            error!("执行命令失败: {}", e);
148        }
149    }
150}
151
152/// 将所有别名路径注入为当前进程的环境变量
153pub fn inject_envs_to_process(config: &YamlConfig) {
154    for (key, value) in config.collect_alias_envs() {
155        // SAFETY: 交互模式为单线程,set_var 不会引起数据竞争
156        unsafe {
157            std::env::set_var(&key, &value);
158        }
159    }
160}
161
162/// 展开字符串中的环境变量引用(支持 $VAR 和 ${VAR} 格式)
163pub fn expand_env_vars(input: &str) -> String {
164    let mut result = String::with_capacity(input.len());
165    let chars: Vec<char> = input.chars().collect();
166    let len = chars.len();
167    let mut i = 0;
168
169    while i < len {
170        if chars[i] == '$' && i + 1 < len {
171            if chars[i + 1] == '{' {
172                if let Some(end) = chars[i + 2..].iter().position(|&c| c == '}') {
173                    let var_name: String = chars[i + 2..i + 2 + end].iter().collect();
174                    if let Ok(val) = std::env::var(&var_name) {
175                        result.push_str(&val);
176                    } else {
177                        result.push_str(&input[i..i + 3 + end]);
178                    }
179                    i = i + 3 + end;
180                    continue;
181                }
182            }
183            let start = i + 1;
184            let mut end = start;
185            while end < len && (chars[end].is_alphanumeric() || chars[end] == '_') {
186                end += 1;
187            }
188            if end > start {
189                let var_name: String = chars[start..end].iter().collect();
190                if let Ok(val) = std::env::var(&var_name) {
191                    result.push_str(&val);
192                } else {
193                    let original: String = chars[i..end].iter().collect();
194                    result.push_str(&original);
195                }
196                i = end;
197                continue;
198            }
199        }
200        result.push(chars[i]);
201        i += 1;
202    }
203
204    result
205}