diff --git a/src/uu/stty/src/flags.rs b/src/uu/stty/src/flags.rs index c10e7c04b39..c8717bfd313 100644 --- a/src/uu/stty/src/flags.rs +++ b/src/uu/stty/src/flags.rs @@ -7,9 +7,10 @@ // spell-checker:ignore ignbrk brkint ignpar parmrk inpck istrip inlcr igncr icrnl ixoff ixon iuclc ixany imaxbel iutf // spell-checker:ignore opost olcuc ocrnl onlcr onocr onlret ofdel nldly crdly tabdly bsdly vtdly ffdly // spell-checker:ignore isig icanon iexten echoe crterase echok echonl noflsh xcase tostop echoprt prterase echoctl ctlecho echoke crtkill flusho extproc -// spell-checker:ignore lnext rprnt susp swtch vdiscard veof veol verase vintr vkill vlnext vquit vreprint vstart vstop vsusp vswtc vwerase werase +// spell-checker:ignore lnext rprnt susp dsusp swtch vdiscard veof veol verase vintr vkill vlnext vquit vreprint vstart vstop vsusp vswtc vwerase werase VDSUSP // spell-checker:ignore sigquit sigtstp // spell-checker:ignore cbreak decctlq evenp litout oddp +// spell-checker:ignore cdtrdsr CDTRDSR ofill OFILL dsusp VDSUSP VFLUSHO VSTATUS noncanonical VMIN deciseconds noncanonical VTIME use crate::Flag; @@ -65,10 +66,14 @@ pub const CONTROL_FLAGS: &[Flag] = &[ Flag::new_grouped("cs7", C::CS7, C::CSIZE), Flag::new_grouped("cs8", C::CS8, C::CSIZE).sane(), Flag::new("hupcl", C::HUPCL), + // Not supported by nix and libc. + // Flag::new("hup", C::HUP).hidden(), Flag::new("cstopb", C::CSTOPB), Flag::new("cread", C::CREAD).sane(), Flag::new("clocal", C::CLOCAL), Flag::new("crtscts", C::CRTSCTS), + // Not supported by nix and libc. + // Flag::new("cdtrdsr", C::CDTRDSR), ]; pub const INPUT_FLAGS: &[Flag] = &[ @@ -81,10 +86,18 @@ pub const INPUT_FLAGS: &[Flag] = &[ Flag::new("inlcr", I::INLCR), Flag::new("igncr", I::IGNCR), Flag::new("icrnl", I::ICRNL).sane(), - Flag::new("ixoff", I::IXOFF), - Flag::new("tandem", I::IXOFF), Flag::new("ixon", I::IXON), - // not supported by nix + Flag::new("ixoff", I::IXOFF), + Flag::new("tandem", I::IXOFF).hidden(), + // not supported by nix and libc: + // - https://github.com/rust-lang/libc/pull/4846 + // - https://github.com/nix-rust/nix/pull/2702 + // #[cfg(any( + // target_os = "aix", + // target_os = "android", + // target_os = "haiku", + // target_os = "linux", + // ))] // Flag::new("iuclc", I::IUCLC), Flag::new("ixany", I::IXANY), Flag::new("imaxbel", I::IMAXBEL).sane(), @@ -112,6 +125,17 @@ pub const OUTPUT_FLAGS: &[Flag] = &[ target_os = "linux", target_os = "macos" ))] + #[cfg(any( + target_os = "android", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos" + ))] + // Not supported by nix. + // See: https://github.com/nix-rust/nix/pull/2701 + // FIXME: Flag::new("ofill", O::OFILL), + Flag::new("ofill", O::from_bits_retain(nix::libc::OFILL)), Flag::new("ofdel", O::OFDEL), #[cfg(any( target_os = "android", @@ -253,7 +277,15 @@ pub const LOCAL_FLAGS: &[Flag] = &[ Flag::new("echok", L::ECHOK).sane(), Flag::new("echonl", L::ECHONL), Flag::new("noflsh", L::NOFLSH), - // Not supported by nix + // Not supported by nix and libc: + // - https://github.com/rust-lang/libc/pull/4847 + // - https://github.com/nix-rust/nix/pull/2703 + // #[cfg(any( + // target_os = "aix", + // target_os = "android", + // target_os = "haiku", + // target_os = "linux", + // ))] // Flag::new("xcase", L::XCASE), Flag::new("tostop", L::TOSTOP), Flag::new("echoprt", L::ECHOPRT), @@ -357,6 +389,18 @@ pub const CONTROL_CHARS: &[(&str, S)] = &[ ("stop", S::VSTOP), // Sends a suspend signal (SIGTSTP). ("susp", S::VSUSP), + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "aix", + target_os = "solaris" + ))] + // Sends a delayed suspend signal (SIGTSTP). + ("dsusp", S::VDSUSP), // Reprints the current line. ("rprnt", S::VREPRINT), // Deletes the last word typed. @@ -365,6 +409,25 @@ pub const CONTROL_CHARS: &[(&str, S)] = &[ ("lnext", S::VLNEXT), // Discards the current line. ("discard", S::VDISCARD), + // deprecated compat option. + // Not supported by nix and libc. + // ("flush", S::VFLUSHO), + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + ))] + // Status character + ("status", S::VSTATUS), + // Minimum number of characters for noncanonical read. + // We handle this manually. + // ("min", S::VMIN), + // Timeout in deciseconds for noncanonical read. + // We handle this manually. + // ("time", S::VTIME), ]; /// This constant lists all possible combination settings, using a bool to represent if the setting is negatable @@ -385,4 +448,5 @@ pub const COMBINATION_SETTINGS: &[(&str, bool)] = &[ ("pass8", true), ("raw", true), ("sane", false), + ("tabs", true), ]; diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index fdeee252df3..3977d6af68d 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -492,8 +492,67 @@ fn print_special_setting(setting: &PrintSetting, fd: i32) -> nix::Result<()> { Ok(()) } -fn print_terminal_size(termios: &Termios, opts: &Options) -> nix::Result<()> { +struct WrappedPrinter { + width: usize, + current: usize, + first_in_line: bool, +} + +impl WrappedPrinter { + fn new(term_size: &Option) -> Self { + let columns = match term_size { + Some(term_size) => term_size.columns, + None => { + const DEFAULT_TERM_WIDTH: u16 = 80; + + std::env::var_os("COLUMNS") + .and_then(|s| s.to_str()?.parse().ok()) + .filter(|&c| c > 0) + .unwrap_or(DEFAULT_TERM_WIDTH) + } + }; + + Self { + width: columns.max(1) as usize, + current: 0, + first_in_line: true, + } + } + + fn print(&mut self, token: &str) { + let token_len = self.prefix().chars().count() + token.chars().count(); + if self.current > 0 && self.current + token_len > self.width { + println!(); + self.current = 0; + self.first_in_line = true; + } + + print!("{}{}", self.prefix(), token); + self.current += token_len; + self.first_in_line = false; + } + + fn prefix(&self) -> &str { + if self.first_in_line { "" } else { " " } + } + + fn flush(&mut self) { + if self.current > 0 { + println!(); + self.current = 0; + self.first_in_line = false; + } + } +} + +fn print_terminal_size( + termios: &Termios, + opts: &Options, + window_size: &Option, + term_size: &Option, +) -> nix::Result<()> { let speed = cfgetospeed(termios); + let mut printer = WrappedPrinter::new(window_size); // BSDs use a u32 for the baud rate, so we can simply print it. #[cfg(any( @@ -518,30 +577,24 @@ fn print_terminal_size(termios: &Termios, opts: &Options) -> nix::Result<()> { )))] for (text, baud_rate) in BAUD_RATES { if *baud_rate == speed { - print!("{} ", translate!("stty-output-speed", "speed" => (*text))); + printer.print(&translate!("stty-output-speed", "speed" => (*text))); break; } } if opts.all { - let mut size = TermSize::default(); - unsafe { tiocgwinsz(opts.file.as_raw_fd(), &raw mut size)? }; - print!( - "{} ", - translate!("stty-output-rows-columns", "rows" => size.rows, "columns" => size.columns) + let term_size = term_size.as_ref().expect("terminal size should be set"); + printer.print( + &translate!("stty-output-rows-columns", "rows" => term_size.rows, "columns" => term_size.columns), ); } - #[cfg(any(target_os = "linux", target_os = "redox"))] + #[cfg(any(target_os = "linux", target_os = "android", target_os = "haiku"))] { - // For some reason the normal nix Termios struct does not expose the line, - // so we get the underlying libc::termios struct to get that information. - let libc_termios: nix::libc::termios = termios.clone().into(); - let line = libc_termios.c_line; - print!("{}", translate!("stty-output-line", "line" => line)); + let line = termios.line_discipline; + printer.print(&translate!("stty-output-line", "line" => line)); } - - println!(); + printer.flush(); Ok(()) } @@ -647,39 +700,41 @@ fn control_char_to_string(cc: nix::libc::cc_t) -> nix::Result { Ok(format!("{meta_prefix}{ctrl_prefix}{character}")) } -fn print_control_chars(termios: &Termios, opts: &Options) -> nix::Result<()> { +fn print_control_chars( + termios: &Termios, + opts: &Options, + term_size: &Option, +) -> nix::Result<()> { if !opts.all { // Print only control chars that differ from sane defaults - let mut printed = false; + let mut printer = WrappedPrinter::new(term_size); for (text, cc_index) in CONTROL_CHARS { let current_val = termios.control_chars[*cc_index as usize]; let sane_val = get_sane_control_char(*cc_index); if current_val != sane_val { - print!("{text} = {}; ", control_char_to_string(current_val)?); - printed = true; + printer.print(&format!( + "{text} = {};", + control_char_to_string(current_val)? + )); } } - - if printed { - println!(); - } + printer.flush(); return Ok(()); } + let mut printer = WrappedPrinter::new(term_size); for (text, cc_index) in CONTROL_CHARS { - print!( - "{text} = {}; ", + printer.print(&format!( + "{text} = {};", control_char_to_string(termios.control_chars[*cc_index as usize])? - ); + )); } - println!( - "{}", - translate!("stty-output-min-time", + printer.print(&translate!("stty-output-min-time", "min" => termios.control_chars[S::VMIN as usize], "time" => termios.control_chars[S::VTIME as usize] - ) - ); + )); + printer.flush(); Ok(()) } @@ -701,18 +756,45 @@ fn print_settings(termios: &Termios, opts: &Options) -> nix::Result<()> { if opts.save { print_in_save_format(termios); } else { - print_terminal_size(termios, opts)?; - print_control_chars(termios, opts)?; - print_flags(termios, opts, CONTROL_FLAGS); - print_flags(termios, opts, INPUT_FLAGS); - print_flags(termios, opts, OUTPUT_FLAGS); - print_flags(termios, opts, LOCAL_FLAGS); + let device_fd = opts.file.as_raw_fd(); + let term_size = { + let mut term_size = TermSize::default(); + let term_size = + unsafe { tiocgwinsz(device_fd, &raw mut term_size) }.map(|_| term_size); + if opts.all { + Some(term_size?) + } else { + term_size.ok() + } + }; + + let stdout_fd = stdout().as_raw_fd(); + let window_size = if device_fd != stdout_fd { + let mut term_size = TermSize::default(); + &unsafe { tiocgwinsz(stdout_fd, &raw mut term_size) } + .map(|_| term_size) + .ok() + } else { + &term_size + }; + + print_terminal_size(termios, opts, &window_size, &term_size)?; + print_control_chars(termios, opts, &window_size)?; + print_flags(termios, opts, CONTROL_FLAGS, &window_size); + print_flags(termios, opts, INPUT_FLAGS, &window_size); + print_flags(termios, opts, OUTPUT_FLAGS, &window_size); + print_flags(termios, opts, LOCAL_FLAGS, &window_size); } Ok(()) } -fn print_flags(termios: &Termios, opts: &Options, flags: &[Flag]) { - let mut printed = false; +fn print_flags( + termios: &Termios, + opts: &Options, + flags: &[Flag], + term_size: &Option, +) { + let mut printer = WrappedPrinter::new(term_size); for &Flag { name, flag, @@ -727,20 +809,17 @@ fn print_flags(termios: &Termios, opts: &Options, flags: &[Flag< let val = flag.is_in(termios, group); if group.is_some() { if val && (!sane || opts.all) { - print!("{name} "); - printed = true; + printer.print(name); } } else if opts.all || val != sane { if !val { - print!("-"); + printer.print(&format!("-{name}")); + continue; } - print!("{name} "); - printed = true; + printer.print(name); } } - if printed { - println!(); - } + printer.flush(); } /// Apply a single setting @@ -806,7 +885,7 @@ fn apply_special_setting( SpecialSetting::Cols(n) => size.columns = *n, SpecialSetting::Line(_n) => { // nix only defines Termios's `line_discipline` field on these platforms - #[cfg(any(target_os = "linux", target_os = "android"))] + #[cfg(any(target_os = "linux", target_os = "android", target_os = "haiku"))] { _termios.line_discipline = *_n; }