atmos/apps/terminal/
shell.rs1use crate::apps::terminal::Terminal;
8use crate::kernel::keyboard::Keyboard;
9use alloc::string::String;
10use alloc::vec::Vec;
11
12fn expand_aliases(cmd_line: &str) -> String {
25 let trimmed = cmd_line.trim();
26 if trimmed.is_empty() {
27 return String::new();
28 }
29
30 let has_symbols = trimmed
32 .chars()
33 .any(|c| c == '(' || c == ')' || c == '{' || c == '}' || c == '=' || c == ',');
34 if has_symbols {
35 return String::from(trimmed);
36 }
37
38 let tokens: Vec<&str> = trimmed.split_whitespace().collect();
40 if tokens.is_empty() {
41 return String::from(trimmed);
42 }
43
44 let cmd = tokens[0];
45 let args = &tokens[1..];
46
47 match cmd {
48 "ls" => {
49 if args.is_empty() {
50 String::from("fs.ls()")
51 } else {
52 alloc::format!("fs.ls(\"{}\")", args[0])
53 }
54 }
55 "cd" => {
56 if args.is_empty() {
57 String::from("fs.cd(\"/user/\")")
58 } else {
59 alloc::format!("fs.cd(\"{}\")", args[0])
60 }
61 }
62 "pwd" => String::from("fs.pwd()"),
63 "cat" => {
64 if args.is_empty() {
65 String::from("print(\"USAGE: cat <file>\")")
66 } else {
67 alloc::format!("fs.cat(\"{}\")", args[0])
68 }
69 }
70 "mkdir" => {
71 if args.is_empty() {
72 String::from("print(\"USAGE: mkdir <dir>\")")
73 } else {
74 alloc::format!("fs.mkdir(\"{}\")", args[0])
75 }
76 }
77 "rm" => {
78 if args.is_empty() {
79 String::from("print(\"USAGE: rm <file>\")")
80 } else {
81 alloc::format!("fs.rm(\"{}\")", args[0])
82 }
83 }
84 "help" => String::from("help()"),
85 "apps" => String::from("os.apps()"),
86 "run" => {
87 if args.is_empty() {
88 String::from("print(\"USAGE: run <app>\")")
89 } else {
90 alloc::format!("os.run(\"{}\")", args[0])
91 }
92 }
93 "drive" => match args.first().copied() {
95 Some("ls") => match args.get(1) {
96 Some(folder) => alloc::format!("drive.ls(\"{}\")", folder),
97 None => String::from("drive.ls()"),
98 },
99 Some("cat") if args.len() > 1 => alloc::format!("drive.cat(\"{}\")", args[1]),
100 Some("download") if args.len() > 2 => {
101 alloc::format!("drive.download(\"{}\", \"{}\")", args[1], args[2])
102 }
103 Some("rm") if args.len() > 1 => alloc::format!("drive.rm(\"{}\")", args[1]),
104 _ => String::from(
105 "print(\"USAGE: drive ls [folder_id] | drive cat <file_id> | drive download <file_id> <local_name> | drive rm <file_id>\")",
106 ),
107 },
108 "gh" => match args.first().copied() {
110 Some("user") => String::from("gh.user()"),
111 Some("repos") => String::from("gh.repos()"),
112 Some("poll") => String::from("gh.poll()"),
113 Some("logout") => String::from("gh.logout()"),
114 Some("login") if args.len() > 1 => alloc::format!("gh.login(\"{}\")", args[1]),
115 _ => String::from(
116 "print(\"USAGE: gh login <client_id> | gh poll | gh user | gh repos | gh logout\")",
117 ),
118 },
119 _ => {
120 let app_name = cmd;
122 let manifest_path = alloc::format!("/apps/{}/manifest.json", app_name);
123 if crate::kernel::fs::get_fs()
124 .read_file(&manifest_path)
125 .is_some()
126 {
127 if args.is_empty() {
128 alloc::format!("os.run(\"{}\")", app_name)
129 } else {
130 let mut run_cmd = alloc::format!("os.run(\"{}\"", app_name);
131 for arg in args {
132 run_cmd.push_str(&alloc::format!(", \"{}\"", arg));
133 }
134 run_cmd.push(')');
135 run_cmd
136 }
137 } else {
138 String::from(trimmed)
139 }
140 }
141 }
142}
143
144pub fn execute_shell_command(cmd_line: &str, terminal: &mut Terminal, _kbd: &mut Keyboard) {
156 let trimmed = cmd_line.trim();
157 if trimmed.is_empty() {
158 return;
159 }
160
161 if trimmed == "clear" {
163 terminal.clear_logs();
164 return;
165 }
166
167 if trimmed == "history" || trimmed == "h" {
169 crate::info!("[Terminal] Executing local history command: {}", trimmed);
170 terminal.show_history();
171 return;
172 }
173
174 if let Some(rest) = trimmed.strip_prefix("h(").and_then(|s| s.strip_suffix(')')) {
176 match rest.trim().parse::<usize>() {
177 Ok(0) | Err(_) => {
178 terminal.print_line(String::from(" h(番号): 1 以上の数値を指定してください"));
179 }
180 Ok(n) => match terminal.history.get(n - 1).cloned() {
181 None => {
182 terminal.print_line(alloc::format!(" h({}): ヒストリにありません(範囲: 1–{})", n, terminal.history.len()));
183 }
184 Some(cmd) => {
185 terminal.print_line(alloc::format!(" → {}", cmd));
186 if terminal.history.last() != Some(&cmd) {
187 terminal.history.push(cmd.clone());
188 }
189 terminal.history_index = terminal.history.len();
190 if let Some(ctx) = &terminal.context {
191 ctx.cmd_queue.push(expand_aliases(&cmd));
192 }
193 }
194 },
195 }
196 return;
197 }
198
199 let resolved_cmd = expand_aliases(trimmed);
200 if resolved_cmd.is_empty() {
201 return;
202 }
203
204 if let Some(ctx) = &terminal.context {
206 ctx.cmd_queue.push(resolved_cmd);
207 }
208}