j_cli/interactive/
shell.rs1use crate::config::YamlConfig;
2use crate::constants::shell;
3use crate::{error, info};
4use colored::Colorize;
5
6pub 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}/.zshenv\" ]; then\n\
40 source \"{home}/.zshenv\"\n\
41 fi\n\
42 if [ -f \"{home}/.zprofile\" ]; then\n\
43 source \"{home}/.zprofile\"\n\
44 fi\n\
45 if [ -f \"{home}/.zshrc\" ]; then\n\
46 source \"{home}/.zshrc\"\n\
47 fi\n\
48 PROMPT='%F{{green}}shell%f (%F{{cyan}}%~%f) %F{{green}}>%f '\n",
49 home = home,
50 );
51
52 let zshrc_path = tmp_dir.join(".zshrc");
53 if let Err(e) = std::fs::write(&zshrc_path, &zshrc_content) {
54 error!("创建临时 .zshrc 失败: {}", e);
55 command.env("PROMPT", "%F{green}shell%f (%F{cyan}%~%f) %F{green}>%f ");
56 } else {
57 command.env("ZDOTDIR", tmp_dir.to_str().unwrap_or("/tmp"));
58 cleanup_path = Some(tmp_dir);
59 }
60 } else if is_bash {
61 let pid = std::process::id();
62 let tmp_rc = std::path::PathBuf::from(format!("/tmp/j_shell_bashrc_{}", pid));
63
64 let home = std::env::var("HOME").unwrap_or_else(|_| "~".to_string());
65 let bashrc_content = format!(
66 "# j shell 临时配置 - 自动生成,退出后自动清理\n\
67 if [ -f \"{home}/.bashrc\" ]; then\n\
68 source \"{home}/.bashrc\"\n\
69 fi\n\
70 PS1='\\[\\033[32m\\]shell\\[\\033[0m\\] (\\[\\033[36m\\]\\w\\[\\033[0m\\]) \\[\\033[32m\\]>\\[\\033[0m\\] '\n",
71 home = home,
72 );
73
74 if let Err(e) = std::fs::write(&tmp_rc, &bashrc_content) {
75 error!("创建临时 bashrc 失败: {}", e);
76 command.env("PS1", "\\[\\033[32m\\]shell\\[\\033[0m\\] (\\[\\033[36m\\]\\w\\[\\033[0m\\]) \\[\\033[32m\\]>\\[\\033[0m\\] ");
77 } else {
78 command.arg("--rcfile");
79 command.arg(tmp_rc.to_str().unwrap_or("/tmp/j_shell_bashrc"));
80 cleanup_path = Some(tmp_rc);
81 }
82 } else {
83 command.env(
84 "PS1",
85 "\x1b[32mshell\x1b[0m (\x1b[36m\\w\x1b[0m) \x1b[32m>\x1b[0m ",
86 );
87 command.env(
88 "PROMPT",
89 "\x1b[32mshell\x1b[0m (\x1b[36m%~\x1b[0m) \x1b[32m>\x1b[0m ",
90 );
91 }
92 }
93
94 command
95 .stdin(std::process::Stdio::inherit())
96 .stdout(std::process::Stdio::inherit())
97 .stderr(std::process::Stdio::inherit());
98
99 match command.status() {
100 Ok(status) => {
101 if !status.success() {
102 if let Some(code) = status.code() {
103 error!("shell 退出码: {}", code);
104 }
105 }
106 }
107 Err(e) => {
108 error!("启动 shell 失败: {}", e);
109 }
110 }
111
112 if let Some(path) = cleanup_path {
113 if path.is_dir() {
114 let _ = std::fs::remove_dir_all(&path);
115 } else {
116 let _ = std::fs::remove_file(&path);
117 }
118 }
119
120 info!("{}", "已返回 copilot 交互模式 🚀".green());
121}
122
123pub fn execute_shell_command(cmd: &str, config: &YamlConfig) {
125 if cmd.is_empty() {
126 return;
127 }
128
129 let os = std::env::consts::OS;
130 let mut command = if os == shell::WINDOWS_OS {
131 let mut c = std::process::Command::new(shell::WINDOWS_CMD);
132 c.args([shell::WINDOWS_CMD_FLAG, cmd]);
133 c
134 } else {
135 let mut c = std::process::Command::new(shell::BASH_PATH);
136 c.args([shell::BASH_CMD_FLAG, cmd]);
137 c
138 };
139
140 for (key, value) in config.collect_alias_envs() {
141 command.env(&key, &value);
142 }
143
144 match command.status() {
145 Ok(status) => {
146 if !status.success() {
147 if let Some(code) = status.code() {
148 error!("命令退出码: {}", code);
149 }
150 }
151 }
152 Err(e) => {
153 error!("执行命令失败: {}", e);
154 }
155 }
156}
157
158pub fn inject_envs_to_process(config: &YamlConfig) {
160 for (key, value) in config.collect_alias_envs() {
161 unsafe {
163 std::env::set_var(&key, &value);
164 }
165 }
166}
167
168pub fn expand_env_vars(input: &str) -> String {
170 let mut result = String::with_capacity(input.len());
171 let chars: Vec<char> = input.chars().collect();
172 let len = chars.len();
173 let mut i = 0;
174
175 while i < len {
176 if chars[i] == '$' && i + 1 < len {
177 if chars[i + 1] == '{' {
178 if let Some(end) = chars[i + 2..].iter().position(|&c| c == '}') {
179 let var_name: String = chars[i + 2..i + 2 + end].iter().collect();
180 if let Ok(val) = std::env::var(&var_name) {
181 result.push_str(&val);
182 } else {
183 result.push_str(&input[i..i + 3 + end]);
184 }
185 i = i + 3 + end;
186 continue;
187 }
188 }
189 let start = i + 1;
190 let mut end = start;
191 while end < len && (chars[end].is_alphanumeric() || chars[end] == '_') {
192 end += 1;
193 }
194 if end > start {
195 let var_name: String = chars[start..end].iter().collect();
196 if let Ok(val) = std::env::var(&var_name) {
197 result.push_str(&val);
198 } else {
199 let original: String = chars[i..end].iter().collect();
200 result.push_str(&original);
201 }
202 i = end;
203 continue;
204 }
205 }
206 result.push(chars[i]);
207 i += 1;
208 }
209
210 result
211}