From 1588b4fbf39635e13670e30f77d049759ee2a75c Mon Sep 17 00:00:00 2001 From: FantasyPvP <80643031+FantasyPvP@users.noreply.github.com> Date: Thu, 14 Nov 2024 22:43:49 +0000 Subject: [PATCH] implemented a custom text editor (but still no fs to use it with :sob:) --- src/lib.rs | 1 + src/system/kernel/tasks/keyboard.rs | 32 ++- src/system/std/io.rs | 4 + src/system/std/render.rs | 4 + src/user/bin/editor.rs | 327 ++++++++++++++++++++++++ src/user/bin/games/snake.rs | 2 +- src/user/bin/mod.rs | 1 + src/user/bin/shell.rs | 5 + src/user/lib/{coords.rs => geometry.rs} | 6 + src/user/lib/mod.rs | 2 +- 10 files changed, 379 insertions(+), 5 deletions(-) create mode 100644 src/user/bin/editor.rs rename src/user/lib/{coords.rs => geometry.rs} (98%) diff --git a/src/lib.rs b/src/lib.rs index ccd2599..3633e6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ #![feature(async_fn_in_trait)] #![feature(async_closure)] #![feature(inherent_associated_types)] +#![feature(iter_advance_by)] use bootloader::{entry_point, BootInfo}; diff --git a/src/system/kernel/tasks/keyboard.rs b/src/system/kernel/tasks/keyboard.rs index 88e0cc9..6a12a22 100644 --- a/src/system/kernel/tasks/keyboard.rs +++ b/src/system/kernel/tasks/keyboard.rs @@ -51,7 +51,9 @@ pub enum KeyStroke { Up, Down, None, - Enter + Enter, + Escape, + Del } impl KeyStroke { @@ -71,12 +73,37 @@ impl KeyStroke { KeyCode::ArrowUp => KeyStroke::Up, KeyCode::ArrowDown => KeyStroke::Down, KeyCode::Enter => KeyStroke::Enter, + KeyCode::Escape => KeyStroke::Escape, + KeyCode::Delete => KeyStroke::Del, _ => KeyStroke::None, } } } - +impl core::fmt::Display for KeyStroke { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + KeyStroke::Char(c) => write!(f, "{}", c), + KeyStroke::Ctrl => write!(f, "CTRL"), + KeyStroke::RCtrl => write!(f, "RCtrl"), + KeyStroke::Alt => write!(f, "ALT"), + KeyStroke::RAlt => write!(f, "RAlt"), + KeyStroke::Shift => write!(f, "SHIFT"), + KeyStroke::RShift => write!(f, "RShift"), + KeyStroke::Meta => write!(f, "META"), + KeyStroke::RMeta => write!(f, "RMeta"), + KeyStroke::Backspace => write!(f, "BACKSPACE"), + KeyStroke::Left => write!(f, "LEFT"), + KeyStroke::Right => write!(f, "RIGHT"), + KeyStroke::Up => write!(f, "UP"), + KeyStroke::Down => write!(f, "DOWN"), + KeyStroke::Enter => write!(f, "ENTER"), + KeyStroke::Escape => write!(f, "ESCAPE"), + KeyStroke::None => write!(f, "NONE"), + KeyStroke::Del => write!(f, "DEL"), + } + } +} impl KeyboardHandler { pub fn new() -> KeyboardHandler { @@ -135,7 +162,6 @@ impl KeyboardHandler { } }, DecodedKey::RawKey(key) => { - print!("{:?}", key); match KeyStroke::from_keycode(key) { KeyStroke::None => (), key => return Some(key) diff --git a/src/system/std/io.rs b/src/system/std/io.rs index 1a592ee..c30584e 100644 --- a/src/system/std/io.rs +++ b/src/system/std/io.rs @@ -90,6 +90,10 @@ impl Display { RENDERER.lock().application_mode(); Display } + + pub fn mv_cursor(&self, x: u8, y: u8) -> Result<(), RenderError> { + RENDERER.lock().cursor_position(x, y) + } } impl Drop for Display { diff --git a/src/system/std/render.rs b/src/system/std/render.rs index 147dfc2..7761dc2 100644 --- a/src/system/std/render.rs +++ b/src/system/std/render.rs @@ -71,6 +71,10 @@ impl Position { pub fn new(x: T, y: T) -> Position { Position { x, y } } + + pub fn zero() -> Position { + Position { x: T::zero(), y: T::zero() } + } pub fn into_usize(self) -> Result, ()> { Ok(Position { diff --git a/src/user/bin/editor.rs b/src/user/bin/editor.rs new file mode 100644 index 0000000..a3bf9e9 --- /dev/null +++ b/src/user/bin/editor.rs @@ -0,0 +1,327 @@ +use crate::{serial_println, std}; +use crate::std::application::{self, Application}; +use crate::std::io::{Color, ColorCode, Display, KeyStroke, Screen}; +use crate::std::render::{ColouredChar, Frame, Position, RenderError}; +use crate::user::lib::libgui::cg_core::CgComponent; + +use alloc::format; +use alloc::{string::{String, ToString}, vec::Vec, boxed::Box}; + +use async_trait::async_trait; + + +pub struct Editor { + buffer: Vec>, // outer vec is the line, inner is col; + cursor_pos: Position, + offset_pos: Position, + command: String, + mode: Mode, + unsaved: bool, + display: Display, + lineno_width: i32, +} + +enum Mode { + Normal, + Insert, + Command, + Diff // TODO +} + +impl core::fmt::Display for Mode { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Mode::Normal => write!(f, "Normal"), + Mode::Insert => write!(f, "Insert"), + Mode::Command => write!(f, "Commnd"), + Mode::Diff => write!(f, "Diff "), + } + } +} + +impl Editor { + fn move_cursor(&mut self, dx: i32, dy: i32) { + if dy != 0 + && self.cursor_pos.y + dy >= 0 + && self.cursor_pos.y + dy <= self.buffer.len() as i32 + { + self.cursor_pos.y += dy; + let line_width = self.buffer.get(self.cursor_pos.y as usize).unwrap_or(&Vec::::new()).len() as i32; + if self.cursor_pos.x > line_width { + self.cursor_pos.x = line_width; + } + } else if self.cursor_pos.x + dx < 0 { + if self.cursor_pos.y - 1 >= 0 { + self.cursor_pos.y -= 1; + self.cursor_pos.x = self.buffer.get(self.cursor_pos.y as usize).unwrap_or(&Vec::::new()).len() as i32; + } + } else if self.cursor_pos.x + dx > self.buffer.get(self.cursor_pos.y as usize).unwrap_or(&Vec::::new()).len() as i32 { + if self.cursor_pos.y + 1 <= self.buffer.len() as i32 { + self.cursor_pos.x = 0; + self.cursor_pos.y += 1; + } + } else if dx != 0 { + self.cursor_pos.x += dx; + } + + serial_println!("cursor: {} {} offset: {} {} ", self.cursor_pos.x, self.cursor_pos.y, self.offset_pos.x, self.offset_pos.y); + + while self.cursor_pos.x + 3 + (self.lineno_width + 2) > 80 + self.offset_pos.x { + self.offset_pos.x += 1; + } + + while self.cursor_pos.x - 3 < self.offset_pos.x && self.offset_pos.x - 3 >= 0 { + self.offset_pos.x -= 1; + } + + while self.cursor_pos.y + 3 > self.offset_pos.y + 25 { + self.offset_pos.y += 1; + } + + while self.cursor_pos.y - 3 < self.offset_pos.y && self.offset_pos.y - 3 >= 0 { + self.offset_pos.y -= 1; + } + + serial_println!( + "moving cursor to {}, {}", + (self.cursor_pos.x - self.offset_pos.x + self.lineno_width + 2) as u8, + (self.cursor_pos.y - self.offset_pos.y) as u8 + ); + + // print all the values below + serial_println!("offset: {}, {}", self.offset_pos.x, self.offset_pos.y); + serial_println!("cursor: {}, {}", self.cursor_pos.x, self.cursor_pos.y); + serial_println!("line width: {}", self.lineno_width + 2); + + self.display.mv_cursor( + (self.cursor_pos.x - self.offset_pos.x + self.lineno_width + 2) as u8, + (self.cursor_pos.y - self.offset_pos.y) as u8 + ).unwrap(); + } + + fn delete_char(&mut self) { + self.unsaved = true; + // if the cursor is at the end of the line + if self.cursor_pos.x == self.buffer.get(self.cursor_pos.y as usize).unwrap_or(&Vec::::new()).len() as i32 { + + if self.cursor_pos.y + 1 == self.buffer.len() as i32 { + return; + } + + let old_line = self.buffer[self.cursor_pos.y as usize + 1].clone(); + self.buffer[self.cursor_pos.y as usize].extend(&old_line); + self.buffer.remove(self.cursor_pos.y as usize + 1); + } else { + self.buffer[self.cursor_pos.y as usize].remove(self.cursor_pos.x as usize); + } + } + + + fn splitline(&mut self) { + self.unsaved = true; + + if let Some(_) = self.buffer.get(self.cursor_pos.y as usize) { + let first_half = self.buffer[self.cursor_pos.y as usize][..self.cursor_pos.x as usize].to_vec(); + let second_half = self.buffer[self.cursor_pos.y as usize][self.cursor_pos.x as usize..].to_vec(); + + self.buffer[self.cursor_pos.y as usize] = first_half; + self.buffer.insert(self.cursor_pos.y as usize + 1, second_half); + } else { + self.buffer.push(Vec::new()); + } + + self.move_cursor(1, 0); + } + + fn insert_char(&mut self, c: char) { + self.unsaved = true; + + if let Some(line) = self.buffer.get_mut(self.cursor_pos.y as usize) { + line.insert(self.cursor_pos.x as usize, c); + } else { + self.buffer.push(Vec::new()); + self.buffer.get_mut(self.cursor_pos.y as usize).unwrap().push(c); + } + } +} + +impl ToString for Editor { + fn to_string(&self) -> String { + self.buffer.iter().map(|line| line.iter().collect::()).collect::>().join("\n") + } +} + +#[async_trait] +impl Application for Editor { + fn new() -> Editor { + Editor { + buffer: Vec::new(), + cursor_pos: Position::zero(), + offset_pos: Position::zero(), + command: String::new(), + mode: Mode::Normal, + unsaved: false, + display: Display::borrow(), + lineno_width: 0 + } + } + + async fn run(&mut self, args: Vec) -> Result<(), application::Error> { + + // if let Some(s) = args.get(0) { + // self.buffer = s.lines().map(|l| l.chars().collect()).collect::>>() + // } + + self.buffer = String::from(" + /$$ /$$$$$$$$ /$$ /$$ /$$$$$$ /$$$$$$$ /$$ /$$ /$$ + /$$/|_____ $$ | $$ / $$ /$$__ $$| $$____/ /$$/| $$| $$ + /$$/ /$$/ | $$/ $$/| $$ \\ $$| $$ /$$/ \\ $$\\ $$ + /$$/ /$$/ \\ $$$$/ | $$ | $$| $$$$$$$ /$$/ \\ $$\\ $$ +| $$ /$$/ >$$ $$ | $$ | $$|_____ $$ /$$/ /$$/ /$$/ + \\ $$ /$$/ /$$/\\ $$| $$/$$ $$ /$$ \\ $$ /$$/ /$$/ /$$/ + \\ $$ /$$$$$$$$| $$ \\ $$| $$$$$$/| $$$$$$//$$/ /$$/ /$$/ + \\__/|________/|__/ |__/ \\____ $$$ \\______/|__/ |__/ |__/ + \\__/ + ").lines().map(|l| l.chars().collect()).collect::>>(); + + + + loop { + // start by rendering the screen + self.lineno_width = self.buffer.len().to_string().len() as i32; + self.render().unwrap().write_to_screen().unwrap(); + + // wait for a keyboard input + let keystroke = std::io::Stdin::keystroke().await; + + match self.mode { + Mode::Normal => { + match keystroke { + KeyStroke::Char('i') => self.mode = Mode::Insert, + KeyStroke::Char(':') => self.mode = Mode::Command, + KeyStroke::Char('d') => self.mode = Mode::Diff, + KeyStroke::Char('`') => { + // TODO: End terminal session + // ncurses::endwin(); + return Ok(()); + } + _ => {} + } + }, + Mode::Insert => { + match keystroke { + KeyStroke::Enter => { + // TODO: newline function + }, + KeyStroke::Char(c) => { + match c { + // escape + '\x1B' => self.mode = Mode::Normal, + // delete + '\x7F' => self.delete_char(), + // backspace + '\x08' => { + self.move_cursor(-1, 0); + self.delete_char(); + }, + // enter + '\n' => self.splitline(), + _ => { + self.insert_char(c); + self.move_cursor(1, 0); + } + } + }, + KeyStroke::Left => { + self.move_cursor(-1, 0); + }, + KeyStroke::Right => { + self.move_cursor(1, 0); + }, + KeyStroke::Up => { + self.move_cursor(0, -1); + }, + KeyStroke::Down => { + self.move_cursor(0, 1); + }, + KeyStroke::None => { + serial_println!("none"); + }, + _ => { + serial_println!("other"); + } + } + } + Mode::Command => { + match keystroke { + KeyStroke::Enter => { + // TODO: execute command + }, + KeyStroke::Char(c) => { + if c == '\x1B' { + self.mode = Mode::Normal; + self.command.clear(); + continue; + } + + self.command.push(c); + }, + _ => {} + } + } + Mode::Diff => {} + } + } + } +} + +impl CgComponent for Editor { + fn render(&self) -> Result { + let mut frame = Frame::new(Position::zero(), Position::new(80, 25))?; + let width = self.lineno_width as usize; + let linecolour = ColorCode::new(Color::Cyan, Color::Black); + + for (i, line) in (self.offset_pos.y..self.offset_pos.y + 24).enumerate() { + if line >= self.buffer.len() as i32 { + break; + } + + // render the line numbers on the left hand side of the screen + let line_num = format!("{:width$} │", line + 1); + for (j, c) in line_num.chars().enumerate() { + frame.write(Position::new(j, i), ColouredChar::coloured(c, linecolour))?; + } + + let line = self.buffer[line as usize].iter().collect::(); + + for (j, c) in line.chars().skip(self.offset_pos.x as usize).take(80 - (width + 2)).enumerate() { + frame.write(Position::new(j + width + 2, i), ColouredChar::new(c))?; + } + } + + // render the toolbar + + // render mode (8 chars) + let mode = format!("[{}]", self.mode); + + // render unsaved (10 chars) + let unsaved = String::from(if self.unsaved { "[Unsaved!]" } else { "" }); + + // line and col (variable width) + let line_and_col = format!("[{}:{}] ", self.cursor_pos.y + 1, self.cursor_pos.x + 1); + + // write to screen + let toolbar = line_and_col + " " + &mode + " " + &unsaved; + + for (i, c) in toolbar.chars().enumerate() { + frame.write(Position::new(i, 24), ColouredChar::new(c))?; + } + + Ok(frame) + } + + fn as_any(&self) -> &dyn core::any::Any { + self + } +} + diff --git a/src/user/bin/games/snake.rs b/src/user/bin/games/snake.rs index 6f094fa..428b957 100644 --- a/src/user/bin/games/snake.rs +++ b/src/user/bin/games/snake.rs @@ -7,7 +7,7 @@ use crate::std::application::{Application, Error}; use crate::std::render::{ColouredChar, Dimensions, Frame, RenderError, ColorCode}; use crate::std::random::Random; use crate::system::std::render; -use super::super::super::lib::coords::{Position, Direction}; +use super::super::super::lib::geometry::{Position, Direction}; #[derive(PartialEq)] enum Gamemode { diff --git a/src/user/bin/mod.rs b/src/user/bin/mod.rs index b6ce62a..4dbb3b8 100644 --- a/src/user/bin/mod.rs +++ b/src/user/bin/mod.rs @@ -4,6 +4,7 @@ pub mod crystalfetch; pub mod rickroll; pub mod shell; pub mod tasks; +pub mod editor; mod gigachad_detector; //mod shellrewrite; diff --git a/src/user/bin/shell.rs b/src/user/bin/shell.rs index 8999bd5..4d07eec 100644 --- a/src/user/bin/shell.rs +++ b/src/user/bin/shell.rs @@ -156,6 +156,11 @@ async fn exec() -> Result<(), Error> { detector.run(args).await?; } + "editor" => { + let mut editor = editor::Editor::new(); + editor.run(args).await?; + } + "wait" => { if args.len() != 1 { return Err(Error::CommandFailed("exactly one argument must be provided".to_string())) diff --git a/src/user/lib/coords.rs b/src/user/lib/geometry.rs similarity index 98% rename from src/user/lib/coords.rs rename to src/user/lib/geometry.rs index 843b63d..4d73b7c 100644 --- a/src/user/lib/coords.rs +++ b/src/user/lib/geometry.rs @@ -118,6 +118,12 @@ impl Position { } } + pub fn zero() -> Position { + Position { + x: 0, + y: 0, + } + } } impl core::ops::Add for Position { diff --git a/src/user/lib/mod.rs b/src/user/lib/mod.rs index 3183c3c..d95c15e 100644 --- a/src/user/lib/mod.rs +++ b/src/user/lib/mod.rs @@ -1,3 +1,3 @@ // pub mod libgui_old_archive; -pub mod coords; +pub mod geometry; pub mod libgui;