From 2bd7114cea89a471dcf6f4c279048db4be5d7731 Mon Sep 17 00:00:00 2001 From: Peter Holloway Date: Tue, 23 Jan 2024 10:21:45 +0000 Subject: fix: Only escape control characters when writing to terminal (#1593) When piping the output of `atuin history list` to a file, it makes more sense for the literal commands to be written rather than the escaped ones that would be printed to the terminal. --- atuin/src/command/client/history.rs | 73 ++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/atuin/src/command/client/history.rs b/atuin/src/command/client/history.rs index 7a12b699..18fd5794 100644 --- a/atuin/src/command/client/history.rs +++ b/atuin/src/command/client/history.rs @@ -1,6 +1,6 @@ use std::{ fmt::{self, Display}, - io::{self, Write}, + io::{self, IsTerminal, Write}, time::Duration, }; @@ -150,50 +150,46 @@ pub fn print_list( let flush_each_line = print0; for h in iterator { - match write!( - w, - "{}{}", - parsed_fmt.with_args(&FmtHistory(h)), - entry_terminator - ) { - Ok(()) => {} - // ignore broken pipe (issue #626) - Err(e) if e.kind() == io::ErrorKind::BrokenPipe => { - return; - } - Err(err) => { - eprintln!("ERROR: History output failed with the following error: {err}"); - std::process::exit(1); - } - } + let fh = FmtHistory(h, CmdFormat::for_output(&w)); + let args = parsed_fmt.with_args(&fh); + check_for_write_errors(write!(w, "{args}{entry_terminator}")); if flush_each_line { - match w.flush() { - Ok(()) => {} - // ignore broken pipe (issue #626) - Err(e) if e.kind() == io::ErrorKind::BrokenPipe => {} - Err(err) => { - eprintln!("ERROR: History output failed with the following error: {err}"); - std::process::exit(1); - } - } + check_for_write_errors(w.flush()); } } if !flush_each_line { - match w.flush() { - Ok(()) => {} - // ignore broken pipe (issue #626) - Err(e) if e.kind() == io::ErrorKind::BrokenPipe => {} - Err(err) => { - eprintln!("ERROR: History output failed with the following error: {err}"); - std::process::exit(1); - } + check_for_write_errors(w.flush()); + } +} + +fn check_for_write_errors(write: Result<(), io::Error>) { + if let Err(err) = write { + // Ignore broken pipe (issue #626) + if err.kind() != io::ErrorKind::BrokenPipe { + eprintln!("ERROR: History output failed with the following error: {err}"); + std::process::exit(1); } } } -/// type wrapper around `History` so we can implement traits -struct FmtHistory<'a>(&'a History); +/// Wrapper around `History` so we can format output dynamically at runtime +struct FmtHistory<'a>(&'a History, CmdFormat); + +enum CmdFormat { + Literal, + Escaped, +} + +impl CmdFormat { + fn for_output(out: &O) -> Self { + if out.is_terminal() { + Self::Escaped + } else { + Self::Literal + } + } +} static TIME_FMT: &[time::format_description::FormatItem<'static>] = format_description!("[year]-[month]-[day] [hour repr:24]:[minute]:[second]"); @@ -203,7 +199,10 @@ impl FormatKey for FmtHistory<'_> { #[allow(clippy::cast_sign_loss)] fn fmt(&self, key: &str, f: &mut fmt::Formatter<'_>) -> Result<(), FormatKeyError> { match key { - "command" => f.write_str(&self.0.command.trim().escape_control())?, + "command" => match self.1 { + CmdFormat::Literal => f.write_str(self.0.command.trim()), + CmdFormat::Escaped => f.write_str(&self.0.command.trim().escape_control()), + }?, "directory" => f.write_str(self.0.cwd.trim())?, "exit" => f.write_str(&self.0.exit.to_string())?, "duration" => { -- cgit v1.3.1