From b5f471c6a5c517605aa0c53738d4e916579ede78 Mon Sep 17 00:00:00 2001 From: Rose Hogenson Date: Sat, 6 Jan 2024 11:14:24 -0800 Subject: Use more idiomatic error handling. --- src/main.rs | 141 +++++++++++++++++++++--------------------------------------- src/rope.rs | 64 ++++----------------------- src/term.rs | 55 +++++++++++------------- 3 files changed, 82 insertions(+), 178 deletions(-) (limited to 'src') diff --git a/src/main.rs b/src/main.rs index 599ed36..a427d27 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ mod term; use rope::Rope; use std::ffi::{OsStr, OsString}; use std::io::{stdout, stdin, Write, Read}; +use std::error::Error; use std::path::Path; use term::Key; @@ -15,77 +16,56 @@ struct State { line_cols: usize, } -impl State { - fn cursor_pos(&mut self) -> Result<(usize, usize), String> { - if let Err(err) = write!(stdout(), "\x1b[6n") { - return Err(format!("cursor position: status report: {}", err)); - } - if let Err(err) = stdout().flush() { - return Err(format!("cursor position: status report: {}", err)); +fn parse_status_report(buf: &[u8]) -> Result<(usize, usize), Box> { + let mut semicolon = 0; + for (i, &c) in buf.iter().enumerate() { + if c == b';' { + semicolon = i; + break; } + } + if semicolon < 2 || semicolon == buf.len()-1 { + return Err(Box::from("invalid response")); + } + let row_str = std::str::from_utf8(&buf[2..semicolon])?; + let col_str = std::str::from_utf8(&buf[semicolon+1..buf.len()-1])?; + let row = row_str.parse::()?; + let col = col_str.parse::()?; + // Yuck... 1 indexing + return Ok((row-1, col-1)); +} + +impl State { + fn cursor_pos(&mut self) -> Result<(usize, usize), Box> { + write!(stdout(), "\x1b[6n").map_err(|err| format!("cursor position: status report: {}", err))?; + stdout().flush().map_err(|err| format!("cursor position: status report: {}", err))?; let mut buf = vec![0; 20]; - let n = match stdin().read(&mut buf) { - Ok(n) => n, - Err(err) => { - return Err(format!("cursor position: read status report: {}", err)); - } - }; + let n = stdin().read(&mut buf).map_err(|err| format!("cursor position: read status report: {}", err))?; buf.truncate(n); - let mut semicolon = 0; - for (i, &c) in buf.iter().enumerate() { - if c == b';' { - semicolon = i; - break; - } - } - let row_str = match std::str::from_utf8(&buf[2..semicolon]) { - Ok(row_str) => row_str, - Err(_) => { - return Err(format!("cursor position: invalid response {}", String::from_utf8_lossy(&buf))); - } - }; - let col_str = match std::str::from_utf8(&buf[semicolon+1..buf.len()-1]) { - Ok(col_str) => col_str, - Err(_) => { - return Err(format!("cursor position: invalid response {}", String::from_utf8_lossy(&buf))); - } - }; - let row = match row_str.parse::() { - Ok(row) => row, - Err(_) => { - return Err(format!("cursor position: invalid response {}", String::from_utf8_lossy(&buf))); - } - }; - let col = match col_str.parse::() { - Ok(col) => col, - Err(_) => { - return Err(format!("cursor position: invalid response {}", String::from_utf8_lossy(&buf))); - } - }; - // Yuck... 1 indexing - return Ok((row-1, col-1)); + let pos = parse_status_report(&buf).map_err(|_| format!("cursor position: invalid response: {}", String::from_utf8_lossy(&buf)))?; + return Ok(pos); } - fn repaint_line_full(&mut self) { - let _ = write!(stdout(), "\x1b[{}H", self.cursor_row+1); + fn repaint_line_full(&mut self) -> Result<(), Box> { + write!(stdout(), "\x1b[{}H", self.cursor_row+1)?; let line = self.buf.line(self.cursor_row); - let _ = line.print(&mut stdout()); - let (_, col) = self.cursor_pos().expect("asdf"); + line.print(&mut stdout())?; + let (_, col) = self.cursor_pos()?; self.line_cols = col; if self.cursor_col >= self.line_cols { self.line_offset = line.len(); - return; + return Ok(()); } - let mut lo = line.char(self.cursor_col).expect("at this point, cursor_col should have some valid offset"); + let mut lo = line.ceil_char_boundary(self.cursor_col); let mut hi = line.len(); // Binary search :D while hi > lo { let x = line.floor_char_boundary(lo + (hi - lo) / 2); - let _ = write!(stdout(), "\x1b[G"); - let _ = line.slice(0, x).print(&mut stdout()); - let (_, col) = self.cursor_pos().expect("ouchie, I should really handle this I guess"); + write!(stdout(), "\x1b[G")?; + line.slice(0, x).print(&mut stdout())?; + let (_, col) = self.cursor_pos()?; if col <= self.cursor_col { lo = line.ceil_char_boundary(x+1); } else { @@ -93,32 +73,18 @@ impl State { } } self.line_offset = line.floor_char_boundary(lo-1); - let _ = write!(stdout(), "\x1b[G\x1b[K"); - let _ = line.print(&mut stdout()); - let _ = write!(stdout(), "\x1b[{}G", self.cursor_col+1); + write!(stdout(), "\x1b[G\x1b[K")?; + line.print(&mut stdout())?; + write!(stdout(), "\x1b[{}G", self.cursor_col+1)?; + return Ok(()); } } -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")); - } - }; +fn edit(file: &OsStr) -> Result<(), Box> { + let _raw_handle = term::raw().map_err(|err| format!("cannot put terminal in raw mode: {}", err))?; - let 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 r = Rope::open(Path::new(file))?; + let size = term::size()?; let mut state = State{ buf: r, cursor_row: 0, @@ -146,20 +112,13 @@ fn edit(file: &OsStr) -> Result<(), String> { let _ = stdout().flush(); loop { - let c = match term::read_key(&mut stdin()) { - Ok(c) => c, - Err(err) => { - return Err(err); - } - }; + let c = term::read_key(&mut stdin()).map_err(|err| format!("read key: {}", err))?; match c { Key::CtrlQ => { break; } Key::CtrlS => { - if let Err(err) = state.buf.save(Path::new(file)) { - return Err(format!("write file {}: {}", file.to_string_lossy(), err)); - } + let _ = state.buf.save(Path::new(file)); } Key::Up => { if state.cursor_row == 0 { @@ -167,7 +126,7 @@ fn edit(file: &OsStr) -> Result<(), String> { } state.cursor_row -= 1; - state.repaint_line_full(); + let _ = state.repaint_line_full(); let _ = stdout().flush(); } Key::Down => { @@ -176,7 +135,7 @@ fn edit(file: &OsStr) -> Result<(), String> { } state.cursor_row += 1; - state.repaint_line_full(); + let _ = state.repaint_line_full(); let _ = stdout().flush(); } Key::Left => { @@ -185,7 +144,7 @@ fn edit(file: &OsStr) -> Result<(), String> { continue; } state.cursor_col -= 1; - state.repaint_line_full(); + let _ = state.repaint_line_full(); let _ = stdout().flush(); } Key::Right => { @@ -193,14 +152,14 @@ fn edit(file: &OsStr) -> Result<(), String> { continue; } state.cursor_col += 1; - state.repaint_line_full(); + let _ = state.repaint_line_full(); let _ = stdout().flush(); } Key::Char(c) => { state.cursor_col = std::cmp::min(state.cursor_col, state.line_cols); state.buf = state.buf.insert(state.buf.line_idx(state.cursor_row)+state.line_offset, c); state.cursor_col += 1; - state.repaint_line_full(); + let _ = state.repaint_line_full(); let _ = stdout().flush(); } } diff --git a/src/rope.rs b/src/rope.rs index ea37e6c..c5e9aa1 100644 --- a/src/rope.rs +++ b/src/rope.rs @@ -1,6 +1,7 @@ use std::fs::File; use std::io::{ErrorKind, Read, Write}; use std::path::Path; +use std::error::Error; use std::rc::Rc; // Set to a small value to help identify bugs in the implementation. @@ -167,13 +168,8 @@ impl Rope { } } - pub fn open(path: &Path) -> Result { - let mut f = match File::open(path) { - Ok(f) => f, - Err(err) => { - return Err(format!("open file {}: {}", path.display(), err)); - } - }; + pub fn open(path: &Path) -> Result> { + let mut f = File::open(path).map_err(|err| format!("open file {}: {}", path.display(), err))?; let mut rope = Rope::leaf(Vec::new()); loop { let mut buf = vec![0; MAX_NODE_SIZE]; @@ -183,7 +179,7 @@ impl Rope { if err.kind() == ErrorKind::Interrupted { continue; } - return Err(format!("read file {}: {}", path.display(), err)); + return Err(Box::from(format!("read file {}: {}", path.display(), err))); } }; if n == 0 { @@ -196,19 +192,10 @@ impl Rope { return Ok(rope); } - pub fn save(&self, path: &Path) -> Result<(), String> { - let mut f = match File::create(path) { - Ok(f) => f, - Err(err) => { - return Err(format!("save: create file {}: {}", path.display(), err)); - } - }; - if let Err(err) = self.print(&mut f) { - return Err(format!("save: write file {}: {}", path.display(), err)); - } - if let Err(err) = f.sync_all() { - return Err(format!("save: write file {}: {}", path.display(), err)); - } + pub fn save(&self, path: &Path) -> Result<(), Box> { + let mut f = File::create(path).map_err(|err| format!("save: create file {}: {}", path.display(), err))?; + self.print(&mut f).map_err(|err| format!("save: write file {}: {}", path.display(), err))?; + f.sync_all().map_err(|err| format!("save: write file {}: {}", path.display(), err))?; return Ok(()); } @@ -260,41 +247,6 @@ impl Rope { return self.slice(start, end-1); } - pub fn char(&self, n: usize) -> Result { - match self { - Rope(Node::Leaf(l)) => { - let mut count = 0; - for (i, &b) in l.bytes().iter().enumerate() { - if b & 0xc0 == 0x80 { - continue; - } - if count == n { - return Ok(i); - } - count += 1; - } - return Err(count); - } - Rope(Node::Branch(b)) => { - match b.left.char(n) { - Ok(i) => { - return Ok(i); - } - Err(len) => { - match b.right.char(n - len) { - Ok(i) => { - return Ok(b.left.len() + i); - } - Err(right_len) => { - return Err(len + right_len); - } - } - } - } - } - } - } - fn is_char_boundary(&self, index: usize) -> bool { match self { Rope(Node::Leaf(l)) => { diff --git a/src/term.rs b/src/term.rs index c4319b4..c23dc4e 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,32 +1,36 @@ use libc::{termios, winsize}; +use std::error::Error; use std::io::{Read, Stdin}; -pub fn size() -> Option { +pub fn size() -> Result> { unsafe { let mut window: winsize = std::mem::zeroed(); - if libc::ioctl(1, libc::TIOCGWINSZ, &mut window) < 0 { - return None; + let status = libc::ioctl(1, libc::TIOCGWINSZ, &mut window); + if status < 0 { + return Err(Box::from(format!("terminal size: ioctl failed with code {}", status))); } - return Some(window); + return Ok(window); } } -fn get_attr() -> Option { +fn get_attr() -> Result> { unsafe { let mut attr: termios = std::mem::zeroed(); - if libc::tcgetattr(1, &mut attr) < 0 { - return None; + let status = libc::tcgetattr(1, &mut attr); + if status < 0 { + return Err(Box::from(format!("tcgetattr failed with code {}", status))); } - return Some(attr); + return Ok(attr); } } -fn set_attr(attrs: &termios) -> Option<()> { +fn set_attr(attrs: &termios) -> Result<(), Box> { unsafe { - if libc::tcsetattr(1, libc::TCSANOW, attrs) < 0 { - return None; + let status = libc::tcsetattr(1, libc::TCSANOW, attrs); + if status < 0 { + return Err(Box::from(format!("set attributes: code {}", status))); } - return Some(()); + return Ok(()); } } @@ -44,21 +48,14 @@ pub struct RawHandle { impl Drop for RawHandle { fn drop(&mut self) { - set_attr(&self.old); + let _ = set_attr(&self.old); } } -pub fn raw() -> Option { - let old = match get_attr() { - Some(old) => old, - None => { - return None; - } - }; - if let None = set_attr(&make_raw()) { - return None; - } - return Some(RawHandle { old: old }); +pub fn raw() -> Result> { + let old = get_attr()?; + set_attr(&make_raw())?; + return Ok(RawHandle { old: old }); } pub enum Key { @@ -71,16 +68,14 @@ pub enum Key { Char(u8), } -pub fn read_key(stdin: &mut Stdin) -> Result { +pub fn read_key(stdin: &mut Stdin) -> Result> { const CTRL_Q: u8 = 17; const CTRL_S: u8 = 19; const ESC: u8 = 27; loop { let mut buf = vec![0; 1]; - if let Err(err) = stdin.read_exact(&mut buf) { - return Err(format!("read input: {}", err)); - } + stdin.read_exact(&mut buf)?; if buf[0] == CTRL_Q { return Ok(Key::CtrlQ); } @@ -92,9 +87,7 @@ pub fn read_key(stdin: &mut Stdin) -> Result { } // Try to handle an escape sequence. let mut buf = vec![0; 2]; - if let Err(err) = stdin.read_exact(&mut buf) { - return Err(format!("read input: {}", err)); - } + stdin.read_exact(&mut buf)?; if buf[0] != b'[' { // Unknown escape sequence, just read another key. continue; -- cgit v1.3.1