From 8e1b33d923eb2a4182f552e128e8e77dd1abd71e Mon Sep 17 00:00:00 2001 From: Tom Solberg Date: Tue, 9 Jan 2024 13:40:24 +0100 Subject: [PATCH 1/9] Use `out.as_raw_fd()` to get size in `unix_term` (#186) --- CHANGELOG.md | 6 ++++++ src/unix_term.rs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f13206f..11da5002 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Bugfixes + +* Properly use configured output of `Term` to get terminal size (#186) + ## 0.15.7 ### Enhancements diff --git a/src/unix_term.rs b/src/unix_term.rs index 8e1e5925..487d453f 100644 --- a/src/unix_term.rs +++ b/src/unix_term.rs @@ -46,7 +46,7 @@ pub fn c_result libc::c_int>(f: F) -> io::Result<()> { pub fn terminal_size(out: &Term) -> Option<(u16, u16)> { unsafe { - if libc::isatty(libc::STDOUT_FILENO) != 1 { + if libc::isatty(out.as_raw_fd()) != 1 { return None; } From 072f9f76646b56a53782476903d48d9f7d0cc80e Mon Sep 17 00:00:00 2001 From: ChanTsune <41658782+ChanTsune@users.noreply.github.com> Date: Tue, 9 Jan 2024 21:41:25 +0900 Subject: [PATCH 2/9] Support build target `wasm32-unknown-emscripten` (#179) --- .github/workflows/ci.yml | 10 +++++++++- src/lib.rs | 2 +- src/term.rs | 4 ++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ceaaad4..c8b36960 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,7 +89,15 @@ jobs: strategy: fail-fast: false matrix: - target: [wasm32-unknown-unknown] + include: + - target: wasm32-unknown-unknown + os: ubuntu-latest + - target: wasm32-unknown-emscripten + os: ubuntu-latest + - target: wasm32-unknown-emscripten + os: windows-latest + - target: wasm32-unknown-emscripten + os: macos-latest runs-on: ubuntu-latest steps: - name: Install rust diff --git a/src/lib.rs b/src/lib.rs index 1b18afc0..a1ac2275 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,7 +92,7 @@ pub use crate::ansi::{strip_ansi_codes, AnsiCodeIterator}; mod common_term; mod kb; mod term; -#[cfg(unix)] +#[cfg(all(unix, not(target_arch = "wasm32")))] mod unix_term; mod utils; #[cfg(target_arch = "wasm32")] diff --git a/src/term.rs b/src/term.rs index 0a402585..c34ed747 100644 --- a/src/term.rs +++ b/src/term.rs @@ -108,7 +108,7 @@ impl<'a> TermFeatures<'a> { { TermFamily::WindowsConsole } - #[cfg(unix)] + #[cfg(all(unix, not(target_arch = "wasm32")))] { TermFamily::UnixTerm } @@ -624,7 +624,7 @@ impl<'a> Read for &'a Term { } } -#[cfg(unix)] +#[cfg(all(unix, not(target_arch = "wasm32")))] pub use crate::unix_term::*; #[cfg(target_arch = "wasm32")] pub use crate::wasm_term::*; From 50ccfcd2bcc7097d631ea452cfa3ea0f17ccc199 Mon Sep 17 00:00:00 2001 From: Thibault Date: Tue, 9 Jan 2024 13:42:52 +0100 Subject: [PATCH 3/9] Update read_line behaviour to read_line_initial_text (#181) --- src/term.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/term.rs b/src/term.rs index c34ed747..1d0643d9 100644 --- a/src/term.rs +++ b/src/term.rs @@ -284,14 +284,7 @@ impl Term { /// This does not include the trailing newline. If the terminal is not /// user attended the return value will always be an empty string. pub fn read_line(&self) -> io::Result { - if !self.is_tty { - return Ok("".into()); - } - let mut rv = String::new(); - io::stdin().read_line(&mut rv)?; - let len = rv.trim_end_matches(&['\r', '\n'][..]).len(); - rv.truncate(len); - Ok(rv) + self.read_line_initial_text("") } /// Read one line of input with initial text. From ed4ca6163ee74a32b8106666f82530f6b70a3d2a Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Tue, 9 Jan 2024 04:43:27 -0800 Subject: [PATCH 4/9] Add a small example for working out control keys (#192) --- examples/keyboard.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 examples/keyboard.rs diff --git a/examples/keyboard.rs b/examples/keyboard.rs new file mode 100644 index 00000000..2dae1acd --- /dev/null +++ b/examples/keyboard.rs @@ -0,0 +1,16 @@ +use std::io; + +use console::{Key, Term}; + +fn main() -> io::Result<()> { + let term = Term::stdout(); + term.write_line("Press any key. Esc to exit")?; + loop { + let key = term.read_key()?; + term.write_line(&format!("You pressed {:?}", key))?; + if key == Key::Escape { + break; + } + } + Ok(()) +} From e750152d4fb93f75d3789e44954c82aa27ae5fd8 Mon Sep 17 00:00:00 2001 From: terrarier2111 <58695553+terrarier2111@users.noreply.github.com> Date: Tue, 9 Jan 2024 13:43:46 +0100 Subject: [PATCH 5/9] Fix behavior of `read_line_initial_text` (#190) --- src/term.rs | 83 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 25 deletions(-) diff --git a/src/term.rs b/src/term.rs index 1d0643d9..4ccafb61 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,6 +1,6 @@ use std::fmt::{Debug, Display}; use std::io::{self, Read, Write}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; #[cfg(unix)] use std::os::unix::io::{AsRawFd, RawFd}; @@ -41,6 +41,8 @@ pub enum TermTarget { pub struct TermInner { target: TermTarget, buffer: Option>>, + prompt: RwLock, + prompt_guard: Mutex<()>, } /// The family of the terminal. @@ -149,6 +151,8 @@ impl Term { Term::with_inner(TermInner { target: TermTarget::Stdout, buffer: None, + prompt: RwLock::new(String::new()), + prompt_guard: Mutex::new(()), }) } @@ -158,6 +162,8 @@ impl Term { Term::with_inner(TermInner { target: TermTarget::Stderr, buffer: None, + prompt: RwLock::new(String::new()), + prompt_guard: Mutex::new(()), }) } @@ -166,6 +172,8 @@ impl Term { Term::with_inner(TermInner { target: TermTarget::Stdout, buffer: Some(Mutex::new(vec![])), + prompt: RwLock::new(String::new()), + prompt_guard: Mutex::new(()), }) } @@ -174,6 +182,8 @@ impl Term { Term::with_inner(TermInner { target: TermTarget::Stderr, buffer: Some(Mutex::new(vec![])), + prompt: RwLock::new(String::new()), + prompt_guard: Mutex::new(()), }) } @@ -201,6 +211,8 @@ impl Term { style, }), buffer: None, + prompt: RwLock::new(String::new()), + prompt_guard: Mutex::new(()), }) } @@ -231,14 +243,21 @@ impl Term { /// Write a string to the terminal and add a newline. pub fn write_line(&self, s: &str) -> io::Result<()> { + let prompt = self.inner.prompt.read().unwrap(); + if !prompt.is_empty() { + self.clear_line()?; + } match self.inner.buffer { Some(ref mutex) => { let mut buffer = mutex.lock().unwrap(); buffer.extend_from_slice(s.as_bytes()); buffer.push(b'\n'); + buffer.extend_from_slice(prompt.as_bytes()); Ok(()) } - None => self.write_through(format!("{}\n", s).as_bytes()), + None => { + self.write_through(format!("{}\n{}", s, prompt.as_str()).as_bytes()) + } } } @@ -288,40 +307,54 @@ impl Term { } /// Read one line of input with initial text. - /// + /// + /// This method blocks until no other thread is waiting for this read_line + /// before reading a line from the terminal. /// This does not include the trailing newline. If the terminal is not /// user attended the return value will always be an empty string. pub fn read_line_initial_text(&self, initial: &str) -> io::Result { if !self.is_tty { return Ok("".into()); } - self.write_str(initial)?; + *self.inner.prompt.write().unwrap() = initial.to_string(); + // use a guard in order to prevent races with other calls to read_line_initial_text + let _guard = self.inner.prompt_guard.lock().unwrap(); - let mut chars: Vec = initial.chars().collect(); + self.write_str(initial)?; - loop { - match self.read_key()? { - Key::Backspace => { - if chars.pop().is_some() { - self.clear_chars(1)?; + fn read_line_internal(slf: &Term, initial: &str) -> io::Result { + let prefix_len = initial.len(); + + let mut chars: Vec = initial.chars().collect(); + + loop { + match slf.read_key()? { + Key::Backspace => { + if prefix_len < chars.len() && chars.pop().is_some() { + slf.clear_chars(1)?; + } + slf.flush()?; + } + Key::Char(chr) => { + chars.push(chr); + let mut bytes_char = [0; 4]; + chr.encode_utf8(&mut bytes_char); + slf.write_str(chr.encode_utf8(&mut bytes_char))?; + slf.flush()?; + } + Key::Enter => { + slf.write_through(format!("\n{}", initial).as_bytes())?; + break; + } + _ => (), } - self.flush()?; - } - Key::Char(chr) => { - chars.push(chr); - let mut bytes_char = [0; 4]; - chr.encode_utf8(&mut bytes_char); - self.write_str(chr.encode_utf8(&mut bytes_char))?; - self.flush()?; - } - Key::Enter => { - self.write_line("")?; - break; - } - _ => (), } + Ok(chars.iter().skip(prefix_len).collect::()) } - Ok(chars.iter().collect::()) + let ret = read_line_internal(self, initial); + + *self.inner.prompt.write().unwrap() = String::new(); + ret } /// Read a line of input securely. From 75ed0175a42a1db9fcd492c059d6938b47c20ac4 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 9 Jan 2024 13:44:22 +0100 Subject: [PATCH 6/9] Changelog entries --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11da5002..86018687 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,15 @@ ## Unreleased +### Enhancements + +* Added `wasm32-unknown-emscripten` target. (#179) +* `read_line_initial_text` now retains the initial prefix. (#190) + ### Bugfixes * Properly use configured output of `Term` to get terminal size (#186) +* Aligned `read_line` and `read_line_initial_text`'s behavior. (#181) ## 0.15.7 From b1e432a7119fe5d445be4eba165f9d4c3250f0e8 Mon Sep 17 00:00:00 2001 From: Alexander Fadeev Date: Tue, 9 Jan 2024 14:48:25 +0200 Subject: [PATCH 7/9] Return `Ctrl+C` instead of SIGINT (#189) --- src/kb.rs | 1 + src/term.rs | 10 +++++++++- src/unix_term.rs | 10 +++++++--- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/kb.rs b/src/kb.rs index 5258c135..2a0f61ed 100644 --- a/src/kb.rs +++ b/src/kb.rs @@ -26,4 +26,5 @@ pub enum Key { PageUp, PageDown, Char(char), + CtrlC, } diff --git a/src/term.rs b/src/term.rs index 4ccafb61..9ae2aba2 100644 --- a/src/term.rs +++ b/src/term.rs @@ -294,7 +294,15 @@ impl Term { if !self.is_tty { Ok(Key::Unknown) } else { - read_single_key() + read_single_key(false) + } + } + + pub fn read_key_raw(&self) -> io::Result { + if !self.is_tty { + Ok(Key::Unknown) + } else { + read_single_key(true) } } diff --git a/src/unix_term.rs b/src/unix_term.rs index 487d453f..77623e34 100644 --- a/src/unix_term.rs +++ b/src/unix_term.rs @@ -295,7 +295,7 @@ fn read_single_key_impl(fd: i32) -> Result { } } -pub fn read_single_key() -> io::Result { +pub fn read_single_key(ctrlc_key: bool) -> io::Result { let tty_f; let fd = unsafe { if libc::isatty(libc::STDIN_FILENO) == 1 { @@ -321,8 +321,12 @@ pub fn read_single_key() -> io::Result { // if the user hit ^C we want to signal SIGINT to outselves. if let Err(ref err) = rv { if err.kind() == io::ErrorKind::Interrupted { - unsafe { - libc::raise(libc::SIGINT); + if !ctrlc_key { + unsafe { + libc::raise(libc::SIGINT); + } + } else { + return Ok(Key::CtrlC); } } } From 995af8f63832b242cc4cf80630a7053796e42c89 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 9 Jan 2024 13:49:06 +0100 Subject: [PATCH 8/9] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86018687..c2a49f35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Added `wasm32-unknown-emscripten` target. (#179) * `read_line_initial_text` now retains the initial prefix. (#190) +* Reading raw input now traps Ctrl+C. (#189) ### Bugfixes From af9168d53c7b6e4e58b3ede27a36737e102ea353 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 9 Jan 2024 13:59:16 +0100 Subject: [PATCH 9/9] Reformat --- src/term.rs | 48 +++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/term.rs b/src/term.rs index 9ae2aba2..44e94055 100644 --- a/src/term.rs +++ b/src/term.rs @@ -255,9 +255,7 @@ impl Term { buffer.extend_from_slice(prompt.as_bytes()); Ok(()) } - None => { - self.write_through(format!("{}\n{}", s, prompt.as_str()).as_bytes()) - } + None => self.write_through(format!("{}\n{}", s, prompt.as_str()).as_bytes()), } } @@ -315,7 +313,7 @@ impl Term { } /// Read one line of input with initial text. - /// + /// /// This method blocks until no other thread is waiting for this read_line /// before reading a line from the terminal. /// This does not include the trailing newline. If the terminal is not @@ -331,31 +329,31 @@ impl Term { self.write_str(initial)?; fn read_line_internal(slf: &Term, initial: &str) -> io::Result { - let prefix_len = initial.len(); + let prefix_len = initial.len(); - let mut chars: Vec = initial.chars().collect(); + let mut chars: Vec = initial.chars().collect(); - loop { - match slf.read_key()? { - Key::Backspace => { - if prefix_len < chars.len() && chars.pop().is_some() { - slf.clear_chars(1)?; - } - slf.flush()?; - } - Key::Char(chr) => { - chars.push(chr); - let mut bytes_char = [0; 4]; - chr.encode_utf8(&mut bytes_char); - slf.write_str(chr.encode_utf8(&mut bytes_char))?; - slf.flush()?; - } - Key::Enter => { - slf.write_through(format!("\n{}", initial).as_bytes())?; - break; + loop { + match slf.read_key()? { + Key::Backspace => { + if prefix_len < chars.len() && chars.pop().is_some() { + slf.clear_chars(1)?; } - _ => (), + slf.flush()?; } + Key::Char(chr) => { + chars.push(chr); + let mut bytes_char = [0; 4]; + chr.encode_utf8(&mut bytes_char); + slf.write_str(chr.encode_utf8(&mut bytes_char))?; + slf.flush()?; + } + Key::Enter => { + slf.write_through(format!("\n{}", initial).as_bytes())?; + break; + } + _ => (), + } } Ok(chars.iter().skip(prefix_len).collect::()) }