Allow customization of the output stream
diff --git a/examples/example.rs b/examples/example.rs
index e02f665..497bc34 100644
--- a/examples/example.rs
+++ b/examples/example.rs
@@ -5,6 +5,7 @@
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 @@
.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 c82a234..e59a308 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -22,6 +22,8 @@
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 @@
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 @@
edit_mode: EditMode::Emacs,
auto_add_history: false,
color_mode: ColorMode::Enabled,
+ output_stream: OutputStreamType::Stdout,
}
}
}
@@ -149,6 +160,13 @@
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 @@
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 @@
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 6546ec1..90b3ac2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -640,7 +640,11 @@
}
}
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 @@
/// 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 @@
}
}
+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 e36ff80..1c6bf41 100644
--- a/src/tty/mod.rs
+++ b/src/tty/mod.rs
@@ -3,7 +3,7 @@
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 @@
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 b1a47d9..8ea87a4 100644
--- a/src/tty/test.rs
+++ b/src/tty/test.rs
@@ -4,7 +4,7 @@
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 @@
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 ed7980f..f07a02d 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 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 @@
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 @@
/// 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 @@
/// 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 @@
/// 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 @@
stdin_isatty: bool,
stdout_isatty: bool,
pub(crate) color_mode: ColorMode,
+ stream_type: OutputStreamType,
}
impl Term for PosixTerminal {
@@ -607,12 +611,13 @@
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 @@
}
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 65b699f..b4bf70e 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::{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 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 @@
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 @@
}
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 @@
}
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 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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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)
}
}