From 2bfeffac8c257f44be0ac2cfe7ee70d3da9497ef Mon Sep 17 00:00:00 2001 From: Robert Grimm Date: Mon, 23 Dec 2024 05:50:04 -0500 Subject: [PATCH] make debugging output for Config contingent on verbose mode; provide reusable interface to terminal mode labels; fix terminal mode calculation on Windows --- crates/prettytty/examples/echo.rs | 10 +- crates/prettytty/src/conn.rs | 9 +- crates/prettytty/src/sys/unix.rs | 191 +++++++++++++++++----------- crates/prettytty/src/sys/windows.rs | 131 ++++++++++++------- 4 files changed, 215 insertions(+), 126 deletions(-) diff --git a/crates/prettytty/examples/echo.rs b/crates/prettytty/examples/echo.rs index c8f4f35..449b0d4 100644 --- a/crates/prettytty/examples/echo.rs +++ b/crates/prettytty/examples/echo.rs @@ -8,6 +8,7 @@ use std::io::{Read, Write}; use prettytty::cmd::{Format, RequestColor, ResetStyle, SetForeground8, SetForegroundDefault}; use prettytty::err::report; +use prettytty::opt::Options; use prettytty::util::WriteNicely; use prettytty::Connection; @@ -16,7 +17,9 @@ const GRAY: SetForeground8 = SetForeground8(244); #[allow(unused_assignments)] fn run() -> std::io::Result<()> { // Access the terminal - let tty = Connection::open()?; + println!("\n"); + let options = Options::verbose_default(); + let tty = Connection::with_options(options)?; let mut input = tty.input(); let mut output = tty.output(); @@ -26,7 +29,7 @@ fn run() -> std::io::Result<()> { // Peek into terminal access write!( output, - "{}press ‹t› to query theme color, ‹q› to quit{}\r\n\r\n", + "\r\n\r\n{}press ‹t› to query theme color, ‹q› to quit{}\r\n\r\n", Format::Bold, Format::Bold.undo() )?; @@ -89,6 +92,7 @@ fn run() -> std::io::Result<()> { // Handle user input. if buffer.contains(&b'q') { output.exec(ResetStyle)?; + output.println("\n\n")?; break; } else if buffer.contains(&b't') { let mut entry = color_requests.next(); @@ -105,7 +109,7 @@ fn run() -> std::io::Result<()> { drop(input); drop(output); drop(tty); - println!("\n\nbye bye!"); + println!("bye bye!"); Ok(()) } diff --git a/crates/prettytty/src/conn.rs b/crates/prettytty/src/conn.rs index f983d99..6f6ffb4 100644 --- a/crates/prettytty/src/conn.rs +++ b/crates/prettytty/src/conn.rs @@ -45,11 +45,16 @@ impl Connection { .map_err(|e| Error::new(ErrorKind::ConnectionRefused, e))?; let config = Config::read(connection.input())?; - println!("{:#?}", &config); + let verbose = options.verbose(); + if verbose { + println!("terminal::config {:?}", &config); + } let config = config.apply(&options).map_or_else( || Ok::, Error>(None), |reconfig| { - println!("{:#?}", &reconfig); + if verbose { + println!("terminal::reconfig {:?}", &reconfig); + } reconfig.write(connection.output())?; Ok(Some(config)) }, diff --git a/crates/prettytty/src/sys/unix.rs b/crates/prettytty/src/sys/unix.rs index a1198fb..0a17d1e 100644 --- a/crates/prettytty/src/sys/unix.rs +++ b/crates/prettytty/src/sys/unix.rs @@ -105,6 +105,41 @@ impl RawConnection { // ---------------------------------------------------------------------------------------------------------- +#[derive(Clone, Copy, Debug)] +pub(crate) enum ModeGroup { + Input, + Output, + Control, + Local, +} + +impl ModeGroup { + pub fn all() -> impl std::iter::Iterator { + use self::ModeGroup::*; + + std::iter::successors( + Some(Input), + |n| Some(match n { + Input => Output, + Output => Control, + Control => Local, + Local => return None, + }) + ) + } + + pub fn name(&self) -> &'static str { + use self::ModeGroup::*; + + match self { + Input => "input", + Output => "output", + Control => "control", + Local => "local" + } + } +} + /// A terminal configuration. pub(crate) struct Config { state: libc::termios, @@ -145,94 +180,100 @@ impl Config { .into_result()?; Ok(()) } -} -impl std::fmt::Debug for Config { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // Different Unix-like operating systems use differently sized modes, - // which is just fine with a macro. + /// Get labels for active modes in given group. + pub fn labels(&self, group: ModeGroup) -> Vec<&'static str> { + let mut labels = Vec::new(); + macro_rules! maybe_add { - ($labels:expr, $field:expr, $mask:expr, $label:expr) => { + ($field:expr, $mask:expr, $label:expr) => { if $field & $mask != 0 { - $labels.push($label); + labels.push($label); } }; } - // Input Modes - let mut input_labels = Vec::new(); - for (label, mask) in [ - ("BRKINT", libc::BRKINT), - ("ICRNL", libc::ICRNL), - ("IGNBRK", libc::IGNBRK), - ("IGNCR", libc::IGNCR), - ("IGNPAR", libc::IGNPAR), - ("INLCR", libc::INLCR), - ("INPCK", libc::INPCK), - ("ISTRIP", libc::ISTRIP), - ("IXANY", libc::IXANY), - ("IXOFF", libc::IXOFF), - ("IXON", libc::IXON), - ("PARMRK", libc::PARMRK), - ] { - maybe_add!(input_labels, self.state.c_iflag, mask, label); - } + match group { + ModeGroup::Input => { + for (label, mask) in [ + ("BRKINT", libc::BRKINT), + ("ICRNL", libc::ICRNL), + ("IGNBRK", libc::IGNBRK), + ("IGNCR", libc::IGNCR), + ("IGNPAR", libc::IGNPAR), + ("INLCR", libc::INLCR), + ("INPCK", libc::INPCK), + ("ISTRIP", libc::ISTRIP), + ("IXANY", libc::IXANY), + ("IXOFF", libc::IXOFF), + ("IXON", libc::IXON), + ("PARMRK", libc::PARMRK), + ] { + maybe_add!(self.state.c_iflag, mask, label); + } + } + ModeGroup::Output => { + for (label, mask) in [ + ("OPOST", libc::OPOST), + ("OCRNL", libc::OCRNL), + ("ONOCR", libc::ONOCR), + ("ONLRET", libc::ONLRET), + ("OFILL", libc::OFILL), + ("OFDEL", libc::OFDEL), + // Missing: NLDLY, CRDLY, TABDLY, BSDLY, VTDLY, FFDLY + ] { + maybe_add!(self.state.c_oflag, mask, label); + } - // Output Modes - let mut output_labels = Vec::new(); - for (label, mask) in [ - ("OPOST", libc::OPOST), - ("OCRNL", libc::OCRNL), - ("ONOCR", libc::ONOCR), - ("ONLRET", libc::ONLRET), - ("OFILL", libc::OFILL), - ("OFDEL", libc::OFDEL), - // Missing: NLDLY, CRDLY, TABDLY, BSDLY, VTDLY, FFDLY - ] { - maybe_add!(output_labels, self.state.c_oflag, mask, label); + } + ModeGroup::Control => { + maybe_add!(self.state.c_cflag, libc::CLOCAL, "CLOCAL"); + maybe_add!(self.state.c_cflag, libc::CREAD, "CREAD"); + match self.state.c_cflag & libc::CSIZE { + libc::CS5 => labels.push("CS5"), + libc::CS6 => labels.push("CS6"), + libc::CS7 => labels.push("CS7"), + libc::CS8 => labels.push("CS8"), + _ => (), + } + for (label, mask) in [ + ("CSTOPB", libc::CSTOPB), + ("HUPCL", libc::HUPCL), + ("PARENB", libc::PARENB), + ("PARODD", libc::PARODD), + ] { + maybe_add!(self.state.c_cflag, mask, label); + } + } + ModeGroup::Local => { + for (label, mask) in [ + ("ECHO", libc::ECHO), + ("ECHOE", libc::ECHOE), + ("ECHOK", libc::ECHOK), + ("ECHONL", libc::ECHONL), + ("ICANON", libc::ICANON), + ("IEXTEN", libc::IEXTEN), + ("ISIG", libc::ISIG), + ("NOFLSH", libc::NOFLSH), + ("TOSTOP", libc::TOSTOP), + ] { + maybe_add!(self.state.c_lflag, mask, label); + } + } } - // Control Modes - let mut control_labels = Vec::new(); - maybe_add!(control_labels, self.state.c_cflag, libc::CLOCAL, "CLOCAL"); - maybe_add!(control_labels, self.state.c_cflag, libc::CREAD, "CREAD"); - match self.state.c_cflag & libc::CSIZE { - libc::CS5 => control_labels.push("CS5"), - libc::CS6 => control_labels.push("CS6"), - libc::CS7 => control_labels.push("CS7"), - libc::CS8 => control_labels.push("CS8"), - _ => (), - } - for (label, mask) in [ - ("CSTOPB", libc::CSTOPB), - ("HUPCL", libc::HUPCL), - ("PARENB", libc::PARENB), - ("PARODD", libc::PARODD), - ] { - maybe_add!(control_labels, self.state.c_cflag, mask, label); - } + labels + } +} - // Local Modes - let mut local_labels = Vec::new(); - for (label, mask) in [ - ("ECHO", libc::ECHO), - ("ECHOE", libc::ECHOE), - ("ECHOK", libc::ECHOK), - ("ECHONL", libc::ECHONL), - ("ICANON", libc::ICANON), - ("IEXTEN", libc::IEXTEN), - ("ISIG", libc::ISIG), - ("NOFLSH", libc::NOFLSH), - ("TOSTOP", libc::TOSTOP), - ] { - maybe_add!(local_labels, self.state.c_lflag, mask, label); +impl std::fmt::Debug for Config { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut debugger = f.debug_struct("Config"); + for group in ModeGroup::all() { + debugger.field(group.name(), &IdentList::new(self.labels(group))); } - f.debug_struct("Config") - .field("input_mode", &IdentList::new(input_labels)) - .field("output_mode", &IdentList::new(output_labels)) - .field("control_mode", &IdentList::new(control_labels)) - .field("local_mode", &IdentList::new(local_labels)) + debugger .field("vmin", &self.state.c_cc[libc::VMIN]) .field("vtime", &self.state.c_cc[libc::VTIME]) .finish() diff --git a/crates/prettytty/src/sys/windows.rs b/crates/prettytty/src/sys/windows.rs index 659db10..fe76036 100644 --- a/crates/prettytty/src/sys/windows.rs +++ b/crates/prettytty/src/sys/windows.rs @@ -65,25 +65,50 @@ impl RawConnection { // ---------------------------------------------------------------------------------------------------------- +#[derive(Clone, Copy, Debug)] +pub(crate) enum ModeGroup { + Input, + Output, +} + +impl ModeGroup { + pub fn all() -> impl std::iter::Iterator { + std::iter::successors( + Some(Self::Input), + |n| Some(match n { + Self::Input => Self::Output, + Self::Output => return None, + }) + ) + } + + pub fn name(&self) -> &'static str { + match self { + Self::Input => "input", + Self::Output => "output", + } + } +} + /// A terminal configuration. pub(crate) struct Config { - input_mode: ConsoleMode, + input_modes: ConsoleMode, input_encoding: u32, - output_mode: ConsoleMode, + output_modes: ConsoleMode, output_encoding: u32, } impl Config { pub fn read(input: RawInput) -> Result { - let input_mode = Self::read_mode(&input)?; + let input_modes = Self::read_mode(&input)?; let input_encoding = unsafe { Console::GetConsoleCP() }.into_result()?; - let output_mode = Self::read_mode(&input)?; + let output_modes = Self::read_mode(&input)?; let output_encoding = unsafe { Console::GetConsoleOutputCP() }.into_result()?; Ok(Self { - input_mode, + input_modes, input_encoding, - output_mode, + output_modes, output_encoding, }) } @@ -99,7 +124,7 @@ impl Config { /// For Unix, charred and cooked mode are the same; they make no changes. /// For Windows, charred mode makes no changes, but cooked mode switches /// to the UTF-8 encoding, `ENABLE_VIRTUAL_TERMINAL_INPUT`, - /// `ENABLE_PROCESSED_OUTPUT`, amd `ENABLE_VIRTUAL_TERMINAL_PROCESSING`. + /// `ENABLE_PROCESSED_OUTPUT`, and `ENABLE_VIRTUAL_TERMINAL_PROCESSING`. /// These options ensure that the terminal actually processed ANSI /// escape sequences. pub fn apply(&self, options: &Options) -> Option { @@ -108,32 +133,33 @@ impl Config { return None; } - let mut input_mode = self.input_mode & Console::ENABLE_VIRTUAL_TERMINAL_INPUT; + let mut input_modes = self.input_modes | Console::ENABLE_VIRTUAL_TERMINAL_INPUT; if options.mode() != Mode::Cooked { - input_mode = input_mode & !Console::ENABLE_ECHO_INPUT & !Console::ENABLE_LINE_INPUT; + input_modes = input_modes & !Console::ENABLE_ECHO_INPUT & !Console::ENABLE_LINE_INPUT; } if options.mode() == Mode::Raw { - input_mode = input_mode & !Console::ENABLE_PROCESSED_INPUT; + input_modes = input_modes & !Console::ENABLE_PROCESSED_INPUT; } let input_encoding = Globalization::CP_UTF8; - let output_mode = self.output_mode - & Console::ENABLE_PROCESSED_OUTPUT - & Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING; + let output_modes = self.output_modes + | Console::ENABLE_PROCESSED_OUTPUT + | Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING + | Console::DISABLE_NEWLINE_AUTO_RETURN; let output_encoding = Globalization::CP_UTF8; Some(Self { - input_mode, + input_modes, input_encoding, - output_mode, + output_modes, output_encoding, }) } pub fn write(&self, output: RawOutput) -> Result<()> { - let result1 = Self::write_mode(&output, self.input_mode); + let result1 = Self::write_mode(&output, self.input_modes); let result2 = unsafe { Console::SetConsoleCP(self.input_encoding) }.into_result(); - let result3 = Self::write_mode(&output, self.output_mode); + let result3 = Self::write_mode(&output, self.output_modes); let result4 = unsafe { Console::SetConsoleOutputCP(self.output_encoding) }.into_result(); result1.and(result2).and(result3).and(result4)?; @@ -144,43 +170,56 @@ impl Config { unsafe { Console::SetConsoleMode(output.handle(), mode) }.into_result()?; Ok(()) } -} -impl std::fmt::Debug for Config { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut input_labels = Vec::new(); - for (label, mask) in [ - ("ENABLE_ECHO_INPUT", Console::ENABLE_ECHO_INPUT), - ("ENABLE_INSERT_MODE", Console::ENABLE_INSERT_MODE), - ("ENABLE_LINE_INPUT", Console::ENABLE_LINE_INPUT), - ("ENABLE_MOUSE_INPUT", Console::ENABLE_MOUSE_INPUT), - ("ENABLE_PROCESSED_INPUT", Console::ENABLE_PROCESSED_INPUT), - ("ENABLE_QUICK_EDIT_MODE", Console::ENABLE_QUICK_EDIT_MODE), - ("ENABLE_WINDOW_INPUT", Console::ENABLE_WINDOW_INPUT), - ("ENABLE_VIRTUAL_TERMINAL_INPUT", Console::ENABLE_VIRTUAL_TERMINAL_INPUT), - ] { - if self.input_mode & mask != 0 { - input_labels.push(label); - } + pub fn labels(&self, group: ModeGroup) -> Vec<&'static str> { + let mut labels = Vec::new(); + + macro_rules! maybe_add { + ($field:expr, $mask:expr, $label:expr) => { + if $field & $mask != 0 { + labels.push($label); + } + }; } - let mut output_labels = Vec::new(); - for (label, mask) in [ - ("ENABLE_PROCESSED_OUTPUT", Console::ENABLE_PROCESSED_OUTPUT), - ("ENABLE_WRAP_AT_EOL_OUTPUT", Console::ENABLE_WRAP_AT_EOL_OUTPUT), - ("ENABLE_VIRTUAL_TERMINAL_PROCESSING", Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING), - ("DISABLE_NEWLINE_AUTO_RETURN", Console::DISABLE_NEWLINE_AUTO_RETURN), - ("ENABLE_LVB_GRID_WORLDWIDE", Console::ENABLE_LVB_GRID_WORLDWIDE), - ] { - if self.output_mode & mask != 0 { - output_labels.push(label); + match group { + ModeGroup::Input => { + for (label, mask) in [ + ("ENABLE_ECHO_INPUT", Console::ENABLE_ECHO_INPUT), + ("ENABLE_INSERT_MODE", Console::ENABLE_INSERT_MODE), + ("ENABLE_LINE_INPUT", Console::ENABLE_LINE_INPUT), + ("ENABLE_MOUSE_INPUT", Console::ENABLE_MOUSE_INPUT), + ("ENABLE_PROCESSED_INPUT", Console::ENABLE_PROCESSED_INPUT), + ("ENABLE_QUICK_EDIT_MODE", Console::ENABLE_QUICK_EDIT_MODE), + ("ENABLE_WINDOW_INPUT", Console::ENABLE_WINDOW_INPUT), + ("ENABLE_VIRTUAL_TERMINAL_INPUT", Console::ENABLE_VIRTUAL_TERMINAL_INPUT), + ] { + maybe_add!(self.input_modes, mask, label); + } + } + ModeGroup::Output => { + for (label, mask) in [ + ("ENABLE_PROCESSED_OUTPUT", Console::ENABLE_PROCESSED_OUTPUT), + ("ENABLE_WRAP_AT_EOL_OUTPUT", Console::ENABLE_WRAP_AT_EOL_OUTPUT), + ("ENABLE_VIRTUAL_TERMINAL_PROCESSING", Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING), + ("DISABLE_NEWLINE_AUTO_RETURN", Console::DISABLE_NEWLINE_AUTO_RETURN), + ("ENABLE_LVB_GRID_WORLDWIDE", Console::ENABLE_LVB_GRID_WORLDWIDE), + ] { + maybe_add!(self.output_modes, mask, label); + } } } + labels + } +} + +impl std::fmt::Debug for Config { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Config") - .field("input_mode", &IdentList::new(input_labels)) + .field("input_modes", &IdentList::new(self.labels(ModeGroup::Input))) .field("input_encoding", &self.input_encoding) - .field("output_mode", &IdentList::new(output_labels)) + .field("output_modes", &IdentList::new(self.labels(ModeGroup::Output))) .field("output_encoding", &self.output_encoding) .finish() }