simple_shell/
lib.rs

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 the remaining text
98                    print!(self.write, "{}", &self.command[self.cursor - 1..]);
99                    // Move cursor to the correct position
100                    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"); // Move cursor left
121            print!(self.write, "{}", &self.command[self.cursor..]); // Print the remaining text
122            print!(self.write, " "); // Clear last character
123            print!(self.write, "\x1b[{}D", self.command.len() - self.cursor + 1);
124            // Move cursor to the correct position
125        }
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}