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}/.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
117pub 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
152pub fn inject_envs_to_process(config: &YamlConfig) {
154 for (key, value) in config.collect_alias_envs() {
155 unsafe {
157 std::env::set_var(&key, &value);
158 }
159 }
160}
161
162pub 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}