diff --git a/src/markdown/table.rs b/src/markdown/table.rs index fa42ca2..00fa7db 100644 --- a/src/markdown/table.rs +++ b/src/markdown/table.rs @@ -19,8 +19,8 @@ use super::{Events, inline, style}; use crate::wrap::wrap_spans; /// The narrowest a column's content shrinks to. A table that still does -/// not fit at this width renders too wide and lets the terminal cut it, -/// which loses the right of a row but never a row itself. +/// not fit at this width renders too wide rather than dropping a row of +/// itself, and what becomes of the overflow is the caller's business. const MINIMUM: usize = 3; /// One row of the table: its cells, each still unwrapped. diff --git a/src/play/transcript.rs b/src/play/transcript.rs index 4d4d74f..f51120a 100644 --- a/src/play/transcript.rs +++ b/src/play/transcript.rs @@ -143,14 +143,14 @@ impl Transcript { ) -> Result<(), B::Error> { let width = self.round_width(terminal)?; for row in self.narration.committable(width) { - self.emit(terminal, row, Kind::Narration)?; + self.narrate(terminal, row)?; } let forming = self.narration.forming(width).len(); for row in self .narration .spill(width, forming.saturating_sub(tail_rows as usize)) { - self.emit(terminal, row, Kind::Narration)?; + self.narrate(terminal, row)?; } Ok(()) } @@ -168,12 +168,46 @@ impl Transcript { pub fn flush(&mut self, terminal: &mut Terminal) -> Result<(), B::Error> { let width = self.round_width(terminal)?; for row in self.narration.finish(width) { - self.emit(terminal, row, Kind::Narration)?; + self.narrate(terminal, row)?; } self.width = None; Ok(()) } + /// Puts one row of the DM's narration in the transcript, cut to what + /// the terminal can hold now. + /// + /// A row wider than the screen is what a round rendered before the + /// terminal narrowed under it, and a table too wide for any screen + /// renders that way at every width. [`Buffer::set_line`] would drop + /// everything past the last column, and a row in the scrollback never + /// repaints, so those words would be gone for good. Wrapping the row + /// again at the width the terminal has now keeps all of them, at the + /// cost of the row's own spacing: a wrap collapses runs of whitespace, + /// so an indent or a table's padding does not survive the second + /// wrap. + /// + /// The extra rows this makes are invisible to + /// [`crate::markdown::MarkdownStream`], which counts the rows it + /// rendered at the round's own width, not the rows that reach the + /// screen. + /// + /// [`Buffer::set_line`]: ratatui::buffer::Buffer::set_line + fn narrate( + &mut self, + terminal: &mut Terminal, + row: Line<'static>, + ) -> Result<(), B::Error> { + let width = terminal.size()?.width as usize; + if row.width() <= width { + return self.emit(terminal, row, Kind::Narration); + } + for row in wrap_spans(Text::from(row), width) { + self.emit(terminal, row, Kind::Narration)?; + } + Ok(()) + } + /// The width this round renders at: the terminal's own width the /// first time the round asks for it, and that same width every time /// after, until [`Transcript::flush`] ends the round. diff --git a/src/play/transcript_tests.rs b/src/play/transcript_tests.rs index 61c6bfd..e9c5e70 100644 --- a/src/play/transcript_tests.rs +++ b/src/play/transcript_tests.rs @@ -420,3 +420,29 @@ fn a_round_that_narrows_mid_stream_says_nothing_a_second_time() { assert_eq!(scrollback(&wide), ["", PARAGRAPH]); assert_eq!(scrollback(&narrow), ["", AFTER]); } + +#[test] +fn a_row_too_wide_for_the_terminal_it_lands_on_keeps_every_word() { + let mut wide = terminal(WIDE); + let mut narrow = terminal(NARROW); + let mut transcript = Transcript::default(); + + transcript.delta(&format!("{AFTER}\n\n{PARAGRAPH}")); + transcript.stream(&mut wide, DEEP_TAIL).unwrap(); + transcript.flush(&mut narrow).unwrap(); + + // The paragraph rendered as one row at eighty columns. All of it is + // here, over the four rows twenty columns takes, and the row that + // separates it from the block above is one blank row of spacing. + assert_eq!(scrollback(&wide), ["", AFTER]); + assert_eq!( + scrollback(&narrow), + [ + "", + "one two three four", + "five six seven eight", + "nine ten eleven", + "twelve", + ] + ); +}