fix: End synchronized update on StdTerminal drop#140
Conversation
|
Thanks for the PR! This looks good, but I think there are some more error cases that would be covered if we did something like this: diff --git a/packages/iocraft/src/render.rs b/packages/iocraft/src/render.rs
index ec7acb1..b6efc5e 100644
--- a/packages/iocraft/src/render.rs
+++ b/packages/iocraft/src/render.rs
@@ -13,7 +13,6 @@ use core::{
pin::Pin,
task::{self, Poll},
};
-use crossterm::{execute, terminal};
use futures::{
future::{select, FutureExt, LocalBoxFuture},
stream::{Stream, StreamExt},
@@ -461,16 +460,18 @@ impl<'a> Tree<'a> {
loop {
term.refresh_size();
let terminal_size = term.size();
- execute!(term, terminal::BeginSynchronizedUpdate,)?;
- let output = self.render(terminal_size.map(|(w, _)| w as usize), Some(&mut term));
- if output.did_clear_terminal_output || prev_canvas.as_ref() != Some(&output.canvas) {
- if !output.did_clear_terminal_output {
- term.clear_canvas()?;
+ term.synchronized_update(|mut term| {
+ let output = self.render(terminal_size.map(|(w, _)| w as usize), Some(&mut term));
+ if output.did_clear_terminal_output || prev_canvas.as_ref() != Some(&output.canvas)
+ {
+ if !output.did_clear_terminal_output {
+ term.clear_canvas()?;
+ }
+ term.write_canvas(&output.canvas)?;
}
- term.write_canvas(&output.canvas)?;
- }
- prev_canvas = Some(output.canvas);
- execute!(term, terminal::EndSynchronizedUpdate)?;
+ prev_canvas = Some(output.canvas);
+ Ok(())
+ })?;
if self.system_context.should_exit() || term.received_ctrl_c() {
break;
}
diff --git a/packages/iocraft/src/terminal.rs b/packages/iocraft/src/terminal.rs
index 3d92bcf..e65a819 100644
--- a/packages/iocraft/src/terminal.rs
+++ b/packages/iocraft/src/terminal.rs
@@ -282,7 +282,6 @@ impl Drop for StdTerminal {
if self.fullscreen {
let _ = queue!(self.dest, terminal::LeaveAlternateScreen);
}
- let _ = execute!(self.dest, cursor::Show);
}
}
@@ -381,6 +380,17 @@ pub(crate) struct Terminal {
received_ctrl_c: bool,
}
+/// Ends a synchronized update when dropped.
+struct SynchronizedTerminal<'a> {
+ inner: &'a mut Terminal,
+}
+
+impl Drop for SynchronizedTerminal<'_> {
+ fn drop(&mut self) {
+ let _ = execute!(self.inner, terminal::EndSynchronizedUpdate);
+ }
+}
+
impl Terminal {
pub fn new() -> io::Result<Self> {
Ok(Self::new_with_impl(StdTerminal::new(false)?))
@@ -428,6 +438,17 @@ impl Terminal {
self.received_ctrl_c
}
+ /// Wraps a series of terminal updates in a synchronized update block, making sure to end the
+ /// synchronized update even if there is an error or panic.
+ pub fn synchronized_update<F>(&mut self, f: F) -> io::Result<()>
+ where
+ F: FnOnce(&mut Self) -> io::Result<()>,
+ {
+ execute!(self, terminal::BeginSynchronizedUpdate)?;
+ let t = SynchronizedTerminal { inner: self };
+ f(t.inner)
+ }
+
pub async fn wait(&mut self) {
match &mut self.event_stream {
Some(event_stream) => {That should provide stronger guarantee that the synchronized update will end as soon as it's intended to. Thoughts? |
|
Looks good ! I think it would make sense to begin synchronized update when creating /// Synchronized update terminal guard.
/// Enters synchronized update on creation, exits when dropped.
struct SynchronizedUpdate<'a> {
inner: &'a mut Terminal,
}
impl<'a> SynchronizedUpdate<'a> {
fn begin(terminal: &'a mut Terminal) -> io::Result<Self> {
execute!(terminal, terminal::BeginSynchronizedUpdate)?;
Ok(Self { inner: terminal })
}
}Sadly none of that seems |
Did this comment get truncated? I think your suggestion looks good. If you want to update the PR, I'll be glad to merge it! |
|
Just pushed
Yeah, turns out I was too sleep deprived to finish the sentence. I was trying to say that sadly none of that seems to prevent terminal poisoning on |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #140 +/- ##
==========================================
+ Coverage 89.56% 89.57% +0.01%
==========================================
Files 31 31
Lines 5039 5054 +15
Branches 5039 5054 +15
==========================================
+ Hits 4513 4527 +14
Misses 428 428
- Partials 98 99 +1
🚀 New features to boost your workflow:
|
|
Thank you! 🚀 |
What It Does
Add
EndSynchronizedUpdateto the list of executed commands forimpl Drop for StdTerminal.In the general
terminal_render_loop, terminal executes an enter to synchronized update, but the render may panic mid-update (for example, #103), which usually causesStdTerminalto drop. But it never ends synchronized update which can cause issues.In my case, I was trying to run something akin to #114 in alacritty + zellij, panic mid-update would freeze the terminal rendering (but it would accept input still); just alacritty worked as expected though (so I think it's zellij related, but I have no idea actually how). Ending synchronized update fixed the issue !
Related Issues