Skip to content

Commit

Permalink
Merge branch 'master' into deps/windows-sys
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Jan 9, 2024
2 parents e64e3ed + af9168d commit 33461bd
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 39 deletions.
10 changes: 9 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## Unreleased

### Enhancements

* 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

* 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

### Enhancements
Expand Down
16 changes: 16 additions & 0 deletions examples/keyboard.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
1 change: 1 addition & 0 deletions src/kb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ pub enum Key {
PageUp,
PageDown,
Char(char),
CtrlC,
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
98 changes: 65 additions & 33 deletions src/term.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -41,6 +41,8 @@ pub enum TermTarget {
pub struct TermInner {
target: TermTarget,
buffer: Option<Mutex<Vec<u8>>>,
prompt: RwLock<String>,
prompt_guard: Mutex<()>,
}

/// The family of the terminal.
Expand Down Expand Up @@ -108,7 +110,7 @@ impl<'a> TermFeatures<'a> {
{
TermFamily::WindowsConsole
}
#[cfg(unix)]
#[cfg(all(unix, not(target_arch = "wasm32")))]
{
TermFamily::UnixTerm
}
Expand Down Expand Up @@ -149,6 +151,8 @@ impl Term {
Term::with_inner(TermInner {
target: TermTarget::Stdout,
buffer: None,
prompt: RwLock::new(String::new()),
prompt_guard: Mutex::new(()),
})
}

Expand All @@ -158,6 +162,8 @@ impl Term {
Term::with_inner(TermInner {
target: TermTarget::Stderr,
buffer: None,
prompt: RwLock::new(String::new()),
prompt_guard: Mutex::new(()),
})
}

Expand All @@ -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(()),
})
}

Expand All @@ -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(()),
})
}

Expand Down Expand Up @@ -201,6 +211,8 @@ impl Term {
style,
}),
buffer: None,
prompt: RwLock::new(String::new()),
prompt_guard: Mutex::new(()),
})
}

Expand Down Expand Up @@ -231,14 +243,19 @@ 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()),
}
}

Expand Down Expand Up @@ -275,7 +292,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<Key> {
if !self.is_tty {
Ok(Key::Unknown)
} else {
read_single_key(true)
}
}

Expand All @@ -284,51 +309,58 @@ 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<String> {
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.
///
/// 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<String> {
if !self.is_tty {
return Ok("".into());
}
*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();

self.write_str(initial)?;

let mut chars: Vec<char> = initial.chars().collect();
fn read_line_internal(slf: &Term, initial: &str) -> io::Result<String> {
let prefix_len = initial.len();

loop {
match self.read_key()? {
Key::Backspace => {
if chars.pop().is_some() {
self.clear_chars(1)?;
let mut chars: Vec<char> = 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()?;
}
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;
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::<String>())
}
Ok(chars.iter().collect::<String>())
let ret = read_line_internal(self, initial);

*self.inner.prompt.write().unwrap() = String::new();
ret
}

/// Read a line of input securely.
Expand Down Expand Up @@ -624,7 +656,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::*;
Expand Down
12 changes: 8 additions & 4 deletions src/unix_term.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub fn c_result<F: FnOnce() -> 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;
}

Expand Down Expand Up @@ -295,7 +295,7 @@ fn read_single_key_impl(fd: i32) -> Result<Key, io::Error> {
}
}

pub fn read_single_key() -> io::Result<Key> {
pub fn read_single_key(ctrlc_key: bool) -> io::Result<Key> {
let tty_f;
let fd = unsafe {
if libc::isatty(libc::STDIN_FILENO) == 1 {
Expand All @@ -321,8 +321,12 @@ pub fn read_single_key() -> io::Result<Key> {
// 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);
}
}
}
Expand Down

0 comments on commit 33461bd

Please sign in to comment.