Skip to content

Commit a0c6c1e

Browse files
authored
Merge pull request console-rs#58 from rjwalters/input_using_read_key
2 parents e17efb4 + f500857 commit a0c6c1e

File tree

2 files changed

+100
-2
lines changed

2 files changed

+100
-2
lines changed

examples/continue.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ fn main() {
1414
return;
1515
}
1616

17-
let input: String = Input::new().with_prompt("Your name").interact().unwrap();
17+
let input: String = Input::new().with_prompt("Your name").interact_text().unwrap();
1818
println!("Hello {}!", input);
1919
}

src/prompts/input.rs

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{
99
validate::Validator,
1010
};
1111

12-
use console::Term;
12+
use console::{Key, Term};
1313

1414
/// Renders an input prompt.
1515
///
@@ -142,6 +142,104 @@ where
142142
self
143143
}
144144

145+
/// Enables the user to enter a printable ascii sequence and returns the result.
146+
///
147+
/// The dialog is rendered on stderr.
148+
pub fn interact_text(&self) -> io::Result<T> {
149+
self.interact_text_on(&Term::stderr())
150+
}
151+
152+
/// Like `interact_text` but allows a specific terminal to be set.
153+
pub fn interact_text_on(&self, term: &Term) -> io::Result<T> {
154+
let mut render = TermThemeRenderer::new(term, self.theme);
155+
156+
loop {
157+
let default_string = self.default.as_ref().map(|x| x.to_string());
158+
159+
render.input_prompt(
160+
&self.prompt,
161+
if self.show_default {
162+
default_string.as_ref().map(|x| x.as_str())
163+
} else {
164+
None
165+
},
166+
)?;
167+
term.flush()?;
168+
169+
// Read input by keystroke so that we can suppress ascii control characters
170+
if !term.is_term() {
171+
return Ok("".to_owned().parse::<T>().unwrap());
172+
}
173+
174+
let mut chars: Vec<char> = Vec::new();
175+
if let Some(initial) = self.initial_text.as_ref() {
176+
term.write_str(initial)?;
177+
chars = initial.chars().collect();
178+
}
179+
loop {
180+
match term.read_key()? {
181+
Key::Backspace => {
182+
if chars.pop().is_some() {
183+
term.clear_chars(1)?;
184+
}
185+
term.flush()?;
186+
}
187+
Key::Char(chr) => {
188+
if !chr.is_ascii_control() {
189+
chars.push(chr);
190+
let mut bytes_char = [0; 4];
191+
chr.encode_utf8(&mut bytes_char);
192+
term.write_str(chr.encode_utf8(&mut bytes_char))?;
193+
term.flush()?;
194+
}
195+
}
196+
Key::Enter => break,
197+
Key::Unknown => {
198+
return Err(io::Error::new(
199+
io::ErrorKind::NotConnected,
200+
"Not a terminal",
201+
))
202+
}
203+
_ => (),
204+
}
205+
}
206+
let input = chars.iter().collect::<String>();
207+
208+
term.clear_line()?;
209+
render.clear()?;
210+
211+
if chars.is_empty() {
212+
if let Some(ref default) = self.default {
213+
render.input_prompt_selection(&self.prompt, &default.to_string())?;
214+
term.flush()?;
215+
return Ok(default.clone());
216+
} else if !self.permit_empty {
217+
continue;
218+
}
219+
}
220+
221+
match input.parse::<T>() {
222+
Ok(value) => {
223+
if let Some(ref validator) = self.validator {
224+
if let Some(err) = validator(&input) {
225+
render.error(&err)?;
226+
continue;
227+
}
228+
}
229+
230+
render.input_prompt_selection(&self.prompt, &input)?;
231+
term.flush()?;
232+
233+
return Ok(value);
234+
}
235+
Err(err) => {
236+
render.error(&err.to_string())?;
237+
continue;
238+
}
239+
}
240+
}
241+
}
242+
145243
/// Enables user interaction and returns the result.
146244
///
147245
/// If the user confirms the result is `true`, `false` otherwise.

0 commit comments

Comments
 (0)