1#![no_std]
2extern crate alloc;
3
4use alloc::{
5 collections::BTreeMap,
6 string::{String, ToString},
7 vec::Vec,
8};
9
10mod constants;
11mod reader;
12mod writer;
13use constants::*;
14use reader::*;
15use writer::*;
16
17#[derive(Clone, Copy)]
18pub struct ShellCommand<'a> {
19 pub help: &'a str,
20 pub func: fn(&[&str], &mut Shell<'a>) -> Result<(), &'a str>,
21 pub aliases: &'a [&'a str],
22}
23
24pub struct Shell<'a> {
25 pub history: Vec<String>,
26 pub commands: BTreeMap<&'a str, ShellCommand<'a>>,
27 pub command: String,
28 pub cursor: usize,
29 write: Writer,
30 read: fn() -> Option<u8>,
31}
32
33impl<'a> Shell<'a> {
34 pub fn new(write: fn(&str), read: fn() -> Option<u8>) -> Self {
35 Self {
36 history: Vec::new(),
37 commands: BTreeMap::new(),
38 command: String::new(),
39 cursor: 0,
40 write: Writer::new(write),
41 read,
42 }
43 }
44
45 fn get_char(&mut self) -> u8 {
46 loop {
47 if let Some(c) = (self.read)() {
48 return c;
49 }
50 }
51 }
52
53 async fn get_char_async(&mut self) -> u8 {
54 (Reader::new(self.read)).await
55 }
56
57 pub fn with_commands(mut self, mut commands: BTreeMap<&'a str, ShellCommand<'a>>) -> Self {
58 self.commands.append(&mut commands);
59 self
60 }
61
62 pub fn run(&mut self) {
63 self.print_prompt();
64
65 loop {
66 let c = self.get_char();
67 match c {
68 ESCAPE => self.handle_escape(),
69 _ => self.match_char(c),
70 }
71 }
72 }
73
74 pub async fn run_async(&mut self) {
75 self.print_prompt();
76
77 loop {
78 let c = self.get_char_async().await;
79 match c {
80 ESCAPE => self.handle_escape_async().await,
81 _ => self.match_char(c),
82 }
83 }
84 }
85
86 fn match_char(&mut self, b: u8) {
87 match b {
88 CTRL_C => self.process_command("exit".to_string()),
89 CTRL_L => self.handle_clear(),
90 ENTER => self.handle_enter(),
91 BACKSPACE => self.handle_backspace(),
92 c if (32..=126).contains(&c) => {
93 self.command.insert(self.cursor, c as char);
94 self.cursor += 1;
95
96 if self.cursor < self.command.len() {
97 print!(self.write, "{}", &self.command[self.cursor - 1..]);
99 print!(self.write, "\x1b[{}D", self.command.len() - self.cursor);
101 } else {
102 print!(self.write, "{}", c as char);
103 }
104 }
105 _ => {}
106 }
107 }
108
109 fn handle_clear(&mut self) {
110 self.clear_screen();
111 self.print_prompt();
112 print!(self.write, "{}", self.command);
113 self.cursor = self.command.len();
114 }
115
116 fn handle_backspace(&mut self) {
117 if self.cursor > 0 {
118 self.command.remove(self.cursor - 1);
119 self.cursor -= 1;
120 print!(self.write, "\x08"); print!(self.write, "{}", &self.command[self.cursor..]); print!(self.write, " "); print!(self.write, "\x1b[{}D", self.command.len() - self.cursor + 1);
124 }
126 }
127
128 fn handle_enter(&mut self) {
129 println!(self.write, "");
130 self.process_command(self.command.clone());
131 self.history.push(self.command.clone());
132 self.command.clear();
133 self.cursor = 0;
134 self.print_prompt();
135 }
136 async fn handle_escape_async(&mut self) {
137 if self.get_char_async().await != CSI {
138 return;
139 }
140 let b = self.get_char_async().await;
141 self._handle_escape(b);
142 }
143
144 fn handle_escape(&mut self) {
145 if self.get_char() != CSI {
146 return;
147 }
148 let b = self.get_char();
149 self._handle_escape(b);
150 }
151
152 fn _handle_escape(&mut self, b: u8) {
153 match b {
154 CSI_UP => {}
155 CSI_DOWN => {}
156 CSI_RIGHT => {
157 if self.cursor < self.command.len() {
158 print!(self.write, "\x1b[1C");
159 self.cursor += 1;
160 }
161 }
162 CSI_LEFT => {
163 if self.cursor > 0 {
164 print!(self.write, "\x1b[1D");
165 self.cursor -= 1;
166 }
167 }
168 _ => {}
169 }
170 }
171
172 fn process_command(&mut self, command: String) {
173 let mut args = command.split_whitespace();
174 let command = args.next().unwrap_or("");
175 let args = args.collect::<Vec<_>>();
176
177 for (name, shell_command) in &self.commands {
178 if shell_command.aliases.contains(&command) || name == &command {
179 return (shell_command.func)(&args, self).unwrap_or_else(|err| {
180 println!(self.write, "{}: {}", command, err);
181 });
182 }
183 }
184
185 if command.is_empty() {
186 return;
187 }
188
189 println!(self.write, "{}: command not found", command);
190 }
191
192 pub fn print_help_screen(&mut self) {
193 println!(self.write, "available commands:");
194 for (name, command) in &self.commands {
195 print!(self.write, " {:<12}{:<25}", name, command.help);
196 if !command.aliases.is_empty() {
197 print!(self.write, " aliases: {}", command.aliases.join(", "));
198 }
199 println!(self.write, "");
200 }
201 }
202
203 pub fn print_prompt(&mut self) {
204 print!(self.write, "> ");
205 }
206
207 pub fn clear_screen(&mut self) {
208 print!(self.write, "\x1b[2J\x1b[1;1H");
209 }
210}