diff --git a/examples/example.rs b/examples/example.rs index e02f665f2d2e16066badc014330d67d5bb05e5be..497bc3427515cecf6bfa2de9a182995eb485c524 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -5,6 +5,7 @@ use log::{Level, LevelFilter, Metadata, Record, SetLoggerError}; use std::borrow::Cow::{self, Borrowed, Owned}; use rustyline::completion::{Completer, FilenameCompleter, Pair}; +use rustyline::config::OutputStreamType; use rustyline::error::ReadlineError; use rustyline::highlight::Highlighter; use rustyline::hint::Hinter; @@ -56,6 +57,7 @@ fn main() { .history_ignore_space(true) .completion_type(CompletionType::List) .edit_mode(EditMode::Emacs) + .output_stream(OutputStreamType::Stdout) .build(); let h = MyHelper(FilenameCompleter::new()); let mut rl = Editor::with_config(config); diff --git a/src/config.rs b/src/config.rs index c82a2341348c54f5cb548898188af8cd465768af..e59a308e96e922c1f8e2fc619112eebaa7e038fc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -22,6 +22,8 @@ pub struct Config { auto_add_history: bool, /// if colors should be enabled. color_mode: ColorMode, + /// Whether to use stdout or stderr + output_stream: OutputStreamType, } impl Config { @@ -99,6 +101,14 @@ impl Config { pub(crate) fn set_color_mode(&mut self, color_mode: ColorMode) { self.color_mode = color_mode; } + + pub fn output_stream(&self) -> OutputStreamType { + self.output_stream + } + + pub(crate) fn set_output_stream(&mut self, stream: OutputStreamType) { + self.output_stream = stream; + } } impl Default for Config { @@ -113,6 +123,7 @@ impl Default for Config { edit_mode: EditMode::Emacs, auto_add_history: false, color_mode: ColorMode::Enabled, + output_stream: OutputStreamType::Stdout, } } } @@ -149,6 +160,13 @@ pub enum ColorMode { Disabled, } +/// Should the editor use stdout or stderr +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum OutputStreamType { + Stderr, + Stdout, +} + /// Configuration builder #[derive(Debug, Default)] pub struct Builder { @@ -231,6 +249,14 @@ impl Builder { self } + /// Whether to use stdout or stderr. + /// + /// Be default, use stdout + pub fn output_stream(mut self, stream: OutputStreamType) -> Builder { + self.set_output_stream(stream); + self + } + pub fn build(self) -> Config { self.p } @@ -303,4 +329,11 @@ pub trait Configurer { fn set_color_mode(&mut self, color_mode: ColorMode) { self.config_mut().set_color_mode(color_mode); } + + /// Whether to use stdout or stderr + /// + /// By default, use stdout + fn set_output_stream(&mut self, stream: OutputStreamType) { + self.config_mut().set_output_stream(stream); + } } diff --git a/src/lib.rs b/src/lib.rs index 6546ec1f2d3eeeaee62a5687eb277acc298127b4..90b3ac2c208b4ee13858220c7bd424e8c50d9dc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -640,7 +640,11 @@ fn readline_raw<H: Helper>( } } drop(guard); // try!(disable_raw_mode(original_mode)); - println!(); + editor + .term + .create_writer() + .write_and_flush("\n".as_bytes()) + .unwrap(); user_input } @@ -686,7 +690,7 @@ impl<H: Helper> Editor<H> { /// Create an editor with a specific configuration. pub fn with_config(config: Config) -> Editor<H> { - let term = Terminal::new(config.color_mode()); + let term = Terminal::new(config.color_mode(), config.output_stream()); Editor { term, history: History::with_config(config), @@ -878,6 +882,61 @@ impl<'a, H: Helper> Iterator for Iter<'a, H> { } } +enum StdStream { + Stdout(io::Stdout), + Stderr(io::Stderr), +} +impl StdStream { + fn from_stream_type(t: config::OutputStreamType) -> StdStream { + match t { + config::OutputStreamType::Stderr => StdStream::Stderr(io::stderr()), + config::OutputStreamType::Stdout => StdStream::Stdout(io::stdout()), + } + } +} +#[cfg(unix)] +impl std::os::unix::io::AsRawFd for StdStream { + fn as_raw_fd(&self) -> std::os::unix::io::RawFd { + match self { + StdStream::Stdout(e) => e.as_raw_fd(), + StdStream::Stderr(e) => e.as_raw_fd(), + } + } +} +impl io::Write for StdStream { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + match self { + StdStream::Stdout(ref mut e) => e.write(buf), + StdStream::Stderr(ref mut e) => e.write(buf), + } + } + + fn flush(&mut self) -> io::Result<()> { + match self { + StdStream::Stdout(ref mut e) => e.flush(), + StdStream::Stderr(ref mut e) => e.flush(), + } + } + + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + match self { + StdStream::Stdout(ref mut e) => e.write_all(buf), + StdStream::Stderr(ref mut e) => e.write_all(buf), + } + } + + fn write_fmt(&mut self, fmt: std::fmt::Arguments) -> io::Result<()> { + match self { + StdStream::Stdout(ref mut e) => e.write_fmt(fmt), + StdStream::Stderr(ref mut e) => e.write_fmt(fmt), + } + } + + fn by_ref(&mut self) -> &mut StdStream { + self + } +} + #[cfg(test)] #[macro_use] extern crate assert_matches; diff --git a/src/tty/mod.rs b/src/tty/mod.rs index e36ff80ec241581ba1ff1d239951beb4d693100a..1c6bf41fe0b7ea49bd635e86e29544cf1a3d041c 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -3,7 +3,7 @@ use std::io::{self, Write}; use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; -use config::{ColorMode, Config}; +use config::{ColorMode, Config, OutputStreamType}; use highlight::Highlighter; use keys::KeyPress; use line_buffer::LineBuffer; @@ -139,7 +139,7 @@ pub trait Term { type Writer: Renderer; // rl_outstream type Mode: RawMode; - fn new(color_mode: ColorMode) -> Self; + fn new(color_mode: ColorMode, stream: OutputStreamType) -> Self; /// Check if current terminal can provide a rich line-editing user /// interface. fn is_unsupported(&self) -> bool; diff --git a/src/tty/test.rs b/src/tty/test.rs index b1a47d90b63bfcfd904a9a678b724a1a76b55fea..8ea87a4096412c3baa076258183d0fcba636437a 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -4,7 +4,7 @@ use std::slice::Iter; use std::vec::IntoIter; use super::{truncate, Position, RawMode, RawReader, Renderer, Term}; -use config::{ColorMode, Config}; +use config::{ColorMode, Config, OutputStreamType}; use error::ReadlineError; use highlight::Highlighter; use keys::KeyPress; @@ -129,7 +129,7 @@ impl Term for DummyTerminal { type Reader = IntoIter<KeyPress>; type Writer = Sink; - fn new(color_mode: ColorMode) -> DummyTerminal { + fn new(color_mode: ColorMode, _stream: OutputStreamType) -> DummyTerminal { DummyTerminal { keys: Vec::new(), cursor: 0, diff --git a/src/tty/unix.rs b/src/tty/unix.rs index ed7980f571b69587bad4ab27d217a65fbeec9f93..f07a02d3d6d5017db5fa943397b69fb96c2f73ae 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,6 +1,7 @@ //! Unix specific definitions use std; -use std::io::{self, Read, Stdout, Write}; +use std::io::{self, Read, Write}; +use std::os::unix::io::AsRawFd; use std::sync; use std::sync::atomic; @@ -14,12 +15,13 @@ use unicode_segmentation::UnicodeSegmentation; use utf8parse::{Parser, Receiver}; use super::{truncate, width, Position, RawMode, RawReader, Renderer, Term}; -use config::{ColorMode, Config}; +use config::{ColorMode, Config, OutputStreamType}; use error; use highlight::Highlighter; use keys::{self, KeyPress}; use line_buffer::LineBuffer; use Result; +use StdStream; const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO; const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO; @@ -28,14 +30,14 @@ const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO; static UNSUPPORTED_TERM: [&'static str; 3] = ["dumb", "cons25", "emacs"]; //#[allow(clippy::identity_conversion)] -fn get_win_size() -> (usize, usize) { +fn get_win_size<T: AsRawFd + ?Sized>(fileno: &T) -> (usize, usize) { use std::mem::zeroed; unsafe { let mut size: libc::winsize = zeroed(); // https://github.com/rust-lang/libc/pull/704 // FIXME: ".into()" used as a temporary fix for a libc bug - match libc::ioctl(STDOUT_FILENO, libc::TIOCGWINSZ.into(), &mut size) { + match libc::ioctl(fileno.as_raw_fd(), libc::TIOCGWINSZ.into(), &mut size) { 0 => (size.ws_col as usize, size.ws_row as usize), // TODO getCursorPosition _ => (80, 24), } @@ -382,16 +384,17 @@ impl Receiver for Utf8 { /// Console output writer pub struct PosixRenderer { - out: Stdout, + out: StdStream, cols: usize, // Number of columns in terminal buffer: String, } impl PosixRenderer { - fn new() -> PosixRenderer { - let (cols, _) = get_win_size(); + fn new(stream_type: OutputStreamType) -> PosixRenderer { + let out = StdStream::from_stream_type(stream_type); + let (cols, _) = get_win_size(&out); PosixRenderer { - out: io::stdout(), + out: out, cols, buffer: String::with_capacity(1024), } @@ -557,7 +560,7 @@ impl Renderer for PosixRenderer { /// Try to update the number of columns in the current terminal, fn update_size(&mut self) { - let (cols, _) = get_win_size(); + let (cols, _) = get_win_size(&self.out); self.cols = cols; } @@ -568,7 +571,7 @@ impl Renderer for PosixRenderer { /// Try to get the number of rows in the current terminal, /// or assume 24 if it fails. fn get_rows(&self) -> usize { - let (_, rows) = get_win_size(); + let (_, rows) = get_win_size(&self.out); rows } } @@ -600,6 +603,7 @@ pub struct PosixTerminal { stdin_isatty: bool, stdout_isatty: bool, pub(crate) color_mode: ColorMode, + stream_type: OutputStreamType, } impl Term for PosixTerminal { @@ -607,12 +611,13 @@ impl Term for PosixTerminal { type Reader = PosixRawReader; type Writer = PosixRenderer; - fn new(color_mode: ColorMode) -> PosixTerminal { + fn new(color_mode: ColorMode, stream_type: OutputStreamType) -> PosixTerminal { let term = PosixTerminal { unsupported: is_unsupported_term(), stdin_isatty: is_a_tty(STDIN_FILENO), stdout_isatty: is_a_tty(STDOUT_FILENO), color_mode, + stream_type, }; if !term.unsupported && term.stdin_isatty && term.stdout_isatty { install_sigwinch_handler(); @@ -680,7 +685,7 @@ impl Term for PosixTerminal { } fn create_writer(&self) -> PosixRenderer { - PosixRenderer::new() + PosixRenderer::new(self.stream_type) } } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 65b699fcbabacf6bfc748198ff1b397b400d5190..b4bf70e647c46be5885be1d7ee670688c8edd562 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -1,5 +1,5 @@ //! Windows specific definitions -use std::io::{self, Stdout, Write}; +use std::io::{self, Write}; use std::mem; use std::sync::atomic; @@ -9,15 +9,18 @@ use winapi::um::winnt::{CHAR, HANDLE}; use winapi::um::{consoleapi, handleapi, processenv, winbase, wincon, winuser}; use super::{truncate, Position, RawMode, RawReader, Renderer, Term}; +use config::OutputStreamType; use config::{ColorMode, Config}; use error; use highlight::Highlighter; use keys::{self, KeyPress}; use line_buffer::LineBuffer; use Result; +use StdStream; const STDIN_FILENO: DWORD = winbase::STD_INPUT_HANDLE; const STDOUT_FILENO: DWORD = winbase::STD_OUTPUT_HANDLE; +const STDERR_FILENO: DWORD = winbase::STD_ERROR_HANDLE; fn get_std_handle(fd: DWORD) -> Result<HANDLE> { let handle = unsafe { processenv::GetStdHandle(fd) }; @@ -66,8 +69,8 @@ pub type Mode = ConsoleMode; pub struct ConsoleMode { original_stdin_mode: DWORD, stdin_handle: HANDLE, - original_stdout_mode: Option<DWORD>, - stdout_handle: HANDLE, + original_stdstream_mode: Option<DWORD>, + stdstream_handle: HANDLE, } impl RawMode for Mode { @@ -77,10 +80,10 @@ impl RawMode for Mode { self.stdin_handle, self.original_stdin_mode, )); - if let Some(original_stdout_mode) = self.original_stdout_mode { + if let Some(original_stdstream_mode) = self.original_stdstream_mode { check!(consoleapi::SetConsoleMode( - self.stdout_handle, - original_stdout_mode, + self.stdstream_handle, + original_stdstream_mode, )); } Ok(()) @@ -94,8 +97,12 @@ pub struct ConsoleRawReader { } impl ConsoleRawReader { - pub fn new() -> Result<ConsoleRawReader> { - let handle = try!(get_std_handle(STDIN_FILENO)); + pub fn new(stream: OutputStreamType) -> Result<ConsoleRawReader> { + let handle = try!(get_std_handle(if stream == OutputStreamType::Stdout { + STDIN_FILENO + } else { + STDERR_FILENO + })); Ok(ConsoleRawReader { handle, buf: [0; 2], @@ -243,18 +250,18 @@ impl RawReader for ConsoleRawReader { } pub struct ConsoleRenderer { - out: Stdout, + out: StdStream, handle: HANDLE, cols: usize, // Number of columns in terminal buffer: String, } impl ConsoleRenderer { - fn new(handle: HANDLE) -> ConsoleRenderer { + fn new(handle: HANDLE, stream_type: OutputStreamType) -> ConsoleRenderer { // Multi line editing is enabled by ENABLE_WRAP_AT_EOL_OUTPUT mode let (cols, _) = get_win_size(handle); ConsoleRenderer { - out: io::stdout(), + out: StdStream::from_stream_type(stream_type), handle, cols, buffer: String::with_capacity(1024), @@ -431,10 +438,11 @@ pub type Terminal = Console; pub struct Console { stdin_isatty: bool, stdin_handle: HANDLE, - stdout_isatty: bool, - stdout_handle: HANDLE, + stdstream_isatty: bool, + stdstream_handle: HANDLE, pub(crate) color_mode: ColorMode, ansi_colors_supported: bool, + stream_type: OutputStreamType, } impl Console {} @@ -444,7 +452,7 @@ impl Term for Console { type Reader = ConsoleRawReader; type Writer = ConsoleRenderer; - fn new(color_mode: ColorMode) -> Console { + fn new(color_mode: ColorMode, stream_type: OutputStreamType) -> Console { use std::ptr; let stdin_handle = get_std_handle(STDIN_FILENO); let stdin_isatty = match stdin_handle { @@ -454,8 +462,13 @@ impl Term for Console { } Err(_) => false, }; - let stdout_handle = get_std_handle(STDOUT_FILENO); - let stdout_isatty = match stdout_handle { + + let stdstream_handle = get_std_handle(if stream_type == OutputStreamType::Stdout { + STDOUT_FILENO + } else { + STDERR_FILENO + }); + let stdstream_isatty = match stdstream_handle { Ok(handle) => { // If this function doesn't fail then fd is a TTY get_console_mode(handle).is_ok() @@ -466,10 +479,11 @@ impl Term for Console { Console { stdin_isatty, stdin_handle: stdin_handle.unwrap_or(ptr::null_mut()), - stdout_isatty, - stdout_handle: stdout_handle.unwrap_or(ptr::null_mut()), + stdstream_isatty, + stdstream_handle: stdstream_handle.unwrap_or(ptr::null_mut()), color_mode, ansi_colors_supported: false, + stream_type, } } @@ -485,7 +499,7 @@ impl Term for Console { fn colors_enabled(&self) -> bool { // TODO ANSI Colors & Windows <10 match self.color_mode { - ColorMode::Enabled => self.stdout_isatty && self.ansi_colors_supported, + ColorMode::Enabled => self.stdstream_isatty && self.ansi_colors_supported, ColorMode::Forced => true, ColorMode::Disabled => false, } @@ -515,16 +529,16 @@ impl Term for Console { raw |= wincon::ENABLE_WINDOW_INPUT; check!(consoleapi::SetConsoleMode(self.stdin_handle, raw)); - let original_stdout_mode = if self.stdout_isatty { - let original_stdout_mode = try!(get_console_mode(self.stdout_handle)); + let original_stdstream_mode = if self.stdstream_isatty { + let original_stdstream_mode = try!(get_console_mode(self.stdstream_handle)); // To enable ANSI colors (Windows 10 only): // https://docs.microsoft.com/en-us/windows/console/setconsolemode - if original_stdout_mode & wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 { - let raw = original_stdout_mode | wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING; + if original_stdstream_mode & wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 { + let raw = original_stdstream_mode | wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING; self.ansi_colors_supported = - unsafe { consoleapi::SetConsoleMode(self.stdout_handle, raw) != 0 }; + unsafe { consoleapi::SetConsoleMode(self.stdstream_handle, raw) != 0 }; } - Some(original_stdout_mode) + Some(original_stdstream_mode) } else { None }; @@ -532,16 +546,16 @@ impl Term for Console { Ok(Mode { original_stdin_mode, stdin_handle: self.stdin_handle, - original_stdout_mode, - stdout_handle: self.stdout_handle, + original_stdstream_mode, + stdstream_handle: self.stdstream_handle, }) } fn create_reader(&self, _: &Config) -> Result<ConsoleRawReader> { - ConsoleRawReader::new() + ConsoleRawReader::new(self.stream_type) } fn create_writer(&self) -> ConsoleRenderer { - ConsoleRenderer::new(self.stdout_handle) + ConsoleRenderer::new(self.stdstream_handle, self.stream_type) } }