support line split and long files
This commit is contained in:
149
src/main.rs
149
src/main.rs
@@ -1,3 +1,5 @@
|
|||||||
|
// My text editor - the pinnacle of inefficient memory usage
|
||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{poll, read, Event, KeyCode, KeyEvent},
|
event::{poll, read, Event, KeyCode, KeyEvent},
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, size},
|
terminal::{disable_raw_mode, enable_raw_mode, size},
|
||||||
@@ -9,6 +11,7 @@ use std::time::Duration;
|
|||||||
struct Buffer {
|
struct Buffer {
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
|
top_y: usize,
|
||||||
buf: Vec<Vec<char>>,
|
buf: Vec<Vec<char>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,49 +32,72 @@ enum Action {
|
|||||||
Move(Direction),
|
Move(Direction),
|
||||||
Write(char),
|
Write(char),
|
||||||
Save,
|
Save,
|
||||||
Delete,
|
Remove(Delete),
|
||||||
Mode(Mode),
|
Mode(Mode),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Delete {
|
||||||
|
Char(usize),
|
||||||
|
Backspace,
|
||||||
|
Line(usize),
|
||||||
|
Word(usize),
|
||||||
|
}
|
||||||
|
|
||||||
enum Direction {
|
enum Direction {
|
||||||
Up,
|
Up(usize),
|
||||||
Down,
|
Down(usize),
|
||||||
Right,
|
Right(usize),
|
||||||
Left,
|
Left(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// I dont bother to handle resizes, I can get resizes as an input if I want from
|
// I dont bother to handle resizes, I can get resizes as an input if I want from
|
||||||
// crossterm::KeyEvent
|
// crossterm::KeyEvent
|
||||||
let term_width = size().unwrap().0.into();
|
// I could probably handle inputs directly without using enums.
|
||||||
let mut buffer = Buffer::new();
|
let file_name = std::env::args().nth(1);
|
||||||
|
let file_input = if let Some(ref input) = file_name {
|
||||||
|
std::fs::read_to_string(input).unwrap()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
let size = size().unwrap();
|
||||||
|
let (width, height) = (size.0.into(), size.1.into());
|
||||||
|
let mut buffer = Buffer::new(file_input);
|
||||||
print!("\u{001b}c");
|
print!("\u{001b}c");
|
||||||
|
buffer.render(height);
|
||||||
loop {
|
loop {
|
||||||
stdout().flush().unwrap();
|
stdout().flush().unwrap();
|
||||||
enable_raw_mode().unwrap();
|
enable_raw_mode().unwrap();
|
||||||
let action = action(&buffer.mode);
|
let action = action(&buffer.mode);
|
||||||
disable_raw_mode().unwrap();
|
disable_raw_mode().unwrap();
|
||||||
if let Some(action) = action {
|
if let Some(action) = action {
|
||||||
buffer.handle_action(action, term_width);
|
buffer.handle_action(action, (width, height), &file_name);
|
||||||
buffer.constr(term_width);
|
buffer.constr((width, height));
|
||||||
}
|
}
|
||||||
buffer.render();
|
buffer.render(height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Buffer {
|
impl Buffer {
|
||||||
fn new() -> Self {
|
fn new(input: String) -> Self {
|
||||||
|
let buf = if input == String::new() {
|
||||||
|
vec![vec![]]
|
||||||
|
} else {
|
||||||
|
input.lines().map(|m| m.chars().collect()).collect()
|
||||||
|
};
|
||||||
Self {
|
Self {
|
||||||
buf: vec![vec![]],
|
buf,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&self) {
|
fn write(&self, file_name: &Option<String>) {
|
||||||
std::fs::write("file.txt", self.buf_to_string()).unwrap();
|
if let Some(file_name) = file_name {
|
||||||
|
std::fs::write(file_name, self.buf_to_string(0..self.buf.len())).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn constr(&mut self, width: usize) {
|
fn constr(&mut self, (widht, height): (usize, usize)) {
|
||||||
if self.pos.height >= self.buf.len() {
|
if self.pos.height >= self.buf.len() {
|
||||||
self.pos.height = self.buf.len() - 1;
|
self.pos.height = self.buf.len() - 1;
|
||||||
}
|
}
|
||||||
@@ -85,13 +111,19 @@ impl Buffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_action(&mut self, action: Action, width: usize) -> Option<()> {
|
fn handle_action(
|
||||||
|
&mut self,
|
||||||
|
action: Action,
|
||||||
|
(width, height): (usize, usize),
|
||||||
|
file_name: &Option<String>,
|
||||||
|
) -> Option<()> {
|
||||||
|
let prev_height = self.pos.height;
|
||||||
match action {
|
match action {
|
||||||
Action::Move(m) => match m {
|
Action::Move(m) => match m {
|
||||||
Direction::Up => self.pos.height = self.pos.height.checked_sub(1).unwrap_or(0),
|
Direction::Up(n) => self.pos.height = self.pos.height.checked_sub(n).unwrap_or(0),
|
||||||
Direction::Down => self.pos.height = self.pos.height.checked_add(1).unwrap_or(0),
|
Direction::Down(n) => self.pos.height = self.pos.height.checked_add(n).unwrap_or(0),
|
||||||
Direction::Right => self.pos.width = self.pos.width.checked_add(1).unwrap_or(0),
|
Direction::Right(n) => self.pos.width = self.pos.width.checked_add(n).unwrap_or(0),
|
||||||
Direction::Left => self.pos.width = self.pos.width.checked_sub(1).unwrap_or(0),
|
Direction::Left(n) => self.pos.width = self.pos.width.checked_sub(n).unwrap_or(0),
|
||||||
},
|
},
|
||||||
Action::Write(c) => {
|
Action::Write(c) => {
|
||||||
if self.pos.width > width - 2 {
|
if self.pos.width > width - 2 {
|
||||||
@@ -100,8 +132,11 @@ impl Buffer {
|
|||||||
}
|
}
|
||||||
match c {
|
match c {
|
||||||
'\n' => {
|
'\n' => {
|
||||||
self.buf[self.pos.height].pop();
|
let temp = self.buf[self.pos.height].clone();
|
||||||
self.buf.push(vec![' ']);
|
let (a, b) = temp.split_at(self.pos.width);
|
||||||
|
self.buf.remove(self.pos.height);
|
||||||
|
self.buf.insert(self.pos.height, b.to_owned());
|
||||||
|
self.buf.insert(self.pos.height, a.to_owned());
|
||||||
self.pos.height += 1;
|
self.pos.height += 1;
|
||||||
self.pos.width = 0;
|
self.pos.width = 0;
|
||||||
return Some(());
|
return Some(());
|
||||||
@@ -112,35 +147,52 @@ impl Buffer {
|
|||||||
}
|
}
|
||||||
self.pos.width += 1;
|
self.pos.width += 1;
|
||||||
}
|
}
|
||||||
Action::Delete => {
|
Action::Remove(d) => match d {
|
||||||
if self.pos.width != 0 {
|
Delete::Backspace => {
|
||||||
self.buf[self.pos.height].remove(self.pos.width - 1);
|
if self.pos.width != 0 {
|
||||||
self.pos.width -= 1;
|
self.buf[self.pos.height].remove(self.pos.width - 1);
|
||||||
self.buf[self.pos.height].push(' ');
|
self.pos.width -= 1;
|
||||||
|
} else if self.buf[self.pos.height].is_empty() {
|
||||||
|
self.buf.remove(self.pos.height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
Delete::Char(n) => {
|
||||||
Action::Mode(m) => {
|
self.buf[self.pos.height].drain(self.pos.width..self.pos.width + n);
|
||||||
self.mode = m;
|
if self.buf.len() != 1 && self.buf[self.pos.height].is_empty() {
|
||||||
}
|
self.buf.remove(self.pos.height);
|
||||||
Action::Save => {
|
}
|
||||||
self.write();
|
}
|
||||||
}
|
Delete::Word(n) => {}
|
||||||
|
Delete::Line(n) => drop(self.buf.remove(self.pos.height)),
|
||||||
|
},
|
||||||
|
Action::Mode(m) => self.mode = m,
|
||||||
|
Action::Save => self.write(file_name),
|
||||||
|
}
|
||||||
|
if self.top_y != 0 && self.pos.height == 0 && prev_height == 0 {
|
||||||
|
self.top_y -= 1;
|
||||||
|
} else if self.pos.height >= height {
|
||||||
|
self.pos.height = height - 1;
|
||||||
|
self.top_y += 1;
|
||||||
}
|
}
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self) {
|
fn render(&self, height: usize) {
|
||||||
|
// I need to find the heigh in the file and height in the terminal and use that to
|
||||||
|
// calculate which lines should be rendered.
|
||||||
|
// let top_wall_y = self.pos.height
|
||||||
|
let min = self.buf.len().min(self.top_y + height - 1);
|
||||||
print!(
|
print!(
|
||||||
"\u{001b}c{}\x1b[{};{}H",
|
"\u{001b}c{}\x1b[{};{}H",
|
||||||
self.buf_to_string(),
|
self.buf_to_string(self.top_y..min),
|
||||||
self.pos.height + 1,
|
self.pos.height + 1,
|
||||||
self.pos.width + 1
|
self.pos.width + 1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn buf_to_string(&self) -> String {
|
fn buf_to_string(&self, range: std::ops::Range<usize>) -> String {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
for line in &self.buf {
|
for line in &self.buf[range] {
|
||||||
for c in line {
|
for c in line {
|
||||||
s.push(*c);
|
s.push(*c);
|
||||||
}
|
}
|
||||||
@@ -151,26 +203,33 @@ impl Buffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn action(mode: &Mode) -> Option<Action> {
|
fn action(mode: &Mode) -> Option<Action> {
|
||||||
|
let mut input_cache = String::new();
|
||||||
if let Some(k) = key_input() {
|
if let Some(k) = key_input() {
|
||||||
let k = k.code;
|
let k = k.code;
|
||||||
|
let n = input_cache.parse().unwrap_or(1);
|
||||||
match mode {
|
match mode {
|
||||||
Mode::Write => match k {
|
Mode::Write => match k {
|
||||||
KeyCode::Char(c) => Some(Action::Write(c)),
|
KeyCode::Char(c) => Some(Action::Write(c)),
|
||||||
KeyCode::Tab => Some(Action::Write('\t')),
|
KeyCode::Tab => Some(Action::Write('\t')),
|
||||||
KeyCode::Enter => Some(Action::Write('\n')),
|
KeyCode::Enter => Some(Action::Write('\n')),
|
||||||
KeyCode::Esc => Some(Action::Mode(Mode::Normal)),
|
KeyCode::Esc => Some(Action::Mode(Mode::Normal)),
|
||||||
KeyCode::Backspace => Some(Action::Delete),
|
KeyCode::Backspace => Some(Action::Remove(Delete::Backspace)),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
Mode::Normal => match k {
|
Mode::Normal => match k {
|
||||||
KeyCode::Char(c) => match c {
|
KeyCode::Char(c) => match c {
|
||||||
'k' => Some(Action::Move(Direction::Up)),
|
'k' => Some(Action::Move(Direction::Up(n))),
|
||||||
'j' => Some(Action::Move(Direction::Down)),
|
'j' => Some(Action::Move(Direction::Down(n))),
|
||||||
'l' => Some(Action::Move(Direction::Right)),
|
'l' => Some(Action::Move(Direction::Right(n))),
|
||||||
'h' => Some(Action::Move(Direction::Left)),
|
'h' => Some(Action::Move(Direction::Left(n))),
|
||||||
'i' => Some(Action::Mode(Mode::Write)),
|
'i' => Some(Action::Mode(Mode::Write)),
|
||||||
'x' => Some(Action::Delete),
|
'x' => Some(Action::Remove(Delete::Char(n))),
|
||||||
'w' => Some(Action::Save),
|
'w' => Some(Action::Save),
|
||||||
|
'd' => Some(Action::Remove(Delete::Line(n))),
|
||||||
|
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => {
|
||||||
|
input_cache.push(c);
|
||||||
|
None
|
||||||
|
}
|
||||||
'q' => exit(),
|
'q' => exit(),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
@@ -184,7 +243,7 @@ fn action(mode: &Mode) -> Option<Action> {
|
|||||||
|
|
||||||
fn key_input() -> Option<KeyEvent> {
|
fn key_input() -> Option<KeyEvent> {
|
||||||
// I do not know what I am doing here but it works
|
// I do not know what I am doing here but it works
|
||||||
while poll(Duration::from_secs(0)).ok().is_some() {
|
if poll(Duration::from_secs(0)).ok().is_some() {
|
||||||
if let Event::Key(k) = read().ok()? {
|
if let Event::Key(k) = read().ok()? {
|
||||||
return Some(k);
|
return Some(k);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user