Skip to content

Commit

Permalink
Add support for Ctrl-C handling
Browse files Browse the repository at this point in the history
  • Loading branch information
samuela authored and fadeevab committed Jan 17, 2024
1 parent 8778406 commit 464b865
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 43 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ indicatif = "0.17.5"
once_cell = "1.18.0"
textwrap = "0.16.0"
zeroize = {version = "1.6.0", features = ["derive"]}

[dev-dependencies]
ctrlc = "3.4.2"
8 changes: 8 additions & 0 deletions examples/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ use std::{thread, time::Duration};
use console::style;

fn main() -> std::io::Result<()> {
// Set a no-op Ctrl-C handler so that Ctrl-C results in a
// `term.read_key()` error instead of terminating the process. You can skip
// this step if you have your own Ctrl-C handler already set up.
//
// We cannot (easily) handle this at the library level due to
// https://github.com/Detegr/rust-ctrlc/issues/106#issuecomment-1887793468.
ctrlc::set_handler(move || {}).expect("Error setting Ctrl-C handler");

cliclack::clear_screen()?;

cliclack::intro(style(" create-app ").on_cyan().black())?;
Expand Down
8 changes: 8 additions & 0 deletions examples/basic_dynamic_items.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
use console::style;

fn main() -> std::io::Result<()> {
// Set a no-op Ctrl-C handler so that Ctrl-C results in a
// `term.read_key()` error instead of terminating the process. You can skip
// this step if you have your own Ctrl-C handler already set up.
//
// We cannot (easily) handle this at the library level due to
// https://github.com/Detegr/rust-ctrlc/issues/106#issuecomment-1887793468.
ctrlc::set_handler(move || {}).expect("Error setting Ctrl-C handler");

cliclack::clear_screen()?;

cliclack::intro(style(" create-app ").on_cyan().black())?;
Expand Down
8 changes: 8 additions & 0 deletions examples/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ impl Theme for MagentaTheme {
}

fn main() -> std::io::Result<()> {
// Set a no-op Ctrl-C handler so that Ctrl-C results in a
// `term.read_key()` error instead of terminating the process. You can skip
// this step if you have your own Ctrl-C handler already set up.
//
// We cannot (easily) handle this at the library level due to
// https://github.com/Detegr/rust-ctrlc/issues/106#issuecomment-1887793468.
ctrlc::set_handler(move || {}).expect("Error setting Ctrl-C handler");

set_theme(MagentaTheme);

intro(style(" theme ").on_magenta().black())?;
Expand Down
8 changes: 4 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
//! ## Cancellation
//!
//! `Esc` cancels the prompt sequence with a nice message.
//! `Ctrl+C` interrupts the session abruptly, it's handled inside of the
//! `Term` crate and cannot be easily caught and rendered fancy.
//! `Ctrl+C` will be handled gracefully (same as `Esc`) if you set up a Ctrl+C
//! handler, eg. with the `ctrlc` crate.
//!
//! # Components
//!
Expand Down Expand Up @@ -226,8 +226,8 @@ pub use select::Select;
pub use spinner::Spinner;
pub use validate::Validate;

fn term_write(line: String) -> io::Result<()> {
Term::stderr().write_str(&line)
fn term_write(line: impl Display) -> io::Result<()> {
Term::stderr().write_str(line.to_string().as_str())
}

/// Clears the terminal.
Expand Down
66 changes: 27 additions & 39 deletions src/prompt/interaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,50 +86,38 @@ pub trait PromptInteraction<T> {
prev_frame = frame;
}

if let State::Submit(result) = state {
return Ok(result);
match state {
State::Submit(result) => return Ok(result),
State::Cancel => return Err(io::ErrorKind::Interrupted.into()),
_ => {}
}

if let State::Cancel = state {
return Err(io::ErrorKind::Interrupted.into());
}

let key = term.read_key()?;

if let Some(cursor) = self.input() {
match key {
Key::Char(chr) if !chr.is_ascii_control() => {
cursor.insert(chr);
}
Key::Backspace => {
cursor.delete_left();
}
Key::Del => {
cursor.delete_right();
}
Key::ArrowLeft => {
cursor.move_left();
}
Key::ArrowRight => {
cursor.move_right();
match term.read_key() {
Ok(Key::Escape) => state = State::Cancel,

Ok(key) => {
if let Some(cursor) = self.input() {
match key {
Key::Char(chr) if !chr.is_ascii_control() => cursor.insert(chr),
Key::Backspace => cursor.delete_left(),
Key::Del => cursor.delete_right(),
Key::ArrowLeft => cursor.move_left(),
Key::ArrowRight => cursor.move_right(),
Key::Home => cursor.move_home(),
Key::End => cursor.move_end(),
_ => {}
}
}
Key::Home => {
cursor.move_home();
}
Key::End => {
cursor.move_end();
}
_ => {}
}
}

match key {
Key::Escape => {
state = State::Cancel;
}
other => {
state = self.on(&Event::Key(other));
state = self.on(&Event::Key(key));
}

// Handle Ctrl-C as a cancel event.
Err(e) if e.kind() == io::ErrorKind::Interrupted => state = State::Cancel,

// Don't handle other errors, just break the loop and propagate
// them.
Err(e) => return Err(e),
}
}
}
Expand Down

0 comments on commit 464b865

Please sign in to comment.