From 333ba12836e6137f1d74749df685834e9739e27e Mon Sep 17 00:00:00 2001 From: Antoine Clarman Date: Wed, 17 Jun 2026 12:28:44 +0200 Subject: [PATCH] feat(console): add multilines --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/driver/console/mod.rs | 145 +++++++++++++++++++++++++++---------- src/driver/keyboard/ps2.rs | 8 +- src/driver/shell/mod.rs | 65 ++++++++++------- src/driver/vga/mod.rs | 1 - 6 files changed, 153 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1dd8c35..c397b48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,4 +4,4 @@ version = 4 [[package]] name = "gratos" -version = "0.4.2" +version = "0.4.3" diff --git a/Cargo.toml b/Cargo.toml index e74d080..7bf4190 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gratos" -version = "0.4.2" +version = "0.4.3" edition = "2024" description = "kernel from scratch" repository = "https://github.com/alisterd51/gratOS" diff --git a/src/driver/console/mod.rs b/src/driver/console/mod.rs index 41c5a9c..fd6e3e0 100644 --- a/src/driver/console/mod.rs +++ b/src/driver/console/mod.rs @@ -3,7 +3,7 @@ mod history; use super::{ shell, vga::{ - BUFFER_HEIGHT, BUFFER_WIDTH, Color, ColorCode, Line, ScreenChar, + BUFFER_HEIGHT, BUFFER_WIDTH, Color, ColorCode, ScreenChar, text_mode::{Writer, set_cursor}, }, }; @@ -17,6 +17,7 @@ const DEFAULT_FOREFROUND_COLOR: Color = Color::LightGray; const DEFAULT_BACKFROUND_COLOR: Color = Color::Black; const DEFAULT_COLOR_CODE: ColorCode = ColorCode::new(DEFAULT_FOREFROUND_COLOR, DEFAULT_BACKFROUND_COLOR); +pub const INPUT_BUFFER_SIZE: usize = 4096; // https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit // escape sequences: @@ -78,6 +79,8 @@ struct ConsoleDescriptor { row_position: usize, column_position: usize, color_code: ColorCode, + input_buffer: [u8; INPUT_BUFFER_SIZE], + input_length: usize, } impl ConsoleDescriptor { @@ -86,17 +89,20 @@ impl ConsoleDescriptor { row_position: 0, column_position: 0, color_code: DEFAULT_COLOR_CODE, + input_buffer: [0; INPUT_BUFFER_SIZE], + input_length: 0, } } } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] enum CsiParam { Undefined, Defined(u32), Invalid, } +#[derive(PartialEq)] enum EscapeState { Normal, Esc, @@ -112,6 +118,7 @@ struct Console { } impl Console { + #[allow(clippy::large_stack_arrays)] const fn new() -> Self { Self { escape_state: EscapeState::Normal, @@ -122,21 +129,82 @@ impl Console { } } - fn apply_byte(&mut self, byte: u8) { + pub fn handle_keyboard_input(&mut self, byte: u8) { match byte { b'\n' | b'\r' => { if self.history.end_line().is_ok() { self.update_screen(); } - let line = self.get_current_line(); - shell::add_line_to_buf(self.id, &line); + + let desc = &mut self.descriptors[self.id]; + let input = &desc.input_buffer[..desc.input_length]; + + crate::driver::shell::add_line_to_buf(self.id, input); + self.descriptors[self.id].input_length = 0; self.new_line(); } - b'\t' => self.write_string(" "), - 0x08 => self.backspace(), - 0x7F => self.delete(), - 0x1B => self.escape_state = EscapeState::Esc, - byte @ b' '..=b'~' => self.apply_escape_byte(byte), + 0x08 | 0x7F => { + let desc = &mut self.descriptors[self.id]; + + if desc.input_length > 0 { + desc.input_length -= 1; + + if desc.column_position > 0 { + desc.column_position -= 1; + } else if desc.row_position > 0 { + desc.row_position -= 1; + desc.column_position = BUFFER_WIDTH - 1; + } + + let col = desc.column_position; + let row = desc.row_position; + let blank = ScreenChar { + ascii_character: b' ', + color_code: desc.color_code, + }; + self.writer.set_char(&blank, col, row); + self.history.set_char(blank, col, row); + self.update_cursor(); + } + } + byte @ b' '..=b'~' => { + let desc = &mut self.descriptors[self.id]; + if desc.input_length < INPUT_BUFFER_SIZE { + desc.input_buffer[desc.input_length] = byte; + desc.input_length += 1; + self.write_byte(byte); + } + } + _ => {} + } + } + + fn apply_byte(&mut self, byte: u8) { + match byte { + b'\n' | b'\r' => { + self.new_line(); + return; + } + b'\t' => { + self.write_string(" "); + return; + } + 0x08 | 0x7F => { + self.backspace(); + return; + } + 0x1B => { + self.escape_state = EscapeState::Esc; + return; + } + _ => {} + } + if self.escape_state != EscapeState::Normal { + self.apply_escape_byte(byte); + return; + } + match byte { + byte @ b' '..=b'~' => self.write_byte(byte), _ => self.write_byte(0xFE), } } @@ -248,27 +316,40 @@ impl Console { if self.history.end_line().is_ok() { self.update_screen(); } - if self.descriptors[self.id].column_position > shell::ps1().len() { - self.descriptors[self.id].column_position -= 1; - self.write_ascii(b' '); - self.update_cursor(); - } - } - fn delete(&mut self) { - if self.history.end_line().is_ok() { - self.update_screen(); + let desc = &mut self.descriptors[self.id]; + + if desc.input_length > 0 { + desc.input_length -= 1; + if desc.column_position > 0 { + desc.column_position -= 1; + } else if desc.row_position > 0 { + desc.row_position -= 1; + desc.column_position = BUFFER_WIDTH - 1; + } + let col = desc.column_position; + let row = desc.row_position; + let blank = ScreenChar { + ascii_character: b' ', + color_code: desc.color_code, + }; + self.writer.set_char(&blank, col, row); + self.history.set_char(blank, col, row); + self.update_cursor(); } - self.write_ascii(b' '); } fn write_byte(&mut self, byte: u8) { if self.history.end_line().is_ok() { self.update_screen(); } - if self.descriptors[self.id].column_position + 1 < BUFFER_WIDTH { - self.write_ascii(byte); - self.descriptors[self.id].column_position += 1; + if self.descriptors[self.id].column_position >= BUFFER_WIDTH { + self.new_line(); + } + self.write_ascii(byte); + self.descriptors[self.id].column_position += 1; + if self.descriptors[self.id].column_position >= BUFFER_WIDTH { + self.new_line(); } self.update_cursor(); } @@ -286,20 +367,6 @@ impl Console { self.history.set_char(c, col, row); } - fn get_current_line(&self) -> Line { - let mut new_line = [0u8; BUFFER_WIDTH]; - if let Ok(line) = self - .history - .get_line(self.descriptors[self.id].row_position) - { - for (new_char, screen_char) in new_line.iter_mut().zip(line.iter()) { - *new_char = screen_char.ascii_character; - } - } - - new_line - } - fn new_line(&mut self) { if self.descriptors[self.id].row_position < BUFFER_HEIGHT - 1 { self.descriptors[self.id].row_position += 1; @@ -450,6 +517,10 @@ pub fn get_tty_id() -> usize { WRITER.lock().get_tty_id() } +pub fn push_keyboard_byte(byte: u8) { + WRITER.lock().handle_keyboard_input(byte); +} + pub fn test_colors() { const FGS: [&str; 16] = [ FG_BLACK, diff --git a/src/driver/keyboard/ps2.rs b/src/driver/keyboard/ps2.rs index e258601..5fa7971 100644 --- a/src/driver/keyboard/ps2.rs +++ b/src/driver/keyboard/ps2.rs @@ -98,11 +98,11 @@ impl Keyboard { fn interpret_keymapvalue(&mut self, keymap_value: KeymapValue, pressed: bool) { match keymap_value { KeymapValue::Ascii(c) | KeymapValue::Lowercase(c) | KeymapValue::Alt(c) if pressed => { - print!("{c}"); + console::push_keyboard_byte(c as u8); } KeymapValue::Control(c) | KeymapValue::ControlAlt(c) if pressed => { - let c = ((c as u8) & 0x3F) as char; - print!("{c}"); + let c = (c as u8) & 0x3F; + console::push_keyboard_byte(c); } KeymapValue::CapsLock if pressed => { self.key_modifier.caps_lock = !self.key_modifier.caps_lock; @@ -120,7 +120,7 @@ impl Keyboard { KeymapValue::RightShift => self.key_modifier.right_shift = pressed, KeymapValue::RightAlt => self.key_modifier.alt_gr = pressed, KeymapValue::Delete if pressed => { - print!("{}", 0x7Fu8 as char); + console::push_keyboard_byte(0x08); } KeymapValue::Right | KeymapValue::AltRight | KeymapValue::ControlRight if pressed => { print!("{CURSOR_RIGHT}"); diff --git a/src/driver/shell/mod.rs b/src/driver/shell/mod.rs index 19f139f..3e8e80b 100644 --- a/src/driver/shell/mod.rs +++ b/src/driver/shell/mod.rs @@ -1,12 +1,11 @@ mod debug; mod hexdump; -use super::{ - console::{self, NUMBER_OF_REGULAR_TTY}, - vga::{BUFFER_WIDTH, Line}, -}; +use super::console::{self, NUMBER_OF_REGULAR_TTY}; use crate::{ - bootprotocol, gdt, memory, + bootprotocol, + driver::console::INPUT_BUFFER_SIZE, + gdt, memory, mutex::Mutex, power::{halt, reboot, shutdown}, print, println, @@ -28,15 +27,12 @@ impl State { } } -fn parse_command(line: &Line) -> Option<&str> { - let line = &line[PS1.len()..]; - let end = line.iter().position(|&c| c == b'\0').unwrap_or(line.len()); - let command = &line[..end]; - core::str::from_utf8(command).ok().map(str::trim) +fn parse_command(buffer: &[u8]) -> Option<&str> { + core::str::from_utf8(buffer).ok().map(str::trim) } -fn execute_command(line: &Line) { - if let Some(command) = parse_command(line) { +fn execute_command(buffer: &[u8]) { + if let Some(command) = parse_command(buffer) { let mut parts = command.split_whitespace(); let command = parts.next().unwrap_or(""); let arg = parts.next(); @@ -114,7 +110,7 @@ fn execute_command(line: &Line) { asm!("ud2", options(nomem, nostack)); }, "" => {} - _ => println!("command not found"), + cmd => println!("command not found: {cmd}"), } } } @@ -122,19 +118,27 @@ fn execute_command(line: &Line) { #[derive(Clone, Copy)] struct Shell { state: State, - line: Line, + line_buffer: [u8; INPUT_BUFFER_SIZE], + line_len: usize, } impl Shell { const fn new() -> Self { Self { state: State::new(), - line: [b'\0'; BUFFER_WIDTH], + line_buffer: [b'\0'; INPUT_BUFFER_SIZE], + line_len: 0, } } - const fn input_line(&mut self, line: &Line) { - self.line = *line; + fn input_line(&mut self, line: &[u8]) { + let len = line.len().min(INPUT_BUFFER_SIZE); + let mut i = 0; + while i < len { + self.line_buffer[i] = line[i]; + i += 1; + } + self.line_len = len; } } @@ -147,14 +151,16 @@ fn output_prompt() { pub fn initialize(id: usize) { let mut shell = SHELLS[id].lock(); + if shell.state == State::Uninitialized { output_prompt(); shell.state = State::Waiting; } } -pub fn add_line_to_buf(id: usize, line: &Line) { +pub fn add_line_to_buf(id: usize, line: &[u8]) { let mut shell = SHELLS[id].lock(); + if shell.state == State::Waiting { shell.input_line(line); shell.state = State::Ready; @@ -162,22 +168,29 @@ pub fn add_line_to_buf(id: usize, line: &Line) { } pub fn interpret(id: usize) { - let (state, line) = { + let mut local_buffer = [0u8; INPUT_BUFFER_SIZE]; + let local_len; + let state; + + { let shell = SHELLS[id].lock(); - (shell.state, shell.line) - }; + state = shell.state; + local_len = shell.line_len; + + let mut i = 0; + while i < local_len { + local_buffer[i] = shell.line_buffer[i]; + i += 1; + } + } if state == State::Ready { - execute_command(&line); + execute_command(&local_buffer[..local_len]); output_prompt(); SHELLS[id].lock().state = State::Waiting; } } -pub const fn ps1() -> &'static str { - PS1 -} - // termcaps: not implemented yet pub const fn up(_id: usize) {} diff --git a/src/driver/vga/mod.rs b/src/driver/vga/mod.rs index 59e0d61..900a03d 100644 --- a/src/driver/vga/mod.rs +++ b/src/driver/vga/mod.rs @@ -48,4 +48,3 @@ pub struct ScreenChar { pub type ScreenCharLine = [ScreenChar; BUFFER_WIDTH]; pub type Screen = [ScreenCharLine; BUFFER_HEIGHT]; -pub type Line = [u8; BUFFER_WIDTH];