initial
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
Cargo.lock
|
7
Cargo.toml
Normal file
7
Cargo.toml
Normal 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
200
src/main.rs
Normal 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)
|
||||||
|
}
|
Reference in New Issue
Block a user