From a6a9471bef17593bdbe7fb62071d1cad0abf5003 Mon Sep 17 00:00:00 2001 From: itsjunetime Date: Sun, 19 Apr 2026 12:56:17 -0500 Subject: [PATCH] fix images not disappearing after terminal exits --- ratatui | 2 +- ratatui-image | 2 +- src/kitty.rs | 71 +++++++++++++++++++++++++++++++++++++-------------- src/main.rs | 58 ++++++++++++++++++++++++----------------- 4 files changed, 88 insertions(+), 45 deletions(-) diff --git a/ratatui b/ratatui index 720ac2d..abe7dcf 160000 --- a/ratatui +++ b/ratatui @@ -1 +1 @@ -Subproject commit 720ac2d0cad1ac6424364fea74856fec9c100cb1 +Subproject commit abe7dcf2344cfc71b413ce2ad20821fe55a8dfb1 diff --git a/ratatui-image b/ratatui-image index 8ad154a..f791e0a 160000 --- a/ratatui-image +++ b/ratatui-image @@ -1 +1 @@ -Subproject commit 8ad154a219c1e4832a378bd07aaf39b3ddff959b +Subproject commit f791e0a99cc4664dfa21affe119d4c1c43e3cc29 diff --git a/src/kitty.rs b/src/kitty.rs index a88f40f..73c17c3 100644 --- a/src/kitty.rs +++ b/src/kitty.rs @@ -1,5 +1,8 @@ use core::fmt::Display; -use std::{io::Write, num::NonZeroU32}; +use std::{ + io::{StdoutLock, Write, stdout}, + num::NonZeroU32 +}; use crossterm::{ cursor::MoveTo, @@ -63,28 +66,55 @@ impl Write for DbgWriter { } } +pub enum MaybeTmuxWriter +where + W: Write +{ + Tmux(TmuxWriter), + Normal(W) +} + +impl MaybeTmuxWriter { + pub fn new(w: W, is_tmux: bool) -> Self { + if is_tmux { + Self::Tmux(TmuxWriter::new(w)) + } else { + Self::Normal(w) + } + } +} + +impl Write for &mut MaybeTmuxWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + match *self { + MaybeTmuxWriter::Tmux(t) => t.write(buf), + MaybeTmuxWriter::Normal(w) => w.write(buf) + } + } + + fn flush(&mut self) -> std::io::Result<()> { + match *self { + MaybeTmuxWriter::Tmux(t) => t.flush(), + MaybeTmuxWriter::Normal(w) => w.flush() + } + } +} + pub async fn run_action<'es>( action: Action<'_, '_>, - is_tmux: bool, + writer: &mut MaybeTmuxWriter>, ev_stream: &'es mut EventStream ) -> Result, TransmitError<<&'es mut EventStream as AsyncInputReader>::Error>> { let writer = DbgWriter { - w: std::io::stdout().lock(), + w: writer, #[cfg(debug_assertions)] buf: String::new() }; - if is_tmux { - action - .execute_async(TmuxWriter::new(writer), ev_stream) - .await - .map(|(_, i)| i) - } else { - action - .execute_async(writer, ev_stream) - .await - .map(|(_, i)| i) - } + action + .execute_async(writer, ev_stream) + .await + .map(|(_, i)| i) } pub async fn do_shms_work(is_tmux: bool, ev_stream: &mut EventStream) -> bool { @@ -104,7 +134,8 @@ pub async fn do_shms_work(is_tmux: bool, ev_stream: &mut EventStream) -> bool { enable_raw_mode().unwrap(); - let res = run_action(Action::Query(&k_img), is_tmux, ev_stream).await; + let mut writer = MaybeTmuxWriter::new(stdout().lock(), is_tmux); + let res = run_action(Action::Query(&k_img), &mut writer, ev_stream).await; disable_raw_mode().unwrap(); @@ -153,6 +184,8 @@ pub async fn display_kitty_images<'es>( ev_stream: &'es mut EventStream, last_z_index: &mut i32 ) -> Result<(), DisplayErr<'es>> { + let mut writer = MaybeTmuxWriter::new(stdout().lock(), is_tmux); + let images = match display { KittyDisplay::NoChange => return Ok(()), KittyDisplay::ClearImages => @@ -161,7 +194,7 @@ pub async fn display_kitty_images<'es>( effect: ClearOrDelete::Clear, which: WhichToDelete::All }), - is_tmux, + &mut writer, ev_stream ) .await @@ -188,7 +221,7 @@ pub async fn display_kitty_images<'es>( ..DisplayConfig::default() }; - execute!(std::io::stdout(), MoveTo(pos.x, pos.y)).unwrap(); + execute!(&mut writer, MoveTo(pos.x, pos.y)).unwrap(); log::debug!("going to display img {img:#?}"); log::debug!("displaying with config {config:#?}"); @@ -217,7 +250,7 @@ pub async fn display_kitty_images<'es>( config, placement_id: None }, - is_tmux, + &mut writer, ev_stream ) .await @@ -234,7 +267,7 @@ pub async fn display_kitty_images<'es>( placement_id: *image_id, config }, - is_tmux, + &mut writer, ev_stream ) .await diff --git a/src/main.rs b/src/main.rs index dbc7aba..ff9efe9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,4 @@ -use core::{ - error::Error, - num::{NonZeroU32, NonZeroUsize} -}; +use core::{error::Error, num::NonZeroUsize}; use std::{ borrow::Cow, ffi::OsString, @@ -16,8 +13,8 @@ use crossterm::{ event::EventStream, execute, terminal::{ - EndSynchronizedUpdate, EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, - enable_raw_mode, window_size + EndSynchronizedUpdate, EnterAlternateScreen, LeaveAlternateScreen, WindowSize, + disable_raw_mode, enable_raw_mode, window_size } }; use debounce::EventDebouncer; @@ -39,7 +36,8 @@ use tdf::{ PrerenderLimit, converter::{ConvertedPage, ConverterMsg, run_conversion_loop}, kitty::{ - DisplayErr, DisplayErrSource, KittyDisplay, display_kitty_images, do_shms_work, run_action + DisplayErr, DisplayErrSource, KittyDisplay, MaybeTmuxWriter, display_kitty_images, + do_shms_work, run_action }, renderer::{self, MUPDF_BLACK, MUPDF_WHITE, RenderError, RenderInfo, RenderNotif}, tui::{BottomMessage, InputAction, MessageSetting, Tui} @@ -250,22 +248,20 @@ async fn inner_main() -> Result<(), WrappedErr> { // We need to create `picker` on this thread because if we create it on the `renderer` thread, // it messes up something with user input. Input never makes it to the crossterm thing let picker = Picker::from_query_stdio() - .or_else(|e| match e { - ratatui_image::errors::Errors::NoFontSize if - window_size.width != 0 - && window_size.height != 0 - && window_size.columns != 0 - && window_size.rows != 0 => - { + .or_else(|e| match (e, window_size) { + ( + ratatui_image::errors::Errors::NoFontSize, + WindowSize { width: 1.., height: 1.., columns: 1.., rows: 1.. } + ) => { // the 'equivalent' that is suggested instead is not the same. We need to keep // calling this. #[expect(deprecated)] Ok(Picker::from_fontsize((cell_width_px, cell_height_px))) }, - ratatui_image::errors::Errors::NoFontSize => Err(WrappedErr( + (ratatui_image::errors::Errors::NoFontSize, _) => Err(WrappedErr( "Unable to detect your terminal's font size; this is an issue with your terminal emulator.\nPlease use a different terminal emulator or report this bug to tdf.".into() )), - e => Err(WrappedErr(format!("Couldn't get the necessary information to set up images: {e}").into())) + (e, _) => Err(WrappedErr(format!("Couldn't get the necessary information to set up images: {e}").into())) })?; // then we want to spawn off the rendering task @@ -325,12 +321,13 @@ async fn inner_main() -> Result<(), WrappedErr> { })?; if is_kitty { + let mut writer = MaybeTmuxWriter::new(stdout().lock(), is_tmux); run_action( Action::Delete(DeleteConfig { effect: ClearOrDelete::Delete, - which: WhichToDelete::IdRange(NonZeroU32::new(1).unwrap()..=NonZeroU32::MAX) + which: WhichToDelete::All }), - is_tmux, + &mut writer, &mut ev_stream ) .await @@ -352,8 +349,8 @@ async fn inner_main() -> Result<(), WrappedErr> { let tui_rx = tui_rx.into_stream(); let from_converter = from_converter.into_stream(); - enter_redraw_loop( - ev_stream, + let res = enter_redraw_loop( + &mut ev_stream, to_renderer, tui_rx, to_converter, @@ -373,16 +370,29 @@ async fn inner_main() -> Result<(), WrappedErr> { ) .into() ) - })?; + }); + if is_kitty { + let mut writer = MaybeTmuxWriter::new(stdout().lock(), is_tmux); + _ = run_action( + Action::Delete(DeleteConfig { + effect: ClearOrDelete::Delete, + which: WhichToDelete::All + }), + &mut writer, + &mut ev_stream + ) + .await; + } drop(maybe_logger); - Ok(()) + + res } // oh shut up clippy who cares #[expect(clippy::too_many_arguments)] async fn enter_redraw_loop( - mut ev_stream: EventStream, + ev_stream: &mut EventStream, to_renderer: Sender, mut tui_rx: RecvStream<'_, Result>, to_converter: Sender, @@ -469,7 +479,7 @@ async fn enter_redraw_loop( })?; let maybe_err = - display_kitty_images(to_display, is_tmux, &mut ev_stream, &mut kitty_z_idx).await; + display_kitty_images(to_display, is_tmux, ev_stream, &mut kitty_z_idx).await; if let Err(DisplayErr { failed_pages,