Skip to content

Commit

Permalink
Fix behavior of read_line_initial_text (#190)
Browse files Browse the repository at this point in the history
  • Loading branch information
terrarier2111 committed Jan 9, 2024
1 parent ed4ca61 commit e750152
Showing 1 changed file with 58 additions and 25 deletions.
83 changes: 58 additions & 25 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 @@ -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,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())
}
}
}

Expand Down Expand Up @@ -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<String> {
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<char> = 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<String> {
let prefix_len = initial.len();

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()?;
}
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::<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

0 comments on commit e750152

Please sign in to comment.