Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
- Replace the generic `From<S>` impl for `BorderStyle` with `FromStr`.
- Bump MSRV requirements: bump resolver and rust edition to 2024.
- Fix `Dialog::important_area` to point at the focused button, so scroll views can reach it.
- Add `Printer::try_on_window`, a non-panicking alternative to `Printer::on_window` for when
the output window no longer fits the backend buffer (e.g. a terminal resize race).

## cursive-core 0.4.7

Expand Down
88 changes: 83 additions & 5 deletions cursive-core/src/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,16 +299,36 @@ impl<'a, 'b> Printer<'a, 'b> {
}
}

/// Calls a closure on the output window for this printer, if it fits in the backend.
///
/// Returns `None` without calling `f` if the current output window no longer fits
/// in the backend buffer -- for instance if the terminal was resized smaller between
/// the last layout pass and this draw. This mirrors [`Self::on_window`], but degrades
/// gracefully instead of panicking: the caller can simply skip this frame's windowed
/// draw and let the next layout pass correct the sizes.
pub fn try_on_window<F, R>(&self, f: F) -> Option<R>
where
F: FnOnce(&mut Window<'_>) -> R,
{
let mut buffer = self.buffer.write();
let mut window = buffer.window(self.output_window())?;
Some(f(&mut window))
}

/// Calls a closure on the output window for this printer.
///
/// # Panics
///
/// Panics if the current output window doesn't fit in the backend buffer, which can
/// happen if the terminal is resized between the layout pass and the draw pass. Use
/// [`Self::try_on_window`] instead if you'd rather degrade gracefully (skip the draw
/// for this frame) than panic in that case.
pub fn on_window<F, R>(&self, f: F) -> R
where
F: FnOnce(&mut Window<'_>) -> R,
{
let mut buffer = self.buffer.write();
let mut window = buffer
.window(self.output_window())
.expect("printer size exceeds backend size");
f(&mut window)
self.try_on_window(f)
.expect("printer size exceeds backend size")
}

/// Prints a line using the given character.
Expand Down Expand Up @@ -755,3 +775,61 @@ impl<'a, 'b> Printer<'a, 'b> {
})
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
#[should_panic(expected = "printer size exceeds backend size")]
fn on_window_panics_when_output_window_exceeds_backend() {
// Simulate a terminal shrinking between the layout pass and the draw
// pass: the printer was laid out for a 10x10 area, but the backend
// buffer has since shrunk to 5x5.
let mut backend_buffer = PrintBuffer::new();
backend_buffer.resize(Vec2::new(5, 5));
let backend_buffer = RwLock::new(backend_buffer);
let theme = Theme::default();

let printer = Printer::new((10, 10), &theme, &backend_buffer);

printer.on_window(|_window| ());
}

#[test]
fn try_on_window_degrades_gracefully_when_output_window_exceeds_backend() {
// Same stale-size race as above, but using the non-panicking variant: the
// windowed draw should simply be skipped for this frame.
let mut backend_buffer = PrintBuffer::new();
backend_buffer.resize(Vec2::new(5, 5));
let backend_buffer = RwLock::new(backend_buffer);
let theme = Theme::default();

let printer = Printer::new((10, 10), &theme, &backend_buffer);

let mut called = false;
let result = printer.try_on_window(|_window| {
called = true;
});

assert_eq!(result, None);
assert!(
!called,
"the closure should not run when the window doesn't fit"
);
}

#[test]
fn try_on_window_runs_closure_when_output_window_fits_backend() {
let mut backend_buffer = PrintBuffer::new();
backend_buffer.resize(Vec2::new(10, 10));
let backend_buffer = RwLock::new(backend_buffer);
let theme = Theme::default();

let printer = Printer::new((10, 10), &theme, &backend_buffer);

let result = printer.try_on_window(|window| window.size());

assert_eq!(result, Some(Vec2::new(10, 10)));
}
}
5 changes: 4 additions & 1 deletion cursive-core/src/views/gradient_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,10 @@ where
self.view.draw(printer);

let buffer_size = printer.buffer_size();
printer.on_window(|window| {
// Use the non-panicking variant: if the backend was resized smaller between the
// layout pass and this draw, just skip the gradient overlay for this frame instead
// of crashing the whole app (see #885). The next layout pass will correct the sizes.
printer.try_on_window(|window| {
let viewport = window.viewport();
for y in 0..viewport.height() {
for x in 0..viewport.width() {
Expand Down