diff --git a/Cargo.toml b/Cargo.toml index f4e715d..c50da64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,8 @@ license = "MIT" [dependencies] clap = { version = "4.5.23", features = ["derive"] } +color-eyre = "0.6.3" crossterm = "0.28.1" +tokio = { version = "1.42.0", features = ["full"] } +tracing = "0.1.41" +tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } diff --git a/src/editor/editor.rs b/src/editor/editor.rs new file mode 100644 index 0000000..5b07a9b --- /dev/null +++ b/src/editor/editor.rs @@ -0,0 +1,236 @@ +use color_eyre::Report; +use crossterm::{ + cursor, + event::{self, poll, read, Event, KeyCode, KeyEvent, KeyModifiers}, + execute, queue, style, + terminal::{self, ClearType}, +}; +use std::{ + fs::File, + io::{self, BufRead, BufReader, Write}, + sync::{atomic::AtomicBool, mpsc, Arc}, + time::{Duration, Instant}, +}; +use tokio::{runtime::Runtime, sync::oneshot}; + +use super::Keymap; + +#[derive(Debug, Eq, PartialEq, Hash)] +pub enum Mode { + NORMAL, + COMMAND, + INSERT, + VISUAL, +} + +#[derive(Debug)] +pub enum ControlCode { + CONTINUE, + QUIT, +} + +#[derive(Debug)] +pub struct Editor { + buffer: Vec, + dirty: bool, + stop: bool, + + mode: Mode, + + size: (u16, u16), + cursor: (u16, u16), + offset: (u16, u16), + + keymap: Keymap, + last_key_time: Instant, + + out: io::Stdout, +} + +impl Drop for Editor { + fn drop(&mut self) { + let _ = execute!(self.out, style::ResetColor, terminal::LeaveAlternateScreen); + } +} + +impl Editor { + pub fn new() -> Self { + let mut keymap = Keymap::new(); + + keymap.add_keybind( + vec![Mode::NORMAL], + vec![ + KeyEvent::new(KeyCode::Char('w'), KeyModifiers::CONTROL), + KeyEvent::new(KeyCode::Char('q'), KeyModifiers::CONTROL), + ], + |editor| Ok(editor.stop = true), + ); + + keymap.add_keybind( + vec![Mode::NORMAL], + vec![ + KeyEvent::new(KeyCode::Char('w'), KeyModifiers::CONTROL), + KeyEvent::new(KeyCode::Char('q'), KeyModifiers::CONTROL), + KeyEvent::new(KeyCode::Char('q'), KeyModifiers::CONTROL), + ], + |editor| Ok(editor.buffer.push("Double QUIT".to_string())), + ); + + let mut editor = Self { + buffer: vec![String::new()], + dirty: true, + stop: false, + + mode: Mode::NORMAL, + + size: terminal::size().unwrap(), + cursor: (0, 0), + offset: (0, 0), + + keymap, + last_key_time: Instant::now(), + + out: io::stdout(), + }; + + let _ = execute!(editor.out, terminal::EnterAlternateScreen); + editor + } + + pub fn load_file(&mut self, filename: &str) { + let file = match File::open(filename) { + Ok(file) => file, + Err(e) => { + eprintln!("Error opening file '{}': {}", filename, e); + return; + } + }; + + let reader = BufReader::new(file); + self.buffer = match reader.lines().collect::, _>>() { + Ok(lines) => lines, + Err(e) => { + eprintln!("Error reading lines from file '{}': {}", filename, e); + return; + } + }; + } + + pub fn run(&mut self) -> Result<(), Report> { + let (tx, mut rx) = mpsc::channel::(); + + let rt = Runtime::new()?; + rt.block_on(async { + tokio::spawn(async move { + Editor::key_event_listener(tx).await; + }); + + while !self.stop { + self.handle_key_event(&mut rx).await?; + self.render().await?; + } + + self.buffer.push("This is outside main loop".to_string()); + self.dirty = true; + self.render().await?; + + Ok(()) + }) + } + + async fn handle_key_event(&mut self, rx: &mut mpsc::Receiver) -> Result<(), Report> { + if self.last_key_time.elapsed().as_millis() > 1000 && !self.keymap.is_empty() { + self.buffer.push("TIMEOUT".to_string()); + self.execute_keymap_action()?; + self.dirty = true; + } + + let event = match rx.try_recv() { + Ok(event) => event, + Err(_) => return Ok(()), + }; + + let mut unresolved = self.keymap.traverse(&self.mode, event)?; + if unresolved.is_some() { + self.execute_keymap_action()?; + unresolved = self.keymap.traverse(&self.mode, event)?; + } + + if let Some(unresolved) = unresolved { + self.buffer.push(format!( + "{} {:?} {:?}", + unresolved.code, unresolved.modifiers, self.last_key_time + )); + } + + if self.keymap.is_leaf() { + self.execute_keymap_action()?; + } + + self.last_key_time = Instant::now(); + self.dirty = true; + Ok(()) + } + + fn execute_keymap_action(&mut self) -> Result<(), Report> { + if let Some(action) = self.keymap.get_action() { + action.borrow_mut()(self)?; + }; + + self.keymap.clear(); + Ok(()) + } + + async fn render(&mut self) -> Result<(), Report> { + if !self.dirty { + return Ok(()); + } + + queue!( + self.out, + style::ResetColor, + terminal::Clear(ClearType::All), + cursor::MoveTo(0, 0) + )?; + + let max_lines = self.size.1 as usize; + + let render = &self.buffer[self.offset.1 as usize..] + .iter() + .take(max_lines) + .collect::>(); + + for line in render { + queue!(self.out, style::Print(line), cursor::MoveToNextLine(1))?; + } + + let rendered_lines = render.len(); + if rendered_lines < max_lines { + let empty_lines = max_lines - rendered_lines; + for _ in 0..empty_lines { + queue!(self.out, style::Print("~"), cursor::MoveToNextLine(1))?; + } + } + + queue!(self.out, cursor::MoveTo(self.cursor.0, self.cursor.1))?; + self.out.flush()?; + + self.dirty = false; + Ok(()) + } + + async fn key_event_listener(tx: mpsc::Sender) { + loop { + if !poll(Duration::from_millis(10)).unwrap() { + tokio::time::sleep(Duration::from_millis(10)).await; + continue; + } + + if let Event::Key(key_event) = read().unwrap() { + if tx.send(key_event).is_err() { + break; + } + } + } + } +} diff --git a/src/editor/keymap.rs b/src/editor/keymap.rs new file mode 100644 index 0000000..15786a6 --- /dev/null +++ b/src/editor/keymap.rs @@ -0,0 +1,140 @@ +use color_eyre::Report; +use crossterm::event::KeyEvent; +use std::{cell::RefCell, collections::HashMap, fmt, io, rc::Rc}; + +use crate::editor::{ControlCode, Editor, Mode}; + +type ActionFn = dyn FnMut(&mut Editor) -> Result<(), Report>; + +#[derive(Clone)] +struct KeyNode { + children: HashMap>>, + action: Option>>, +} + +impl fmt::Debug for KeyNode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "KeyNode {{ children: {:?} }}", self.children) + } +} + +impl KeyNode { + pub fn new() -> Rc> { + Rc::new(RefCell::new(Self { + children: HashMap::new(), + action: None, + })) + } + + pub fn is_leaf(&self) -> bool { + self.children.is_empty() + } + + pub fn insert(&mut self, sequence: Vec, action: Rc>) { + if sequence.is_empty() { + self.action = Some(action); + return; + } + + let (key, sequence) = sequence.split_first().unwrap(); + let next_node = self.children.entry(key.clone()).or_insert_with(KeyNode::new); + + next_node.borrow_mut().insert(sequence.to_vec(), action) + } +} + +#[derive(Debug)] +pub struct Keymap { + root: HashMap>>, + current: Option>>, +} + +impl Keymap { + pub fn new() -> Self { + Self { + root: HashMap::new(), + current: None, + } + } + + pub fn add_keybind(&mut self, modes: Vec, sequence: Vec, action: F) + where + F: FnMut(&mut Editor) -> Result<(), Report> + 'static, + { + let action = Rc::new(RefCell::new(action)); + + for mode in modes { + let mut root_node = self.root.entry(mode).or_insert_with(KeyNode::new).borrow_mut(); + root_node.insert(sequence.clone(), action.clone()); + } + } + + pub fn traverse(&mut self, mode: &Mode, event: KeyEvent) -> Result, Report> { + let current_node = match self.current { + Some(ref node) => node.clone(), + None => self.root.get(mode).unwrap().clone(), + }; + + let next_node = match current_node.borrow().children.get(&event) { + Some(node) => node.clone(), + None => return Ok(Some(event)), + }; + + self.current = Some(next_node); + Ok(None) + } + + pub fn is_leaf(&self) -> bool { + match self.current { + Some(ref node) => node.borrow().is_leaf(), + None => false, + } + } + + pub fn get_action(&self) -> Option>> { + if self.current.is_none() { + return None; + } + + self.current.as_ref()?.borrow().action.clone() + } + + pub fn clear(&mut self) { + self.current = None; + } + + pub fn is_empty(&self) -> bool { + self.current.is_none() + } +} + +//impl Keymap { +// pub fn new() -> Self { +// Self { +// root: KeyNode::new(), +// command: Vec::new(), +// } +// } +// +// pub fn add_keybind(&mut self, modes: Vec, sequence: Vec, action: F) +// where +// F: FnMut(&mut Editor) -> io::Result + 'static, +// { +// for mode in modes { +// self.root.insert(mode, sequence.clone(), action); +// } +// } +// +// pub fn clear(&mut self) { +// self.command.clear(); +// } +// +// pub fn traverse(&mut self, mode: Mode, event: KeyEvent) -> Option<&mut KeyNode> { +// let mut current_node = &mut self.root; +// +// if let Some(next_node) = current_node.children.get_mut(&(mode, event)) { +// self.current_path.push((mode, event)); +// self.current +// } +// } +//} diff --git a/src/editor/mod.rs b/src/editor/mod.rs new file mode 100644 index 0000000..15725b2 --- /dev/null +++ b/src/editor/mod.rs @@ -0,0 +1,7 @@ +mod editor; +mod keymap; + +pub(crate) use self::editor::{ControlCode, Mode}; +pub(crate) use self::keymap::Keymap; + +pub use self::editor::Editor; diff --git a/src/main.rs b/src/main.rs index 71aa17c..a56b084 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,146 +1,35 @@ -mod rope; +mod editor; -use std::{cmp, io}; - -use crossterm::{ - cursor, - event::{read, Event, KeyCode, KeyEvent, KeyModifiers}, - execute, queue, style, - terminal::{self, ClearType}, -}; +use std::time::Instant; use clap::Parser; +use color_eyre::Report; +use crossterm::terminal; -enum Mode { - NORMAL, - INSERT, - VISUAL, +use editor::Editor; +use tracing_subscriber::EnvFilter; + +struct RawModeGuard; +impl Drop for RawModeGuard { + fn drop(&mut self) { + let _ = terminal::disable_raw_mode(); + } } -#[allow(dead_code)] -struct Editor { - buffer: Vec, - command: String, - out: W, - cursor: (u16, u16), - size: (u16, u16), - mode: Mode, -} - -impl Editor { - fn new(out: W) -> Self { - Self { - size: terminal::size().unwrap(), - buffer: Vec::new(), - command: String::new(), - out, - cursor: (0, 0), - mode: Mode::NORMAL, - } +fn setup() -> Result<(), Report> { + if std::env::var("RUST_LIB_BACKTRACE").is_err() { + std::env::set_var("RUST_LIB_BACKTRACE", "1") } + color_eyre::install()?; - fn setup(&mut self) -> io::Result<()> { - execute!(self.out, terminal::EnterAlternateScreen)?; - terminal::enable_raw_mode()?; - - self.buffer.push("".to_string()); - - for _ in 1..self.size.0 { - self.buffer.push("~".to_string()); - } - - Ok(()) + if std::env::var("RUST_LOG").is_err() { + std::env::set_var("RUST_LOG", "info") } + tracing_subscriber::fmt::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .init(); - fn teardown(&mut self) -> io::Result<()> { - execute!(self.out, style::ResetColor, terminal::LeaveAlternateScreen)?; - terminal::disable_raw_mode() - } - - fn run(&mut self) -> io::Result<()> { - loop { - queue!( - self.out, - style::ResetColor, - terminal::Clear(ClearType::CurrentLine), - cursor::MoveTo(0, 0) - )?; - - for message in &self.buffer { - queue!(self.out, style::Print(message), cursor::MoveToNextLine(1))?; - } - - queue!(self.out, cursor::MoveTo(self.cursor.0, self.cursor.1))?; - - self.out.flush()?; - - match Self::read_key_event()? { - KeyEvent { - code: KeyCode::Char('q'), - modifiers: KeyModifiers::CONTROL, - .. - } => { - break; - } - KeyEvent { - code: KeyCode::Char('j'), - .. - } => { - self.cursor = (self.cursor.0, cmp::min(self.cursor.1 + 1, self.size.1 - 1)); - } - KeyEvent { - code: KeyCode::Char('k'), - .. - } => { - self.cursor = (self.cursor.0, self.cursor.1.saturating_sub(1)); - } - - KeyEvent { - code: KeyCode::Char('h'), - .. - } => { - self.cursor = (self.cursor.0.saturating_sub(1), self.cursor.1); - } - KeyEvent { - code: KeyCode::Char('l'), - .. - } => { - self.cursor = (cmp::min(self.cursor.0 + 1, self.size.0 - 1), self.cursor.1); - } - KeyEvent { - code: KeyCode::Char('d'), - modifiers: KeyModifiers::CONTROL, - .. - } => { - self.cursor = ( - self.cursor.0, - cmp::min(self.cursor.1 + self.size.1 / 2, self.size.1 - 1), - ); - } - KeyEvent { - code: KeyCode::Char('u'), - modifiers: KeyModifiers::CONTROL, - .. - } => { - self.cursor = ( - self.cursor.0, - cmp::max(self.cursor.1.saturating_sub(self.size.1 / 2), 0), - ); - } - _ => {} - } - } - - Ok(()) - } - - pub fn read_key_event() -> std::io::Result { - loop { - if let Ok(Event::Key(key_event)) = read() { - return Ok(key_event); - } - } - } + Ok(()) } #[derive(Parser, Debug)] @@ -150,15 +39,22 @@ struct Args { filename: Option, } -fn main() -> std::io::Result<()> { +fn main() -> Result<(), Report> { + setup()?; + let args = Args::parse(); - println!("{:?}", args); - let mut editor = Editor::new(io::stdout()); + { + terminal::enable_raw_mode()?; + let _raw_mode_guard = RawModeGuard; - //editor.setup()?; - //editor.run()?; - //editor.teardown() + let mut editor = Editor::new(); + if let Some(filename) = &args.filename { + editor.load_file(filename); + }; + + editor.run()?; + } Ok(()) } diff --git a/src/rope.rs b/src/rope.rs deleted file mode 100644 index 08459ba..0000000 --- a/src/rope.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::rc::Rc; - -enum Node { - Leaf(String), - Internal { - left: Rc, - right: Rc, - weight: usize, - }, -} - -impl Node { - fn len(&self) -> usize { - match self { - Node::Leaf(s) => s.len(), - Node::Internal { weight, right, .. } => weight + right.len(), - } - } - - fn new_leaf(text: &str) -> Rc { - Rc::new(Node::Leaf(text.to_string())) - } - - fn new_internal(left: Rc, right: Rc) -> Rc { - Rc::new(Node::Internal { - weight: left.len(), - left, - right, - }) - } -} - -pub struct Rope { - root: Rc, -} - -impl Rope { - pub fn new(text: &str) -> Self { - Self { - root: Node::new_leaf(text), - } - } - - pub fn len(&self) -> usize { - self.root.len() - } - - pub fn insert(&mut self, index: usize, text: &str) { - self.root = self.insert_rec(&self.root, index, text); - } -} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..aae1fd1 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1 @@ +pub(crate) mod rope; diff --git a/src/util/rope.rs b/src/util/rope.rs new file mode 100644 index 0000000..e7cc270 --- /dev/null +++ b/src/util/rope.rs @@ -0,0 +1,91 @@ +use std::fmt; +use std::rc::Rc; + +enum Node { + Leaf(String), + Internal { + left: Rc, + right: Rc, + weight: usize, + }, +} + +impl Node { + fn new_leaf(text: &str) -> Rc { + Rc::new(Node::Leaf(text.to_string())) + } + + fn new_internal(left: Rc, right: Rc) -> Rc { + Rc::new(Node::Internal { + weight: left.len(), + left, + right, + }) + } + + fn len(&self) -> usize { + match self { + Node::Leaf(s) => s.len(), + Node::Internal { weight, right, .. } => weight + right.len(), + } + } +} + +pub struct Rope { + root: Rc, +} + +impl Rope { + pub fn new(text: &str) -> Self { + Self { + root: Node::new_leaf(text), + } + } + + pub fn len(&self) -> usize { + self.root.len() + } + + pub fn insert(&mut self, index: usize, text: &str) { + self.root = self.insert_rec(&self.root, index, text); + } + + fn insert_rec(&self, node: &Rc, index: usize, text: &str) -> Rc { + match node.as_ref() { + Node::Leaf(existing) => { + let mut new_text = String::with_capacity(existing.len() + text.len()); + new_text.push_str(&existing[..index]); + new_text.push_str(text); + new_text.push_str(&existing[index..]); + Node::new_leaf(&new_text) + } + Node::Internal { left, right, weight } => { + if index < *weight { + let left = self.insert_rec(left, index, text); + return Node::new_internal(left, right.clone()); + } else { + let right = self.insert_rec(right, index - weight, text); + return Node::new_internal(left.clone(), right); + } + } + } + } + + fn collect_text(&self, node: &Rc, result: &mut String) { + match node.as_ref() { + Node::Leaf(text) => result.push_str(text), + Node::Internal { left, right, .. } => { + self.collect_text(left, result); + self.collect_text(right, result); + } + } + } +} + +impl fmt::Display for Rope { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut result = String::new(); + self.collect_text(&self.root, &mut result); + f.write_str(&result) + } +}