Skip to content

Commit 75595a1

Browse files
authored
Merge pull request #74 from sminez/utf8-input
#65 handling utf8 input over stdin
2 parents f280382 + 876712e commit 75595a1

File tree

1 file changed

+45
-8
lines changed

1 file changed

+45
-8
lines changed

src/ui/tui.rs

+45-8
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::{
2222
use std::{
2323
char,
2424
cmp::{min, Ordering},
25-
io::{stdin, stdout, Read, Stdin, Stdout, Write},
25+
io::{stdin, stdout, Read, Stdout, Write},
2626
panic,
2727
sync::mpsc::Sender,
2828
thread::{spawn, JoinHandle},
@@ -748,16 +748,30 @@ fn spawn_input_thread(tx: Sender<Event>) -> JoinHandle<()> {
748748
})
749749
}
750750

751-
fn try_read_char(stdin: &mut Stdin) -> Option<char> {
752-
let mut buf: [u8; 1] = [0; 1];
753-
if stdin.read_exact(&mut buf).is_ok() {
754-
Some(buf[0] as char)
755-
} else {
756-
None
751+
fn try_read_char(stdin: &mut impl Read) -> Option<char> {
752+
let mut buf: [u8; 4] = [0; 4];
753+
754+
for i in 0..4 {
755+
if stdin.read_exact(&mut buf[i..i + 1]).is_err() {
756+
return if i == 0 {
757+
None
758+
} else {
759+
Some(char::REPLACEMENT_CHARACTER)
760+
};
761+
}
762+
763+
match std::str::from_utf8(&buf[0..i + 1]) {
764+
Ok(s) => return s.chars().next(),
765+
Err(e) if e.error_len().is_some() => return Some(char::REPLACEMENT_CHARACTER),
766+
Err(_) => (),
767+
}
757768
}
769+
770+
// utf8 requires at most 4 bytes so at this point we have invalid data
771+
Some(char::REPLACEMENT_CHARACTER)
758772
}
759773

760-
fn try_read_input(stdin: &mut Stdin) -> Option<Input> {
774+
fn try_read_input(stdin: &mut impl Read) -> Option<Input> {
761775
let c = try_read_char(stdin)?;
762776

763777
// Normal key press
@@ -811,3 +825,26 @@ fn try_read_input(stdin: &mut Stdin) -> Option<Input> {
811825

812826
Some(Input::Esc)
813827
}
828+
829+
#[cfg(test)]
830+
mod tests {
831+
use super::*;
832+
use simple_test_case::test_case;
833+
use std::{char::REPLACEMENT_CHARACTER, io};
834+
835+
#[test_case("a".as_bytes(), &['a']; "single ascii character")]
836+
#[test_case(&[240, 159, 146, 150], &['💖']; "single utf8 character")]
837+
#[test_case(&[165, 159, 146, 150], &[REPLACEMENT_CHARACTER; 4]; "invalid utf8 with non-ascii prefix")]
838+
#[test_case(&[65, 159, 146, 150], &['A', REPLACEMENT_CHARACTER, REPLACEMENT_CHARACTER, REPLACEMENT_CHARACTER]; "invalid utf8 with ascii prefix")]
839+
#[test]
840+
fn try_read_char_works(bytes: &[u8], expected: &[char]) {
841+
let mut r = io::Cursor::new(bytes);
842+
let mut chars = Vec::new();
843+
844+
while let Some(ch) = try_read_char(&mut r) {
845+
chars.push(ch);
846+
}
847+
848+
assert_eq!(&chars, expected);
849+
}
850+
}

0 commit comments

Comments
 (0)