diff options
| author | Rose Hogenson <rosehogenson@posteo.net> | 2024-01-13 12:31:42 -0800 |
|---|---|---|
| committer | Rose Hogenson <rosehogenson@posteo.net> | 2024-01-13 12:31:42 -0800 |
| commit | 1e32f5d480ecefa3a1c8fd1c43ac6b4260459266 (patch) | |
| tree | 2c268543df0f19fa424a8d01b642e23c3d38771e | |
| parent | d8df9a6e5f09a0f9649ef16d15f86a22c4720ade (diff) | |
| download | editor-1e32f5d480ecefa3a1c8fd1c43ac6b4260459266.tar.zst | |
Reject files with invalid utf-8.
It's easier to only support files with valid utf-8.
| -rw-r--r-- | src/main.rs | 43 | ||||
| -rw-r--r-- | src/rope.rs | 99 | ||||
| -rw-r--r-- | src/term.rs | 24 |
3 files changed, 101 insertions, 65 deletions
diff --git a/src/main.rs b/src/main.rs index 5b9d2f5..0cc9ad4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use std::fs::File; use std::io::{stderr, stdout, Write}; use std::path::{Path, PathBuf}; -fn open(file: &Path) -> Result<Rope, std::io::Error> { +fn open(file: &Path) -> Result<Rope, Box<dyn Error>> { let mut f = File::open(file)?; let r = Rope::read(&mut f)?; Ok(r) @@ -38,10 +38,7 @@ fn line_offset(stdin: &mut Reader, line: &Rope, col: u16) -> Result<usize, Box<d let mut lo = 0; let mut hi = line.len() + 1; while lo < hi { - let mut h = lo + (hi - lo) / 2; - if h < line.len() { - h = line.floor_char_boundary(h); - } + let h = line.floor_char_boundary(lo + (hi - lo) / 2); write!(stdout(), "\x1b[G")?; line.slice(0, h).print(&mut stdout())?; let (_, c) = stdin.cursor_pos()?; @@ -234,12 +231,12 @@ impl State { } fn keypress(&mut self, key: Key) -> Result<bool, Box<dyn Error>> { - const CTRL_Q: u8 = b'Q' - b'@'; - const CTRL_S: u8 = b'S' - b'@'; - const CTRL_Y: u8 = b'Y' - b'@'; - const CTRL_Z: u8 = b'Z' - b'@'; - const BACKSPACE: u8 = b'H' - b'@'; - const OTHER_BACKSPACE: u8 = 127; + const CTRL_Q: char = (b'Q' - b'@') as char; + const CTRL_S: char = (b'S' - b'@') as char; + const CTRL_Y: char = (b'Y' - b'@') as char; + const CTRL_Z: char = (b'Z' - b'@') as char; + const BACKSPACE: char = (b'H' - b'@') as char; + const OTHER_BACKSPACE: char = '\x7f'; let inserting = self.inserting; self.inserting = false; @@ -446,15 +443,15 @@ impl State { self.update_cursor_col()?; stdout().flush()?; } - Key::Byte(CTRL_Q) => { + Key::Char(CTRL_Q) => { return Ok(false); } - Key::Byte(CTRL_S) => { + Key::Char(CTRL_S) => { let mut f = File::create(&self.path)?; self.buf.print(&mut f)?; f.sync_all()?; } - Key::Byte(CTRL_Z) => { + Key::Char(CTRL_Z) => { if let Some(n) = self.n { if n > 0 { self.n = Some(n - 1); @@ -468,7 +465,7 @@ impl State { self.update_cursor_col()?; stdout().flush()?; } - Key::Byte(CTRL_Y) => { + Key::Char(CTRL_Y) => { let Some(n) = self.n else { return Ok(true); }; @@ -481,7 +478,7 @@ impl State { self.update_cursor_col()?; stdout().flush()?; } - Key::Byte(BACKSPACE) | Key::Byte(OTHER_BACKSPACE) => { + Key::Char(BACKSPACE) | Key::Char(OTHER_BACKSPACE) => { self.insert(inserting); if self.line_offset == 0 && self.cursor_row == 0 && self.row_start == 0 { return Ok(true); @@ -519,7 +516,7 @@ impl State { self.update_cursor_col()?; stdout().flush()?; } - Key::Byte(b'\r') | Key::Byte(b'\n') => { + Key::Char('\r') | Key::Char('\n') => { self.insert(inserting); let size = term::size()?; let offset = self @@ -529,7 +526,7 @@ impl State { self.buf = self .buf .slice(0, offset) - .concat(&Rope::new(&[b'\n'])) + .concat(&Rope::new("\n")) .concat(&self.buf.slice(offset, self.buf.len())); if self.cursor_row < size.ws_row - 1 { self.cursor_row += 1; @@ -542,20 +539,22 @@ impl State { write!(stdout(), "\r\n")?; stdout().flush()?; } - Key::Byte(c) => { + Key::Char(c) => { self.insert(inserting); let offset = self .buf .line_start(self.row_start + usize::from(self.cursor_row)) + self.line_offset; + let mut buf = [0; 4]; + let s = c.encode_utf8(&mut buf); self.buf = self .buf .slice(0, offset) - .concat(&Rope::new(&[c])) + .concat(&Rope::new(s)) .concat(&self.buf.slice(offset, self.buf.len())); - self.line_offset += 1; + self.line_offset += s.len(); self.need_repaint_line = true; - stdout().write_all(&[c])?; + stdout().write_all(s.as_bytes())?; stdout().flush()?; } } diff --git a/src/rope.rs b/src/rope.rs index 4e14d84..ab9a7bc 100644 --- a/src/rope.rs +++ b/src/rope.rs @@ -1,8 +1,23 @@ +use std::error::Error; use std::io::{ErrorKind, Read, Write}; use std::rc::Rc; const MAX_NODE_SIZE: usize = 1024; +fn read(r: &mut dyn Read, buf: &mut [u8]) -> Result<usize, std::io::Error> { + loop { + match r.read(buf) { + Ok(n) => return Ok(n), + Err(err) => { + if err.kind() == ErrorKind::Interrupted { + continue; + } + return Err(err); + } + } + } +} + enum Insert { Node(Rope), Split(Rope, Rope), @@ -10,16 +25,16 @@ enum Insert { #[derive(Debug, Clone)] struct Leaf { - unsafe_buf: Option<Rc<[u8]>>, + unsafe_buf: Option<Rc<str>>, start: u16, end: u16, lines: u16, } impl Leaf { - fn bytes(&self) -> &[u8] { + fn buf(&self) -> &str { match &self.unsafe_buf { - None => &[], + None => "", Some(buf) => &buf[usize::from(self.start)..usize::from(self.end)], } } @@ -44,7 +59,7 @@ pub struct Rope(Node); impl Rope { pub fn len(&self) -> usize { match self { - Rope(Node::Leaf(l)) => l.bytes().len(), + Rope(Node::Leaf(l)) => l.buf().len(), Rope(Node::Branch(b)) => b.len, } } @@ -67,12 +82,12 @@ impl Rope { lines: 0, })); - fn leaf(buf: &[u8]) -> Rope { + fn leaf(buf: &str) -> Rope { if buf.len() > MAX_NODE_SIZE { panic!("buffer too long"); } let mut lines = 0; - for &b in buf.iter() { + for &b in buf.as_bytes().iter() { if b == b'\n' { lines += 1; } @@ -108,19 +123,20 @@ impl Rope { fn concat_height(&self, self_height: usize, other: &Rope, other_height: usize) -> Insert { if let (Rope(Node::Leaf(l)), Rope(Node::Leaf(r))) = (self, other) { if self.len() + other.len() <= MAX_NODE_SIZE { - let mut buf = Vec::with_capacity(self.len() + other.len()); - buf.extend_from_slice(l.bytes()); - buf.extend_from_slice(r.bytes()); + let mut buf = String::with_capacity(self.len() + other.len()); + buf.push_str(l.buf()); + buf.push_str(r.buf()); return Insert::Node(Rope::leaf(&buf)); } if self.len() < MAX_NODE_SIZE / 2 || other.len() < MAX_NODE_SIZE / 2 { - let mut buf = Vec::with_capacity(self.len() + other.len()); - buf.extend_from_slice(l.bytes()); - buf.extend_from_slice(r.bytes()); - return Insert::Split( - Rope::leaf(&buf[..buf.len() / 2]), - Rope::leaf(&buf[buf.len() / 2..]), - ); + let mut buf = String::with_capacity(self.len() + other.len()); + buf.push_str(l.buf()); + buf.push_str(r.buf()); + let mut half = buf.len() / 2; + while !buf.is_char_boundary(half) { + half -= 1; + } + return Insert::Split(Rope::leaf(&buf[..half]), Rope::leaf(&buf[half..])); } return Insert::Split(self.clone(), other.clone()); } @@ -203,43 +219,52 @@ impl Rope { } } - pub fn read(r: &mut dyn Read) -> Result<Rope, std::io::Error> { + pub fn read(r: &mut dyn Read) -> Result<Rope, Box<dyn Error>> { let mut buf = vec![0; MAX_NODE_SIZE]; let mut rope = Rope::EMPTY; + let mut offset = 0; loop { - let n = match r.read(&mut buf) { - Ok(n) => n, + let mut n = read(r, &mut buf[offset..])?; + n += offset; + if n == 0 { + break; + } + let s = match std::str::from_utf8(&buf[..n]) { + Ok(s) => s, Err(err) => { - if err.kind() == ErrorKind::Interrupted { - continue; + let v = err.valid_up_to(); + if v == 0 { + return Err(Box::new(err)); } - return Err(err); + rope = rope.concat(&Rope::leaf( + std::str::from_utf8(&buf[..v]).expect("buf should be valid up to v"), + )); + offset = n - v; + for i in 0..offset { + buf[i] = buf[v + i]; + } + continue; } }; - if n == 0 { - break; - } - rope = rope.concat(&Rope::leaf(&buf[..n])); + rope = rope.concat(&Rope::leaf(s)); + offset = 0; } Ok(rope) } - pub fn new(buf: &[u8]) -> Rope { + pub fn new(buf: &str) -> Rope { if buf.is_empty() { return Rope::EMPTY; } if buf.len() <= MAX_NODE_SIZE { return Rope::leaf(buf); } - let mut buf = buf; - Rope::read(&mut buf).expect("reading from a slice should never fail") + Rope::read(&mut buf.as_bytes()).expect("reading from a slice should never fail") } fn is_char_boundary(&self, i: usize) -> bool { match self { - Rope(Node::Leaf(l)) => { - return i < l.bytes().len() && l.bytes()[i] & 0xc0 != 0x80; - } + Rope(Node::Leaf(l)) => l.buf().is_char_boundary(i), Rope(Node::Branch(b)) => { let mut i = i; for c in b.children.iter() { @@ -248,7 +273,7 @@ impl Rope { } i -= c.len(); } - false + i == 0 } } } @@ -275,7 +300,7 @@ impl Rope { match self { Rope(Node::Leaf(l)) => { let mut count = 0; - for (i, &c) in l.bytes().iter().enumerate() { + for (i, &c) in l.buf().as_bytes().iter().enumerate() { if c != b'\n' { continue; } @@ -317,7 +342,7 @@ impl Rope { pub fn print(&self, w: &mut dyn Write) -> Result<(), std::io::Error> { match self { Rope(Node::Leaf(l)) => { - w.write_all(l.bytes())?; + w.write_all(l.buf().as_bytes())?; Ok(()) } Rope(Node::Branch(b)) => { @@ -349,7 +374,7 @@ impl Rope { match self { Rope(Node::Leaf(l)) => { let mut lines = 0; - for &b in l.bytes()[start..end].iter() { + for &b in l.buf()[start..end].as_bytes().iter() { if b == b'\n' { lines += 1; } @@ -388,7 +413,7 @@ impl Rope { panic!("Index {} out of range 0..{}", i, self.len()); } match self { - Rope(Node::Leaf(l)) => l.bytes()[i], + Rope(Node::Leaf(l)) => l.buf().as_bytes()[i], Rope(Node::Branch(b)) => { let mut i = i; for c in b.children.iter() { diff --git a/src/term.rs b/src/term.rs index e390125..831cd43 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,6 +1,6 @@ use libc::{termios, winsize}; use std::error::Error; -use std::io::{stdout, BufRead, BufReader, Stdin, Write}; +use std::io::{stdout, BufRead, BufReader, Read, Stdin, Write}; const ESC: u8 = 27; @@ -83,7 +83,7 @@ pub enum Key { End, PgDn, PgUp, - Byte(u8), + Char(char), } pub struct Reader { @@ -110,9 +110,21 @@ impl Reader { return Ok(Key::Timeout); } if buf[0] != ESC { - let b = buf[0]; - self.stdin.consume(1); - return Ok(Key::Byte(b)); + let mut n = usize::try_from(buf[0].leading_ones()) + .expect("buf[0] is a u8 so it can only have 8 leading ones"); + if n == 0 { + n = 1; + } + let mut char = [0; 4]; + self.stdin.read_exact(&mut char[..n])?; + let Ok(s) = std::str::from_utf8(&char) else { + continue; + }; + return Ok(Key::Char( + s.chars() + .nth(0) + .expect("s should be exactly one codepoint long"), + )); } if buf.len() < 3 || buf[1] != b'[' { // Unknown escape sequence, eat the escape and one more byte. @@ -173,7 +185,7 @@ impl Reader { } "3~" => { // Backspace - return Ok(Key::Byte(8)); + return Ok(Key::Char(char::from(b'H' - b'@'))); } _ => { continue; |
