commit 3b4cb6ac86042d7848d4a479b97f9d9b0000e657 Author: Vegard Matthey Date: Thu Apr 18 17:23:17 2024 +0200 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..89f9708 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "cursor_mover" +version = "0.1.0" +edition = "2021" + +[dependencies] +crossterm = "0.27.0" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..94a4696 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,200 @@ +use crossterm::{ + event::{poll, read, Event, KeyCode, KeyEvent}, + terminal::{disable_raw_mode, enable_raw_mode, size}, +}; +use std::io::{stdout, Write}; +use std::time::Duration; + +#[derive(Default)] +struct Buffer { + mode: Mode, + pos: Position, + buf: Vec>, +} + +#[derive(Default)] +struct Position { + width: usize, + height: usize, +} + +#[derive(Default)] +enum Mode { + #[default] + Normal, + Write, +} + +enum Action { + Move(Direction), + Write(char), + Save, + Delete, + Mode(Mode), +} + +enum Direction { + Up, + Down, + Right, + Left, +} + +fn main() { + // I dont bother to handle resizes, I can get resizes as an input if I want from + // crossterm::KeyEvent + let term_width = size().unwrap().0.into(); + let mut buffer = Buffer::new(); + print!("\u{001b}c"); + loop { + stdout().flush().unwrap(); + enable_raw_mode().unwrap(); + let action = action(&buffer.mode); + disable_raw_mode().unwrap(); + if let Some(action) = action { + buffer.handle_action(action, term_width); + buffer.constr(term_width); + } + buffer.render(); + } +} + +impl Buffer { + fn new() -> Self { + Self { + buf: vec![vec![]], + ..Default::default() + } + } + + fn write(&self) { + std::fs::write("file.txt", self.buf_to_string()).unwrap(); + } + + fn constr(&mut self, width: usize) { + if self.pos.height >= self.buf.len() { + self.pos.height = self.buf.len() - 1; + } + if self.buf[self.pos.height].len() == 0 { + self.buf[self.pos.height].push(' '); + self.pos.width = 0; + return; + } + if self.pos.width >= self.buf[self.pos.height].len() { + self.pos.width = self.buf[self.pos.height].len() - 1; + } + } + + fn handle_action(&mut self, action: Action, width: usize) -> Option<()> { + match action { + Action::Move(m) => match m { + Direction::Up => self.pos.height = self.pos.height.checked_sub(1).unwrap_or(0), + Direction::Down => self.pos.height = self.pos.height.checked_add(1).unwrap_or(0), + Direction::Right => self.pos.width = self.pos.width.checked_add(1).unwrap_or(0), + Direction::Left => self.pos.width = self.pos.width.checked_sub(1).unwrap_or(0), + }, + Action::Write(c) => { + if self.pos.width > width - 2 { + self.pos.width = width - 1; + return None; + } + match c { + '\n' => { + self.buf[self.pos.height].pop(); + self.buf.push(vec![' ']); + self.pos.height += 1; + self.pos.width = 0; + return Some(()); + } + _ => { + self.buf[self.pos.height].insert(self.pos.width, c); + } + } + self.pos.width += 1; + } + Action::Delete => { + if self.pos.width != 0 { + self.buf[self.pos.height].remove(self.pos.width - 1); + self.pos.width -= 1; + self.buf[self.pos.height].push(' '); + } + } + Action::Mode(m) => { + self.mode = m; + } + Action::Save => { + self.write(); + } + } + Some(()) + } + + fn render(&self) { + print!( + "\u{001b}c{}\x1b[{};{}H", + self.buf_to_string(), + self.pos.height + 1, + self.pos.width + 1 + ); + } + + fn buf_to_string(&self) -> String { + let mut s = String::new(); + for line in &self.buf { + for c in line { + s.push(*c); + } + s.push('\n'); + } + s + } +} + +fn action(mode: &Mode) -> Option { + if let Some(k) = key_input() { + let k = k.code; + match mode { + Mode::Write => match k { + KeyCode::Char(c) => Some(Action::Write(c)), + KeyCode::Tab => Some(Action::Write('\t')), + KeyCode::Enter => Some(Action::Write('\n')), + KeyCode::Esc => Some(Action::Mode(Mode::Normal)), + KeyCode::Backspace => Some(Action::Delete), + _ => None, + }, + Mode::Normal => match k { + KeyCode::Char(c) => match c { + 'k' => Some(Action::Move(Direction::Up)), + 'j' => Some(Action::Move(Direction::Down)), + 'l' => Some(Action::Move(Direction::Right)), + 'h' => Some(Action::Move(Direction::Left)), + 'i' => Some(Action::Mode(Mode::Write)), + 'x' => Some(Action::Delete), + 'w' => Some(Action::Save), + 'q' => exit(), + _ => None, + }, + _ => None, + }, + } + } else { + None + } +} + +fn key_input() -> Option { + // I do not know what I am doing here but it works + while poll(Duration::from_secs(0)).ok().is_some() { + if let Event::Key(k) = read().ok()? { + return Some(k); + } + } + None +} + +fn exit() -> ! { + disable_raw_mode().unwrap(); + print!("\u{001b}c"); + stdout().flush().unwrap(); + std::process::exit(0) +}