mod rope; mod term; use rope::Rope; use std::ffi::{OsStr, OsString}; use std::io::{Stdout, Write}; use std::path::Path; use term::Key; fn move_cursor(stdout: &mut Stdout, row: usize, col: usize) { let _ = write!(stdout, "\x1b[{};{}H", row + 1, col + 1); let _ = stdout.flush(); } fn edit(file: &OsStr) -> Result<(), String> { let _raw_handle = match term::raw() { Some(h) => h, None => { return Err(String::from("cannot put terminal in raw mode")); } }; let mut r = match Rope::open(Path::new(file)) { Ok(r) => r, Err(err) => { return Err(format!("open file {}: {}", file.to_string_lossy(), err)); } }; let size = match term::size() { Some(size) => size, None => { return Err(String::from("cannot get terminal size")); } }; let mut stdout = std::io::stdout(); let _ = stdout.write(b"\x1b[J"); for i in 0..usize::from(size.ws_row) { if i > 0 { let _ = stdout.write(b"\r\n"); } let line = match r.line(i) { Some(line) => line, None => { break; } }; // TODO: handle line-wrapping. let _ = line.print(&mut stdout); } let _ = stdout.write(b"\x1b[H"); let _ = stdout.flush(); let mut cursor_row = 0; let mut cursor_col = 0; let mut stdin = std::io::stdin(); loop { let c = match term::read_key(&mut stdin) { Ok(c) => c, Err(err) => { return Err(err); } }; match c { Key::CtrlQ => { break; } Key::CtrlS => { if let Err(err) = r.save(Path::new(file)) { return Err(format!("write file {}: {}", file.to_string_lossy(), err)); } } Key::Up => { if cursor_row == 0 { continue; } cursor_row -= 1; let len = r .line(cursor_row) .expect("cursor_row should always be valid") .len(); if cursor_col > len { move_cursor(&mut stdout, cursor_row, len); } else { move_cursor(&mut stdout, cursor_row, cursor_col); } } Key::Down => { if cursor_row == usize::from(size.ws_row - 1) || cursor_row == r.lines() { continue; } cursor_row += 1; let len = r .line(cursor_row) .expect("cursor_row should always be valid") .len(); if cursor_col > len { move_cursor(&mut stdout, cursor_row, len); } else { move_cursor(&mut stdout, cursor_row, cursor_col); } } Key::Left => { let len = r .line(cursor_row) .expect("cursor_row should always be valid") .len(); cursor_col = std::cmp::min(cursor_col, len); if cursor_col == 0 { continue; } cursor_col -= 1; move_cursor(&mut stdout, cursor_row, cursor_col); } Key::Right => { let len = r .line(cursor_row) .expect("cursor_row should always be valid") .len(); if cursor_col >= len { continue; } cursor_col += 1; move_cursor(&mut stdout, cursor_row, cursor_col); } Key::Char(c) => { let line = r .line(cursor_row) .expect("cursor_row should always be valid"); cursor_col = std::cmp::min(cursor_col, line.len()); r = line.insert(cursor_col, c); move_cursor(&mut stdout, cursor_row, 0); let _ = r .line(cursor_row) .expect("cursor_row should always be valid") .print(&mut stdout); cursor_col += 1; move_cursor(&mut stdout, cursor_row, cursor_col); } } } return Ok(()); } fn main() { let args: Vec = std::env::args_os().collect(); if args.len() != 2 { println!("Usage: edit "); std::process::exit(1); } if let Err(err) = edit(&args[1]) { println!("FAIL: {}", err); std::process::exit(1); } }