From 39aa4781084a67b36531cfb161893c90a283cc40 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Thu, 20 Mar 2025 22:02:38 +0100 Subject: [PATCH] Extract unwrapping for buffer-writes that are assumed to not fail --- src/common.rs | 33 +++++++++++++++++++++++++++++++++ src/output.rs | 43 +++++++++++++++++++++++++------------------ src/reader.rs | 31 +++++++++++++++---------------- 3 files changed, 73 insertions(+), 34 deletions(-) diff --git a/src/common.rs b/src/common.rs index 2457691e7..55cfbdb51 100644 --- a/src/common.rs +++ b/src/common.rs @@ -10,6 +10,7 @@ use crate::global_safety::RelaxedAtomicBool; use crate::key; use crate::libc::MB_CUR_MAX; +use crate::output::Output; use crate::parse_util::parse_util_escape_string_with_quote; use crate::termsize::Termsize; use crate::wchar::{decode_byte_from_char, encode_byte_to_char, prelude::*}; @@ -1377,6 +1378,38 @@ pub fn write_loop(fd: &Fd, buf: &[u8]) -> std::io::Result<()> { Ok(()) } +// Output writes always succeed; this adapter allows us to use it in a write-like macro. +struct OutputWriteAdapter<'a, T: Output>(&'a mut T); + +impl<'a, T: Output> std::fmt::Write for OutputWriteAdapter<'a, T> { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + self.0.write_bytes(s.as_bytes()); + Ok(()) + } +} + +impl<'a, T: Output> std::io::Write for OutputWriteAdapter<'a, T> { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0.write_bytes(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +pub(crate) fn do_write_to_output(writer: &mut impl Output, args: std::fmt::Arguments<'_>) { + let mut adapter = OutputWriteAdapter(writer); + std::fmt::write(&mut adapter, args).unwrap() +} + +#[macro_export] +macro_rules! write_to_output { + ($out:expr, $($arg:tt)*) => {{ + $crate::common::do_write_to_output($out, format_args!($($arg)*)); + }}; +} + /// A rusty port of the C++ `read_loop()` function from `common.cpp`. This should be deprecated in /// favor of native rust read/write methods at some point. /// diff --git a/src/output.rs b/src/output.rs index 4d676800a..50f6656a8 100644 --- a/src/output.rs +++ b/src/output.rs @@ -42,6 +42,16 @@ pub fn set_color_support(val: ColorSupport) { COLOR_SUPPORT.store(val.bits(), Ordering::Relaxed); } +pub(crate) trait Output { + fn write_bytes(&mut self, buf: &[u8]); +} + +impl Output for Vec { + fn write_bytes(&mut self, buf: &[u8]) { + self.extend_from_slice(buf); + } +} + fn index_for_color(c: RgbColor) -> u8 { if c.is_named() || !(get_color_support().contains(ColorSupport::TERM_256COLOR)) { return c.to_name_index(); @@ -65,14 +75,13 @@ fn write_color_escape(outp: &mut Outputter, term: &Term, todo: &CStr, mut idx: u if term.max_colors == Some(8) && idx > 8 { idx -= 8; } - write!( + write_to_output!( outp, "\x1B[{}m", (if idx > 7 { 82 } else { 30 }) + i32::from(idx) + ((i32::from(!is_fg)) * 10) - ) - .expect("Writing to in-memory buffer should never fail"); + ); } else { - write!(outp, "\x1B[{};5;{}m", if is_fg { 38 } else { 48 }, idx).unwrap(); + write_to_output!(outp, "\x1B[{};5;{}m", if is_fg { 38 } else { 48 }, idx); } } } @@ -183,15 +192,14 @@ pub fn write_color(&mut self, color: RgbColor, is_fg: bool) -> bool { // Foreground: ^[38;2;;;m // Background: ^[48;2;;;m let rgb = color.to_color24(); - write!( + write_to_output!( self, "\x1B[{};2;{};{};{}m", if is_fg { 38 } else { 48 }, rgb.r, rgb.g, rgb.b - ) - .expect("Outputter::write should never fail"); + ); true } @@ -423,6 +431,13 @@ fn flush(&mut self) -> Result<()> { } } +impl Output for Outputter { + fn write_bytes(&mut self, buf: &[u8]) { + self.contents.extend_from_slice(buf); + self.maybe_flush(); + } +} + impl Outputter { /// Emit a terminfo string, like tputs. /// affcnt (number of lines affected) is assumed to be 1, i.e. not applicable. @@ -472,17 +487,9 @@ fn drop(&mut self) { } } -impl<'a> Write for BufferedOuputter<'a> { - fn write(&mut self, buf: &[u8]) -> Result { - self.0 - .write(buf) - .expect("Writing to in-memory buffer should never fail"); - Ok(buf.len()) - } - - fn flush(&mut self) -> Result<()> { - self.0.flush().unwrap(); - Ok(()) +impl<'a> Output for BufferedOuputter<'a> { + fn write_bytes(&mut self, buf: &[u8]) { + self.0.write_bytes(buf); } } diff --git a/src/reader.rs b/src/reader.rs index b87a9a54f..06a1fb98b 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -106,6 +106,7 @@ use crate::output::parse_color; use crate::output::parse_color_maybe_none; use crate::output::BufferedOuputter; +use crate::output::Output; use crate::output::Outputter; use crate::pager::{PageRendering, Pager, SelectionMotion}; use crate::panic::AT_EXIT; @@ -689,12 +690,11 @@ fn read_i(parser: &Parser) { data.clear(EditableLineTag::Commandline); data.update_buff_pos(EditableLineTag::Commandline, None); // OSC 133 "Command start" - write!( + write_to_output!( &mut BufferedOuputter::new(Outputter::stdoutput()), "\x1b]133;C;cmdline_url={}\x07", escape_string(&command, EscapeStringStyle::Url), - ) - .unwrap(); + ); event::fire_generic(parser, L!("fish_preexec").to_owned(), vec![command.clone()]); let eval_res = reader_run_command(parser, &command); signal_clear_cancel(); @@ -707,12 +707,11 @@ fn read_i(parser: &Parser) { parser.libdata_mut().exit_current_script = false; // OSC 133 "Command finished" - write!( + write_to_output!( &mut BufferedOuputter::new(Outputter::stdoutput()), "\x1b]133;D;{}\x07", parser.get_last_status() - ) - .unwrap(); + ); event::fire_generic(parser, L!("fish_postexec").to_owned(), vec![command]); // Allow any pending history items to be returned in the history array. data.history.resolve_pending(); @@ -1451,7 +1450,7 @@ pub fn request_cursor_position( assert!(wait_guard.is_none()); *wait_guard = Some(BlockingWait::CursorPosition(cursor_position_wait)); } - let _ = out.write_all(b"\x1b[6n"); + out.write_bytes(b"\x1b[6n"); self.save_screen_state(); } @@ -2580,7 +2579,7 @@ fn handle_char_event(&mut self, injected_event: Option) -> ControlFlo } } -fn xtgettcap(out: &mut impl Write, cap: &str) { +fn xtgettcap(out: &mut impl Output, cap: &str) { FLOG!( reader, format!( @@ -2589,16 +2588,16 @@ fn xtgettcap(out: &mut impl Write, cap: &str) { format!("\x1bP+q{}\x1b\\", DisplayAsHex(cap)) ) ); - let _ = write!(out, "\x1bP+q{}\x1b\\", DisplayAsHex(cap)); + write_to_output!(out, "\x1bP+q{}\x1b\\", DisplayAsHex(cap)); } -fn query_capabilities_via_dcs(out: &mut impl std::io::Write) { - let _ = out.write_all(b"\x1b[?2026h"); // begin synchronized update - let _ = out.write_all(b"\x1b[?1049h"); // enable alternative screen buffer - xtgettcap(out.by_ref(), "indn"); - xtgettcap(out.by_ref(), "cuu"); - let _ = out.write_all(b"\x1b[?1049l"); // disable alternative screen buffer - let _ = out.write_all(b"\x1b[?2026l"); // end synchronized update +fn query_capabilities_via_dcs(out: &mut impl Output) { + out.write_bytes(b"\x1b[?2026h"); // begin synchronized update + out.write_bytes(b"\x1b[?1049h"); // enable alternative screen buffer + xtgettcap(out, "indn"); + xtgettcap(out, "cuu"); + out.write_bytes(b"\x1b[?1049l"); // disable alternative screen buffer + out.write_bytes(b"\x1b[?2026l"); // end synchronized update } impl<'a> Reader<'a> {