From 1e86d80e262a095d93fe4b1259c3b4fceb54b2cd Mon Sep 17 00:00:00 2001 From: gwenn <gtreguier@gmail.com> Date: Mon, 23 Jul 2018 21:29:33 +0200 Subject: [PATCH] One step toward colors support --- src/config.rs | 26 +++++++++++++++++++ src/lib.rs | 2 +- src/tty/mod.rs | 8 +++--- src/tty/test.rs | 10 +++++--- src/tty/unix.rs | 21 +++++++++++++--- src/tty/windows.rs | 63 +++++++++++++++++++++++++++++++++------------- 6 files changed, 102 insertions(+), 28 deletions(-) diff --git a/src/config.rs b/src/config.rs index d1dbc42b..f5199a80 100644 --- a/src/config.rs +++ b/src/config.rs @@ -20,6 +20,8 @@ pub struct Config { /// If true, each nonblank line returned by `readline` will be /// automatically added to the history. auto_add_history: bool, + /// if colors should be enabled. + color_mode: ColorMode, } impl Config { @@ -70,6 +72,13 @@ impl Config { pub fn auto_add_history(&self) -> bool { self.auto_add_history } + + /// Tell if colors should be enabled. + /// + /// By default, they are except if stdout is not a tty. + pub fn color_mode(&self) -> ColorMode { + self.color_mode + } } impl Default for Config { @@ -83,6 +92,7 @@ impl Default for Config { keyseq_timeout: -1, edit_mode: EditMode::Emacs, auto_add_history: false, + color_mode: ColorMode::Enabled, } } } @@ -111,6 +121,14 @@ pub enum EditMode { Vi, } +/// Colorization mode +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ColorMode { + Enabled, + Forced, + Disabled, +} + /// Configuration builder #[derive(Debug, Default)] pub struct Builder { @@ -193,6 +211,14 @@ impl Builder { self } + /// Forces colorization on or off. + /// + /// By default, colorization is on except if stdout is not a tty. + pub fn colors_enabled(mut self, color_mode: ColorMode) -> Builder { + self.p.color_mode = color_mode; + self + } + pub fn build(self) -> Config { self.p } diff --git a/src/lib.rs b/src/lib.rs index a5e72bbc..41f224d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -656,7 +656,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(); + let term = Terminal::new(config.color_mode()); Editor { term, history: History::with_config(config), diff --git a/src/tty/mod.rs b/src/tty/mod.rs index e6c42dc5..1047c421 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::Config; +use config::{ColorMode, Config}; use consts::KeyPress; use line_buffer::LineBuffer; use Result; @@ -120,14 +120,16 @@ pub trait Term { type Writer: Renderer; // rl_outstream type Mode: RawMode; - fn new() -> Self; + fn new(color_mode: ColorMode) -> Self; /// Check if current terminal can provide a rich line-editing user /// interface. fn is_unsupported(&self) -> bool; /// check if stdin is connected to a terminal. fn is_stdin_tty(&self) -> bool; + /// Check if output supports colors. + fn colors_enabled(&self) -> bool; /// Enable RAW mode for the terminal. - fn enable_raw_mode(&self) -> Result<Self::Mode>; + fn enable_raw_mode(&mut self) -> Result<Self::Mode>; /// Create a RAW reader fn create_reader(&self, config: &Config) -> Result<Self::Reader>; /// Create a writer diff --git a/src/tty/test.rs b/src/tty/test.rs index 29a1e797..b8e5df59 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::Config; +use config::{ColorMode, Config}; use consts::KeyPress; use error::ReadlineError; use line_buffer::LineBuffer; @@ -121,7 +121,7 @@ impl Term for DummyTerminal { type Writer = Sink; type Mode = Mode; - fn new() -> DummyTerminal { + fn new(_color_mode: ColorMode) -> DummyTerminal { DummyTerminal { keys: Vec::new(), cursor: 0, @@ -138,9 +138,13 @@ impl Term for DummyTerminal { true } + fn colors_enabled(&self) -> bool { + false + } + // Interactive loop: - fn enable_raw_mode(&self) -> Result<Mode> { + fn enable_raw_mode(&mut self) -> Result<Mode> { Ok(()) } diff --git a/src/tty/unix.rs b/src/tty/unix.rs index b1d5b788..cd35e302 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -13,7 +13,7 @@ use nix::sys::termios::SetArg; use unicode_segmentation::UnicodeSegmentation; use super::{truncate, width, Position, RawMode, RawReader, Renderer, Term}; -use config::Config; +use config::{ColorMode, Config}; use consts::{self, KeyPress}; use error; use line_buffer::LineBuffer; @@ -533,6 +533,8 @@ pub type Terminal = PosixTerminal; pub struct PosixTerminal { unsupported: bool, stdin_isatty: bool, + stdout_isatty: bool, + color_mode: ColorMode, } impl Term for PosixTerminal { @@ -540,12 +542,14 @@ impl Term for PosixTerminal { type Writer = PosixRenderer; type Mode = Mode; - fn new() -> PosixTerminal { + fn new(color_mode: ColorMode) -> PosixTerminal { let term = PosixTerminal { unsupported: is_unsupported_term(), stdin_isatty: is_a_tty(STDIN_FILENO), + stdout_isatty: is_a_tty(STDOUT_FILENO), + color_mode, }; - if !term.unsupported && term.stdin_isatty && is_a_tty(STDOUT_FILENO) { + if !term.unsupported && term.stdin_isatty && term.stdout_isatty { install_sigwinch_handler(); } term @@ -564,9 +568,18 @@ impl Term for PosixTerminal { self.stdin_isatty } + /// Check if output supports colors. + fn colors_enabled(&self) -> bool { + match self.color_mode { + ColorMode::Enabled => self.stdout_isatty, + ColorMode::Forced => true, + ColorMode::Disabled => false, + } + } + // Interactive loop: - fn enable_raw_mode(&self) -> Result<Mode> { + fn enable_raw_mode(&mut self) -> Result<Mode> { use nix::errno::Errno::ENOTTY; use nix::sys::termios::{ControlFlags, InputFlags, LocalFlags, SpecialCharacterIndices}; if !self.stdin_isatty { diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 6d9fa363..26027670 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -9,7 +9,7 @@ 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::Config; +use config::{ColorMode, Config}; use consts::{self, KeyPress}; use error; use line_buffer::LineBuffer; @@ -65,7 +65,7 @@ pub type Mode = ConsoleMode; pub struct ConsoleMode { original_stdin_mode: DWORD, stdin_handle: HANDLE, - original_stdout_mode: DWORD, + original_stdout_mode: Option<DWORD>, stdout_handle: HANDLE, } @@ -76,10 +76,12 @@ impl RawMode for Mode { self.stdin_handle, self.original_stdin_mode, )); - check!(consoleapi::SetConsoleMode( - self.stdout_handle, - self.original_stdout_mode, - )); + if let Some(original_stdout_mode) = self.original_stdout_mode { + check!(consoleapi::SetConsoleMode( + self.stdout_handle, + original_stdout_mode, + )); + } Ok(()) } } @@ -396,7 +398,10 @@ pub type Terminal = Console; pub struct Console { stdin_isatty: bool, stdin_handle: HANDLE, + stdout_isatty: bool, stdout_handle: HANDLE, + color_mode: ColorMode, + ansi_colors_supported: bool, } impl Console {} @@ -406,7 +411,7 @@ impl Term for Console { type Writer = ConsoleRenderer; type Mode = Mode; - fn new() -> Console { + fn new(color_mode: ColorMode) -> Console { use std::ptr; let stdin_handle = get_std_handle(STDIN_FILENO); let stdin_isatty = match stdin_handle { @@ -416,12 +421,22 @@ impl Term for Console { } Err(_) => false, }; + let stdout_handle = get_std_handle(STDOUT_FILENO); + let stdout_isatty = match stdout_handle { + Ok(handle) => { + // If this function doesn't fail then fd is a TTY + get_console_mode(handle).is_ok() + } + Err(_) => false, + }; - let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut()); Console { stdin_isatty, stdin_handle: stdin_handle.unwrap_or(ptr::null_mut()), - stdout_handle, + stdout_isatty, + stdout_handle: stdout_handle.unwrap_or(ptr::null_mut()), + color_mode, + ansi_colors_supported: false, } } @@ -434,12 +449,20 @@ impl Term for Console { self.stdin_isatty } + fn colors_enabled(&self) -> bool { + match self.color_mode { + ColorMode::Enabled => self.stdout_isatty, + ColorMode::Forced => true, + ColorMode::Disabled => false, + } + } + // pub fn install_sigwinch_handler(&mut self) { // See ReadConsoleInputW && WINDOW_BUFFER_SIZE_EVENT // } /// Enable RAW mode for the terminal. - fn enable_raw_mode(&self) -> Result<Mode> { + fn enable_raw_mode(&mut self) -> Result<Mode> { if !self.stdin_isatty { try!(Err(io::Error::new( io::ErrorKind::Other, @@ -459,13 +482,19 @@ impl Term for Console { let raw = raw | wincon::ENABLE_WINDOW_INPUT; check!(consoleapi::SetConsoleMode(self.stdin_handle, raw)); - let original_stdout_mode = try!(get_console_mode(self.stdout_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; - check!(consoleapi::SetConsoleMode(self.stdout_handle, raw)); - } + let original_stdout_mode = if self.stdout_isatty { + let original_stdout_mode = try!(get_console_mode(self.stdout_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; + self.ansi_colors_supported = + consoleapi::SetConsoleMode(self.stdout_handle, raw) != 0; + } + Some(original_stdout_mode) + } else { + None + }; Ok(Mode { original_stdin_mode, -- GitLab