diff options
| author | Peter Holloway <holloway.p.r@gmail.com> | 2024-01-23 10:21:45 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-01-23 10:21:45 +0000 |
| commit | 2bd7114cea89a471dcf6f4c279048db4be5d7731 (patch) | |
| tree | 148b842ee69cab5a64cc8cc26e7bef79786b966c | |
| parent | docs: Mention environment variables for custom paths (#1614) (diff) | |
| download | atuin-2bd7114cea89a471dcf6f4c279048db4be5d7731.zip | |
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.
| -rw-r--r-- | atuin/src/command/client/history.rs | 73 |
1 files 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<O: IsTerminal>(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" => { |
