Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce signal-hook #608

Merged
merged 15 commits into from
Jun 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ regex = { version = "1.5.4", optional = true }
nix = { version = "0.24", default-features = false, features = ["fs", "ioctl", "poll", "signal", "term"] }
utf8parse = "0.2"
skim = { version = "0.9", optional = true }
signal-hook = { version = "0.3", optional = true, default-features = false }

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["consoleapi", "handleapi", "synchapi", "minwindef", "processenv", "std", "winbase", "wincon", "winuser"] }
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ Readline implementation in Rust that is based on [Antirez' Linenoise](https://gi
## Example
```rust
use rustyline::error::ReadlineError;
use rustyline::Editor;
use rustyline::{Editor, Result};

fn main() {
fn main() -> Result<()> {
// `()` can be used when no completer is required
let mut rl = Editor::<()>::new();
let mut rl = Editor::<()>::new()?;
if rl.load_history("history.txt").is_err() {
println!("No previous history.");
}
Expand All @@ -49,7 +49,7 @@ fn main() {
}
}
}
rl.save_history("history.txt").unwrap();
rl.save_history("history.txt")
}
```

Expand Down
2 changes: 1 addition & 1 deletion examples/custom_key_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ impl ConditionalEventHandler for TabEventHandler {
}

fn main() -> Result<()> {
let mut rl = Editor::<MyHelper>::new();
let mut rl = Editor::<MyHelper>::new()?;
rl.set_helper(Some(MyHelper(HistoryHinter {})));

let ceh = Box::new(CompleteHintHandler);
Expand Down
2 changes: 1 addition & 1 deletion examples/diy_hints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ fn main() -> Result<()> {
println!("This is a DIY hint hack of rustyline");
let h = DIYHinter { hints: diy_hints() };

let mut rl: Editor<DIYHinter> = Editor::new();
let mut rl: Editor<DIYHinter> = Editor::new()?;
rl.set_helper(Some(h));

loop {
Expand Down
2 changes: 1 addition & 1 deletion examples/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ fn main() -> rustyline::Result<()> {
colored_prompt: "".to_owned(),
validator: MatchingBracketValidator::new(),
};
let mut rl = Editor::with_config(config);
let mut rl = Editor::with_config(config)?;
rl.set_helper(Some(h));
rl.bind_sequence(KeyEvent::alt('n'), Cmd::HistorySearchForward);
rl.bind_sequence(KeyEvent::alt('p'), Cmd::HistorySearchBackward);
Expand Down
2 changes: 1 addition & 1 deletion examples/external_print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use rand::{thread_rng, Rng};
use rustyline::{Editor, ExternalPrinter, Result};

fn main() -> Result<()> {
let mut rl = Editor::<()>::new();
let mut rl = Editor::<()>::new()?;
let mut printer = rl.create_external_printer()?;
thread::spawn(move || {
let mut rng = thread_rng();
Expand Down
2 changes: 1 addition & 1 deletion examples/input_multiline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fn main() -> Result<()> {
let h = InputValidator {
brackets: MatchingBracketValidator::new(),
};
let mut rl = Editor::new();
let mut rl = Editor::new()?;
rl.set_helper(Some(h));

let input = rl.readline("> ")?;
Expand Down
2 changes: 1 addition & 1 deletion examples/input_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ impl Validator for InputValidator {

fn main() -> Result<()> {
let h = InputValidator {};
let mut rl = Editor::new();
let mut rl = Editor::new()?;
rl.set_helper(Some(h));

let input = rl.readline("> ")?;
Expand Down
3 changes: 2 additions & 1 deletion examples/minimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use rustyline::{Editor, Result};

/// Minimal REPL
fn main() -> Result<()> {
let mut rl = Editor::<()>::new();
env_logger::init();
let mut rl = Editor::<()>::new()?;
loop {
let line = rl.readline("> ")?; // read
println!("Line: {}", line); // eval / print
Expand Down
2 changes: 1 addition & 1 deletion examples/numeric_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ impl ConditionalEventHandler for FilteringEventHandler {
}

fn main() -> Result<()> {
let mut rl = Editor::<()>::new();
let mut rl = Editor::<()>::new()?;

rl.bind_sequence(
Event::Any,
Expand Down
2 changes: 1 addition & 1 deletion examples/read_password.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ impl Highlighter for MaskingHighlighter {
fn main() -> Result<()> {
println!("This is just a hack. Reading passwords securely requires more than that.");
let h = MaskingHighlighter { masking: false };
let mut rl = Editor::new();
let mut rl = Editor::new()?;
rl.set_helper(Some(h));

let username = rl.readline("Username:")?;
Expand Down
4 changes: 3 additions & 1 deletion src/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthChar;

use super::{Context, Helper, Result};
use crate::error::ReadlineError;
use crate::highlight::Highlighter;
use crate::hint::Hint;
use crate::history::SearchDirection;
Expand Down Expand Up @@ -83,7 +84,8 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
) -> Result<Cmd> {
loop {
let rc = input_state.next_cmd(rdr, self, single_esc_abort, ignore_external_print);
if rc.is_err() && self.out.sigwinch() {
if let Err(ReadlineError::WindowResized) = rc {
debug!(target: "rustyline", "SIGWINCH");
self.out.update_size();
self.prompt_size = self
.out
Expand Down
48 changes: 40 additions & 8 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Contains error type for handling I/O and Errno errors
#[cfg(windows)]
use std::char;
use std::error;
use std::error::Error;
use std::fmt;
use std::io;

Expand All @@ -21,10 +21,8 @@ pub enum ReadlineError {
/// Unix Error from syscall
#[cfg(unix)]
Errno(nix::Error),
/// Error generated on WINDOW_BUFFER_SIZE_EVENT to mimic unix SIGWINCH
/// signal
#[cfg(windows)]
WindowResize,
/// Error generated on WINDOW_BUFFER_SIZE_EVENT / SIGWINCH signal
WindowResized,
/// Like Utf8Error on unix
#[cfg(windows)]
Decode(char::DecodeUtf16Error),
Expand All @@ -41,8 +39,7 @@ impl fmt::Display for ReadlineError {
ReadlineError::Interrupted => write!(f, "Interrupted"),
#[cfg(unix)]
ReadlineError::Errno(ref err) => err.fmt(f),
#[cfg(windows)]
ReadlineError::WindowResize => write!(f, "WindowResize"),
ReadlineError::WindowResized => write!(f, "WindowResized"),
#[cfg(windows)]
ReadlineError::Decode(ref err) => err.fmt(f),
#[cfg(windows)]
Expand All @@ -51,10 +48,45 @@ impl fmt::Display for ReadlineError {
}
}

impl error::Error for ReadlineError {}
impl Error for ReadlineError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match *self {
ReadlineError::Io(ref err) => Some(err),
ReadlineError::Eof => None,
ReadlineError::Interrupted => None,
#[cfg(unix)]
ReadlineError::Errno(ref err) => Some(err),
ReadlineError::WindowResized => None,
#[cfg(windows)]
ReadlineError::Decode(ref err) => Some(err),
#[cfg(windows)]
ReadlineError::SystemError(_) => None,
}
}
}

#[cfg(unix)]
#[derive(Debug)]
pub(crate) struct WindowResizedError;
#[cfg(unix)]
impl fmt::Display for WindowResizedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "WindowResized")
}
}
#[cfg(unix)]
impl Error for WindowResizedError {}

impl From<io::Error> for ReadlineError {
fn from(err: io::Error) -> Self {
#[cfg(unix)]
if err.kind() == io::ErrorKind::Interrupted {
if let Some(e) = err.get_ref() {
if e.downcast_ref::<WindowResizedError>().is_some() {
return ReadlineError::WindowResized;
}
}
}
ReadlineError::Io(err)
}
}
Expand Down
27 changes: 14 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
//! Usage
//!
//! ```
//! let mut rl = rustyline::Editor::<()>::new();
//! let mut rl = rustyline::Editor::<()>::new()?;
//! let readline = rl.readline(">> ");
//! match readline {
//! Ok(line) => println!("Line: {:?}", line),
//! Err(_) => println!("No input"),
//! }
//! # Ok::<(), rustyline::error::ReadlineError>(())
//! ```
#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
Expand Down Expand Up @@ -46,13 +47,14 @@ use std::sync::{Arc, Mutex};
use log::debug;
use unicode_width::UnicodeWidthStr;

use crate::tty::{RawMode, Renderer, Term, Terminal};
use crate::tty::{RawMode, RawReader, Renderer, Term, Terminal};

#[cfg(feature = "custom-bindings")]
pub use crate::binding::{ConditionalEventHandler, Event, EventContext, EventHandler};
use crate::completion::{longest_common_prefix, Candidate, Completer};
pub use crate::config::{Behavior, ColorMode, CompletionType, Config, EditMode, HistoryDuplicates};
use crate::edit::State;
use crate::error::ReadlineError;
use crate::highlight::Highlighter;
use crate::hint::Hinter;
use crate::history::{History, SearchDirection};
Expand Down Expand Up @@ -572,6 +574,7 @@ impl<'h> Context<'h> {
}

/// Line editor
#[must_use]
pub struct Editor<H: Helper> {
term: Terminal,
history: History,
Expand All @@ -584,29 +587,27 @@ pub struct Editor<H: Helper> {
#[allow(clippy::new_without_default)]
impl<H: Helper> Editor<H> {
/// Create an editor with the default configuration
#[must_use]
pub fn new() -> Self {
pub fn new() -> Result<Self> {
Self::with_config(Config::default())
}

/// Create an editor with a specific configuration.
#[must_use]
pub fn with_config(config: Config) -> Self {
pub fn with_config(config: Config) -> Result<Self> {
let term = Terminal::new(
config.color_mode(),
config.behavior(),
config.tab_stop(),
config.bell_style(),
config.enable_bracketed_paste(),
);
Self {
)?;
Ok(Self {
term,
history: History::with_config(config),
helper: None,
kill_ring: Arc::new(Mutex::new(KillRing::new(60))),
config,
custom_bindings: Bindings::new(),
}
})
}

/// This method will read a line from STDIN and will display a `prompt`.
Expand Down Expand Up @@ -687,7 +688,7 @@ impl<H: Helper> Editor<H> {
let mut rdr = self.term.create_reader(&self.config, term_key_map);
if self.term.is_output_tty() && self.config.check_cursor_position() {
if let Err(e) = s.move_cursor_at_leftmost(&mut rdr) {
if s.out.sigwinch() {
if let ReadlineError::WindowResized = e {
s.out.update_size();
} else {
return Err(e);
Expand Down Expand Up @@ -730,22 +731,21 @@ impl<H: Helper> Editor<H> {
original_mode.disable_raw_mode()?;
tty::suspend()?;
let _ = self.term.enable_raw_mode()?; // TODO original_mode may have changed
s.out.update_size(); // window may have been resized
s.refresh_line()?;
continue;
}

#[cfg(unix)]
if cmd == Cmd::QuotedInsert {
// Quoted insert
use crate::tty::RawReader;
let c = rdr.next_char()?;
s.edit_insert(c, 1)?;
continue;
}

#[cfg(windows)]
if cmd == Cmd::PasteFromClipboard {
use crate::tty::RawReader;
let clipboard = rdr.read_pasted_text()?;
s.edit_yank(&input_state, &clipboard[..], Anchor::Before, 1)?;
}
Expand Down Expand Up @@ -849,7 +849,7 @@ impl<H: Helper> Editor<H> {

/// Returns an iterator over edited lines
/// ```
/// let mut rl = rustyline::Editor::<()>::new();
/// let mut rl = rustyline::Editor::<()>::new()?;
/// for readline in rl.iter("> ") {
/// match readline {
/// Ok(line) => {
Expand All @@ -861,6 +861,7 @@ impl<H: Helper> Editor<H> {
/// }
/// }
/// }
/// # Ok::<(), rustyline::error::ReadlineError>(())
/// ```
pub fn iter<'a>(&'a mut self, prompt: &'a str) -> impl Iterator<Item = Result<String>> + 'a {
Iter {
Expand Down
2 changes: 1 addition & 1 deletion src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ mod vi_insert;

fn init_editor(mode: EditMode, keys: &[KeyEvent]) -> Editor<()> {
let config = Config::builder().edit_mode(mode).build();
let mut editor = Editor::<()>::with_config(config);
let mut editor = Editor::<()>::with_config(config).unwrap();
editor.term.keys.extend(keys.iter().cloned());
editor
}
Expand Down
10 changes: 3 additions & 7 deletions src/tty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@ pub trait Renderer {
/// Clear rows used by prompt and edited line
fn clear_rows(&mut self, layout: &Layout) -> Result<()>;

/// Check if a SIGWINCH signal has been received
fn sigwinch(&self) -> bool;
/// Update the number of columns/rows in the current terminal.
fn update_size(&mut self);
/// Get the number of columns in the current terminal.
Expand Down Expand Up @@ -157,10 +155,6 @@ impl<'a, R: Renderer + ?Sized> Renderer for &'a mut R {
(**self).clear_rows(layout)
}

fn sigwinch(&self) -> bool {
(**self).sigwinch()
}

fn update_size(&mut self) {
(**self).update_size();
}
Expand Down Expand Up @@ -233,7 +227,9 @@ pub trait Term {
tab_stop: usize,
bell_style: BellStyle,
enable_bracketed_paste: bool,
) -> Self;
) -> Result<Self>
where
Self: Sized;
/// Check if current terminal can provide a rich line-editing user
/// interface.
fn is_unsupported(&self) -> bool;
Expand Down
Loading