This commit is contained in:
2024-04-18 17:23:17 +02:00
commit 3b4cb6ac86
3 changed files with 209 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
Cargo.lock

7
Cargo.toml Normal file
View File

@@ -0,0 +1,7 @@
[package]
name = "cursor_mover"
version = "0.1.0"
edition = "2021"
[dependencies]
crossterm = "0.27.0"

200
src/main.rs Normal file
View File

@@ -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<Vec<char>>,
}
#[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<Action> {
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<KeyEvent> {
// 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)
}