#![forbid(unsafe_code)] use crate::types::*; use smol::prelude::*; use smol::fs::File; use smol::lock::RwLock; use std::path::PathBuf; use std::process::ExitCode; use std::ops::Range; mod encoding; mod terminal; mod types; struct Ivy { terminal: RwLock, buffer: RwLock, window: RwLock, argument_files: Vec, } struct Buffer { path: Option, contents: RwLock>, lines: RwLock>, has_end_newline: RwLock, } struct Window { /* The traditional order of writing "row, column" is the opposite of the * traditional order of writing "x, y"; be mindful. An alternate approach * would use "x" and "y" for all the names, which might arguably reduce * confusion, but personally we find the ordering concern to be * aesthetically pleasing, a quiet reminder of how much humans care about * everything. */ cursor_column: RwLock, cursor_row: RwLock, } fn main() -> ExitCode { smol::block_on(async { if let Err(e) = Ivy::new().await.run().await { println!("{:?}", e); ExitCode::FAILURE } else { ExitCode::SUCCESS } }) } impl Ivy { async fn new() -> Self { Ivy { terminal: RwLock::new(Terminal::new().await), buffer: RwLock::new(Buffer::new().await), window: RwLock::new(Window::new().await), argument_files: Vec::new(), } } async fn run(mut self) -> Result<()> { /* The awkward structure here is because it's important that cleanup still * happen even if something fails in the middle. */ let mut error = None; error = error.or(self.init().await.err()); error = error.or(self.interact().await.err()); error = error.or(self.zap().await.err()); if let Some(error) = error { Err(error) } else { Ok(()) } } async fn init(&mut self) -> Result<()> { self.init_arguments().await?; self.init_editor().await?; self.terminal.write().await.init_termios().await?; self.terminal.write().await.init_full_screen().await?; Ok(()) } async fn zap(&mut self) -> Result<()> { let mut error = None; error = error.or( self.terminal.write().await.zap_full_screen().await.err()); error = error.or( self.terminal.write().await.zap_termios().await.err()); if let Some(error) = error { Err(error) } else { Ok(()) } } async fn init_arguments(&mut self) -> Result<()> { self.argument_files.clear(); let mut args = std::env::args(); let _ = args.next(); for argument in args { self.argument_files.push(argument.into()); } Ok(()) } async fn init_editor(&mut self) -> Result<()> { if !self.argument_files.is_empty() { let path = self.argument_files.remove(0); let new_buffer = Buffer::from_file(path).await?; *self.buffer.write().await = new_buffer; } Ok(()) } async fn draw(&mut self) -> Result<()> { let mut terminal = self.terminal.write().await; let buffer = self.buffer.read().await; let height = *terminal.height.read().await; let mut screen_y = 0; for logical_y in 0 .. height - 1 { if let Some(span) = buffer.line_span(logical_y).await { if logical_y > 0 { terminal.stdout.write_all("\n".as_bytes()).await?; } terminal.stdout.write_all(&buffer.contents.read().await[span]).await?; screen_y = logical_y + 1; } else { break; } } for _ in screen_y .. height - 1 { terminal.stdout.write_all("\n~".as_bytes()).await?; } terminal.stdout.write_all("\n status goes here".as_bytes()).await?; let window = self.window.read().await; terminal.do_cursor_position(*window.cursor_column.read().await, *window.cursor_row.read().await).await?; terminal.stdout.flush().await?; Ok(()) } async fn interact(&mut self) -> Result<()> { self.draw().await?; loop { let c = self.terminal.write().await.read_char().await?; match c { 'h' => { self.handle_movement(async |ivy: &mut Ivy| { let window = ivy.window.write().await; let mut column = window.cursor_column.write().await; if *column > 0 { *column -= 1; } Ok(()) }).await?; }, 'j' => { self.handle_movement(async |ivy: &mut Ivy| { let window = ivy.window.write().await; let mut row = window.cursor_row.write().await; *row += 1; Ok(()) }).await?; }, 'k' => { self.handle_movement(async |ivy: &mut Ivy| { let window = ivy.window.write().await; let mut row = window.cursor_row.write().await; if *row > 0 { *row -= 1; } Ok(()) }).await?; }, 'l' => { self.handle_movement(async |ivy: &mut Ivy| { let window = ivy.window.write().await; let mut column = window.cursor_column.write().await; *column += 1; Ok(()) }).await?; }, _ => { return Ok(()); }, } } } async fn handle_movement(&mut self, movement: impl AsyncFn(&mut Ivy) -> Result<()>) -> Result<()> { let (old_row, old_column) = { let window = self.window.read().await; (*window.cursor_row.read().await, *window.cursor_column.read().await) }; movement(self).await?; let (new_row, new_column) = { let window = self.window.read().await; (*window.cursor_row.read().await, *window.cursor_column.read().await) }; if old_row != new_row || old_column != new_column { let mut terminal = self.terminal.write().await; terminal.do_cursor_position(new_column, new_row).await?; terminal.stdout.flush().await?; } Ok(()) } } impl Buffer { pub async fn new() -> Self { Buffer { path: None, contents: RwLock::new("\n".to_string().into_bytes()), lines: RwLock::new(vec![0]), has_end_newline: RwLock::new(true), } } pub async fn from_file(path: PathBuf) -> Result { let mut file = File::open(&path).await?; let mut contents = Vec::new(); let _ = file.read_to_end(&mut contents).await?; let mut result = Buffer { path: Some(path), contents: RwLock::new(contents), lines: RwLock::new(vec![0]), has_end_newline: RwLock::new(true), }; result.count_lines().await; Ok(result) } pub async fn count_lines(&mut self) { let contents = self.contents.read().await; *self.lines.write().await = vec![0]; if contents.len() == 0 { *self.has_end_newline.write().await = false; } else { let mut offset = 0; while offset < contents.len() { let c = contents[offset]; if c == b'\n' { offset += 1; if offset < contents.len() { self.lines.write().await.push(offset); } else { *self.has_end_newline.write().await = true; } } else if offset + 1 == contents.len() { *self.has_end_newline.write().await = false; } offset += 1; } } } pub async fn line_span(&self, index: usize) -> Option> { let lines = self.lines.read().await; if index < lines.len() { let start = lines[index]; if index + 1 < lines.len() { let end = lines[index + 1] - 1; Some(start .. end) } else { let end = if *self.has_end_newline.read().await { self.contents.read().await.len() - 1 } else { self.contents.read().await.len() }; Some(start .. end) } } else { None } } } impl Window { pub async fn new() -> Self { Window { cursor_column: RwLock::new(0), cursor_row: RwLock::new(0), } } }