zellij-server/src/screen: improve error handling (#1770)

* zellij-server/src/screen: improve error handling

* cleanup context vs with_context usage

* update error handling docs

* zellij-server/src/screen.rs: fix formatting
This commit is contained in:
Bohdan Ivashko 2022-10-04 12:09:32 +03:00 committed by GitHub
parent 716f606b44
commit 5fede55fbd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 96 additions and 57 deletions

View file

@ -26,7 +26,6 @@ If you have an interest in this, don't hesitate to get in touch with us and
refer to the [tracking issue][tracking_issue] to see what has already been refer to the [tracking issue][tracking_issue] to see what has already been
done. done.
# Error handling facilities # Error handling facilities
You get access to all the relevant functions and traits mentioned in the You get access to all the relevant functions and traits mentioned in the
@ -43,7 +42,6 @@ Panics are generally handled via the `Panic` error type and the
[`handle_panic`][handle_panic] panic handler function. The fancy formatting [`handle_panic`][handle_panic] panic handler function. The fancy formatting
is performed by the [`miette`][miette] crate. is performed by the [`miette`][miette] crate.
## Propagating errors ## Propagating errors
We use the [`anyhow`][anyhow] crate to propagate errors up the call stack. At We use the [`anyhow`][anyhow] crate to propagate errors up the call stack. At
@ -53,7 +51,7 @@ of providing [`context`][context] about where (i.e. under which circumstances)
an error happened. an error happened.
A critical requirement for propagating errors is that all functions involved A critical requirement for propagating errors is that all functions involved
must return the [`Result`][Result] type. This allows convenient error handling must return the [`Result`][result] type. This allows convenient error handling
with the `?` operator. with the `?` operator.
At some point you will likely stop propagating errors and decide what to do At some point you will likely stop propagating errors and decide what to do
@ -64,7 +62,6 @@ with the error. Generally you can:
1. Terminate program execution (See [`fatal`][fatal]), or 1. Terminate program execution (See [`fatal`][fatal]), or
2. Continue program execution (See [`non_fatal`][non_fatal]) 2. Continue program execution (See [`non_fatal`][non_fatal])
## Handling errors ## Handling errors
Ideally, when the program encounters an error it will try to recover as good as Ideally, when the program encounters an error it will try to recover as good as
@ -76,13 +73,13 @@ Recovery usually isn't an option if an operation has changed the internal state
(i.e. the value or content of specific variables) of objects in the code. In (i.e. the value or content of specific variables) of objects in the code. In
this case, if an error is encountered, it is best to declare the program state this case, if an error is encountered, it is best to declare the program state
corrupted and terminate the whole application. This can be done by `unwrap`ing corrupted and terminate the whole application. This can be done by `unwrap`ing
on the [`Result`][Result] type. Always try to propagate the error as good as on the [`Result`][result] type. Always try to propagate the error as good as
you can and attach meaningful context before `unwrap`ing. This gives the user you can and attach meaningful context before `unwrap`ing. This gives the user
an idea what went wrong and can also help developers in quickly identifying an idea what went wrong and can also help developers in quickly identifying
which parts of the code to debug if necessary. which parts of the code to debug if necessary.
When you encounter such a fatal error and cannot propagate it further up (e.g. When you encounter such a fatal error and cannot propagate it further up (e.g.
because the current function cannot be changed to return a [`Result`][Result], because the current function cannot be changed to return a [`Result`][result],
or because it is the "root" function of a program thread), use the or because it is the "root" function of a program thread), use the
[`fatal`][fatal] function to panic the application. It will attach some small [`fatal`][fatal] function to panic the application. It will attach some small
context to the error and finally `unwrap` it. Using this function over the context to the error and finally `unwrap` it. Using this function over the
@ -95,7 +92,6 @@ to handle it. Instead of `panic`ing the application, the error is written to
the application log and execution continues. Please use this sparingly, as an the application log and execution continues. Please use this sparingly, as an
error usually calls for actions to be taken rather than ignoring it. error usually calls for actions to be taken rather than ignoring it.
# Examples of applied error handling # Examples of applied error handling
You can have a look at the commit that introduced error handling to the You can have a look at the commit that introduced error handling to the
@ -103,7 +99,6 @@ You can have a look at the commit that introduced error handling to the
`zellij-server/src/screen.rs`). We'll use this to demonstrate a few things in `zellij-server/src/screen.rs`). We'll use this to demonstrate a few things in
the following text. the following text.
## Converting a function to return a `Result` type ## Converting a function to return a `Result` type
Here's an example of the `Screen::render` function as it looked before: Here's an example of the `Screen::render` function as it looked before:
@ -172,7 +167,7 @@ to this:
pub fn resize_to_screen(&mut self, new_screen_size: Size) -> Result<()> { pub fn resize_to_screen(&mut self, new_screen_size: Size) -> Result<()> {
// ... // ...
self.render() self.render()
.context("failed to resize to screen size: {new_screen_size:#?}") .with_context(|| format!("failed to resize to screen size: {new_screen_size:#?}"))
} }
``` ```
@ -183,30 +178,58 @@ time to do something about the error.
In general, any function calling `unwrap` or `expect` is a good candidate to be In general, any function calling `unwrap` or `expect` is a good candidate to be
rewritten to return a `Result` type instead. rewritten to return a `Result` type instead.
## Attaching context ## Attaching context
[Anyhow][anyhow]s [`Context`][context] trait gives us two methods to attach [Anyhow][anyhow]s [`Context`][context] trait gives us two methods to attach
context to an error: `context` and `with_context`. In functions where `Result`s context to an error: `context` and `with_context`. You should use `context`
can be returned in multiple places, it is preferable to use `with_context`, as if the message contains only a static text and `with_context` if you need
it saves you from duplicating the same context message in multiple places in additional formatting:
the code. This was shown in the `render` method above, but the call to the
other function returning a `Result` wasn't shown: ```rust
fn move_clients_between_tabs(
&mut self,
source_tab_index: usize,
destination_tab_index: usize,
clients_to_move: Option<Vec<ClientId>>,
) -> Result<()> {
// ...
if let Some(client_mode_info_in_source_tab) = drained_clients {
let destination_tab = self.get_indexed_tab_mut(destination_tab_index)
.context("failed to get destination tab by index")
.with_context(|| format!("failed to move clients from tab {source_tab_index} to tab {destination_tab_index}"))?;
// ...
}
Ok(())
}
```
Feel free to move context string/closure to a variable to avoid copy-pasting:
```rust ```rust
pub fn render(&mut self) -> Result<()> { pub fn render(&mut self) -> Result<()> {
let err_context = || "failed to render screen".to_string(); let err_context = "failed to render screen";
// ... // ...
for tab_index in tabs_to_close { for tab_index in tabs_to_close {
// ... // ...
self.close_tab_at_index(tab_index) self.close_tab_at_index(tab_index)
.with_context(err_context)?; .context(err_context)?;
} }
// ... // ...
self.bus self.bus
.senders .senders
.send_to_server(ServerInstruction::Render(Some(serialized_output))) .send_to_server(ServerInstruction::Render(Some(serialized_output)))
.context(err_context)
}
// ...
pub fn close_tab(&mut self, client_id: ClientId) -> Result<()> {
let err_context = || format!("failed to close tab for client {client_id:?}");
let active_tab_index = *self
.active_tab_indices
.get(&client_id)
.with_context(err_context)?;
self.close_tab_at_index(active_tab_index)
.with_context(err_context) .with_context(err_context)
} }
``` ```
@ -214,7 +237,6 @@ other function returning a `Result` wasn't shown:
When there is only a single `Result` to be returned from your function, use When there is only a single `Result` to be returned from your function, use
`context` as shown above for `resize_to_screen`. `context` as shown above for `resize_to_screen`.
## Choosing helpful context messages ## Choosing helpful context messages
When attaching context to an error, usually you want to express what you were When attaching context to an error, usually you want to express what you were
@ -248,7 +270,6 @@ give us a [`NotFound`][2] error), so we don't have to repeat that.
In case of doubt, look at the name of the function you're currently working in In case of doubt, look at the name of the function you're currently working in
and write a context message somehow mentioning this. and write a context message somehow mentioning this.
## Terminating execution ## Terminating execution
We want to propagate errors as far up as we can. This way, every function along We want to propagate errors as far up as we can. This way, every function along
@ -300,13 +321,12 @@ the application (i.e. crash zellij). Since we made sure to attach context
messages to the errors on their way up, we will see these messages in the messages to the errors on their way up, we will see these messages in the
resulting output! resulting output!
[tracking_issue]: https://github.com/zellij-org/zellij/issues/1753 [tracking_issue]: https://github.com/zellij-org/zellij/issues/1753
[handle_panic]: https://docs.rs/zellij-utils/latest/zellij_utils/errors/fn.handle_panic.html [handle_panic]: https://docs.rs/zellij-utils/latest/zellij_utils/errors/fn.handle_panic.html
[miette]: https://crates.io/crates/miette [miette]: https://crates.io/crates/miette
[anyhow]: https://crates.io/crates/anyhow [anyhow]: https://crates.io/crates/anyhow
[context]: https://docs.rs/anyhow/latest/anyhow/trait.Context.html [context]: https://docs.rs/anyhow/latest/anyhow/trait.Context.html
[Result]: https://doc.rust-lang.org/stable/std/result/enum.Result.html [result]: https://doc.rust-lang.org/stable/std/result/enum.Result.html
[fatal]: https://docs.rs/zellij-utils/latest/zellij_utils/errors/trait.FatalError.html#tymethod.fatal [fatal]: https://docs.rs/zellij-utils/latest/zellij_utils/errors/trait.FatalError.html#tymethod.fatal
[non_fatal]: https://docs.rs/zellij-utils/latest/zellij_utils/errors/trait.FatalError.html#tymethod.non_fatal [non_fatal]: https://docs.rs/zellij-utils/latest/zellij_utils/errors/trait.FatalError.html#tymethod.non_fatal
[1]: https://github.com/zellij-org/zellij/commit/99e2bef8c68bd166cf89e90c8ffe8c02272ab4d3 [1]: https://github.com/zellij-org/zellij/commit/99e2bef8c68bd166cf89e90c8ffe8c02272ab4d3

View file

@ -394,18 +394,28 @@ impl Screen {
source_tab_index: usize, source_tab_index: usize,
destination_tab_index: usize, destination_tab_index: usize,
clients_to_move: Option<Vec<ClientId>>, clients_to_move: Option<Vec<ClientId>>,
) { ) -> Result<()> {
let err_context = || {
format!(
"failed to move clients from tab {source_tab_index} to tab {destination_tab_index}"
)
};
// None ==> move all clients // None ==> move all clients
let drained_clients = self let drained_clients = self
.get_indexed_tab_mut(source_tab_index) .get_indexed_tab_mut(source_tab_index)
.map(|t| t.drain_connected_clients(clients_to_move)); .map(|t| t.drain_connected_clients(clients_to_move));
if let Some(client_mode_info_in_source_tab) = drained_clients { if let Some(client_mode_info_in_source_tab) = drained_clients {
let destination_tab = self.get_indexed_tab_mut(destination_tab_index).unwrap(); let destination_tab = self
.get_indexed_tab_mut(destination_tab_index)
.context("failed to get destination tab by index")
.with_context(err_context)?;
destination_tab.add_multiple_clients(client_mode_info_in_source_tab); destination_tab.add_multiple_clients(client_mode_info_in_source_tab);
destination_tab.update_input_modes(); destination_tab.update_input_modes();
destination_tab.set_force_render(); destination_tab.set_force_render();
destination_tab.visible(true); destination_tab.visible(true);
} }
Ok(())
} }
fn update_client_tab_focus(&mut self, client_id: ClientId, new_tab_index: usize) { fn update_client_tab_focus(&mut self, client_id: ClientId, new_tab_index: usize) {
match self.active_tab_indices.remove(&client_id) { match self.active_tab_indices.remove(&client_id) {
@ -439,7 +449,8 @@ impl Screen {
let current_tab_index = current_tab.index; let current_tab_index = current_tab.index;
let new_tab_index = new_tab.index; let new_tab_index = new_tab.index;
if self.session_is_mirrored { if self.session_is_mirrored {
self.move_clients_between_tabs(current_tab_index, new_tab_index, None); self.move_clients_between_tabs(current_tab_index, new_tab_index, None)
.with_context(err_context)?;
let all_connected_clients: Vec<ClientId> = let all_connected_clients: Vec<ClientId> =
self.connected_clients.borrow().iter().copied().collect(); self.connected_clients.borrow().iter().copied().collect();
for client_id in all_connected_clients { for client_id in all_connected_clients {
@ -450,7 +461,8 @@ impl Screen {
current_tab_index, current_tab_index,
new_tab_index, new_tab_index,
Some(vec![client_id]), Some(vec![client_id]),
); )
.with_context(err_context)?;
self.update_client_tab_focus(client_id, new_tab_index); self.update_client_tab_focus(client_id, new_tab_index);
} }
@ -559,7 +571,7 @@ impl Screen {
tab.set_force_render(); tab.set_force_render();
} }
self.render() self.render()
.context("failed to resize to screen size: {new_screen_size:#?}") .with_context(|| format!("failed to resize to screen size: {new_screen_size:#?}"))
} }
pub fn update_pixel_dimensions(&mut self, pixel_dimensions: PixelDimensions) { pub fn update_pixel_dimensions(&mut self, pixel_dimensions: PixelDimensions) {
@ -604,7 +616,7 @@ impl Screen {
/// Renders this [`Screen`], which amounts to rendering its active [`Tab`]. /// Renders this [`Screen`], which amounts to rendering its active [`Tab`].
pub fn render(&mut self) -> Result<()> { pub fn render(&mut self) -> Result<()> {
let err_context = || "failed to render screen".to_string(); let err_context = "failed to render screen";
let mut output = Output::new( let mut output = Output::new(
self.sixel_image_store.clone(), self.sixel_image_store.clone(),
@ -622,14 +634,13 @@ impl Screen {
} }
} }
for tab_index in tabs_to_close { for tab_index in tabs_to_close {
self.close_tab_at_index(tab_index) self.close_tab_at_index(tab_index).context(err_context)?;
.with_context(err_context)?;
} }
let serialized_output = output.serialize(); let serialized_output = output.serialize();
self.bus self.bus
.senders .senders
.send_to_server(ServerInstruction::Render(Some(serialized_output))) .send_to_server(ServerInstruction::Render(Some(serialized_output)))
.with_context(err_context) .context(err_context)
} }
/// Returns a mutable reference to this [`Screen`]'s tabs. /// Returns a mutable reference to this [`Screen`]'s tabs.
@ -689,7 +700,7 @@ impl Screen {
new_pids: Vec<RawFd>, new_pids: Vec<RawFd>,
client_id: ClientId, client_id: ClientId,
) -> Result<()> { ) -> Result<()> {
let err_context = || format!("failed to create new tab for client {client_id:?}",); let err_context = || format!("failed to create new tab for client {client_id:?}");
let tab_index = self.get_new_tab_index(); let tab_index = self.get_new_tab_index();
let position = self.tabs.len(); let position = self.tabs.len();
@ -703,7 +714,7 @@ impl Screen {
self.bus self.bus
.os_input .os_input
.as_ref() .as_ref()
.with_context(|| format!("failed to create new tab for client {client_id:?}"))? .with_context(err_context)?
.clone(), .clone(),
self.bus.senders.clone(), self.bus.senders.clone(),
self.max_panes, self.max_panes,
@ -795,7 +806,7 @@ impl Screen {
} }
self.connected_clients.borrow_mut().remove(&client_id); self.connected_clients.borrow_mut().remove(&client_id);
self.update_tabs() self.update_tabs()
.with_context(|| format!("failed to remote client {client_id:?}")) .with_context(|| format!("failed to remove client {client_id:?}"))
} }
pub fn update_tabs(&self) -> Result<()> { pub fn update_tabs(&self) -> Result<()> {
@ -843,7 +854,8 @@ impl Screen {
} }
pub fn update_active_tab_name(&mut self, buf: Vec<u8>, client_id: ClientId) -> Result<()> { pub fn update_active_tab_name(&mut self, buf: Vec<u8>, client_id: ClientId) -> Result<()> {
let s = str::from_utf8(&buf).unwrap(); let s = str::from_utf8(&buf)
.with_context(|| format!("failed to construct tab name from buf: {buf:?}"))?;
if let Some(active_tab) = self.get_active_tab_mut(client_id) { if let Some(active_tab) = self.get_active_tab_mut(client_id) {
match s { match s {
"\0" => { "\0" => {
@ -860,8 +872,9 @@ impl Screen {
} }
}, },
} }
self.update_tabs() self.update_tabs().with_context(|| {
.context("failed to update active tabs name for client id: {client_id:?}") format!("failed to update active tabs name for client id: {client_id:?}")
})
} else { } else {
log::error!("Active tab not found for client id: {client_id:?}"); log::error!("Active tab not found for client id: {client_id:?}");
Ok(()) Ok(())
@ -965,11 +978,12 @@ impl Screen {
self.render() self.render()
} }
fn unblock_input(&self) { fn unblock_input(&self) -> Result<()> {
self.bus self.bus
.senders .senders
.send_to_server(ServerInstruction::UnblockInputThread) .send_to_server(ServerInstruction::UnblockInputThread)
.unwrap(); .context("failed to send message to server")
.context("failed to unblock input")
} }
} }
@ -1043,7 +1057,7 @@ pub(crate) fn screen_thread_main(
} }
}, },
}; };
screen.unblock_input(); screen.unblock_input()?;
screen.update_tabs()?; screen.update_tabs()?;
screen.render()?; screen.render()?;
@ -1051,7 +1065,7 @@ pub(crate) fn screen_thread_main(
ScreenInstruction::OpenInPlaceEditor(pid, client_id) => { ScreenInstruction::OpenInPlaceEditor(pid, client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| tab active_tab!(screen, client_id, |tab: &mut Tab| tab
.suppress_active_pane(pid, client_id)); .suppress_active_pane(pid, client_id));
screen.unblock_input(); screen.unblock_input()?;
screen.update_tabs()?; screen.update_tabs()?;
screen.render()?; screen.render()?;
@ -1059,28 +1073,28 @@ pub(crate) fn screen_thread_main(
ScreenInstruction::TogglePaneEmbedOrFloating(client_id) => { ScreenInstruction::TogglePaneEmbedOrFloating(client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| tab active_tab!(screen, client_id, |tab: &mut Tab| tab
.toggle_pane_embed_or_floating(client_id)); .toggle_pane_embed_or_floating(client_id));
screen.unblock_input(); screen.unblock_input()?;
screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins
screen.render()?; screen.render()?;
}, },
ScreenInstruction::ToggleFloatingPanes(client_id, default_shell) => { ScreenInstruction::ToggleFloatingPanes(client_id, default_shell) => {
active_tab!(screen, client_id, |tab: &mut Tab| tab active_tab!(screen, client_id, |tab: &mut Tab| tab
.toggle_floating_panes(client_id, default_shell)); .toggle_floating_panes(client_id, default_shell));
screen.unblock_input(); screen.unblock_input()?;
screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins
screen.render()?; screen.render()?;
}, },
ScreenInstruction::HorizontalSplit(pid, client_id) => { ScreenInstruction::HorizontalSplit(pid, client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| tab active_tab!(screen, client_id, |tab: &mut Tab| tab
.horizontal_split(pid, client_id)); .horizontal_split(pid, client_id));
screen.unblock_input(); screen.unblock_input()?;
screen.update_tabs()?; screen.update_tabs()?;
screen.render()?; screen.render()?;
}, },
ScreenInstruction::VerticalSplit(pid, client_id) => { ScreenInstruction::VerticalSplit(pid, client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| tab active_tab!(screen, client_id, |tab: &mut Tab| tab
.vertical_split(pid, client_id)); .vertical_split(pid, client_id));
screen.unblock_input(); screen.unblock_input()?;
screen.update_tabs()?; screen.update_tabs()?;
screen.render()?; screen.render()?;
}, },
@ -1143,7 +1157,7 @@ pub(crate) fn screen_thread_main(
}, },
ScreenInstruction::MoveFocusLeftOrPreviousTab(client_id) => { ScreenInstruction::MoveFocusLeftOrPreviousTab(client_id) => {
screen.move_focus_left_or_previous_tab(client_id)?; screen.move_focus_left_or_previous_tab(client_id)?;
screen.unblock_input(); screen.unblock_input()?;
screen.render()?; screen.render()?;
}, },
ScreenInstruction::MoveFocusDown(client_id) => { ScreenInstruction::MoveFocusDown(client_id) => {
@ -1158,7 +1172,7 @@ pub(crate) fn screen_thread_main(
}, },
ScreenInstruction::MoveFocusRightOrNextTab(client_id) => { ScreenInstruction::MoveFocusRightOrNextTab(client_id) => {
screen.move_focus_right_or_next_tab(client_id)?; screen.move_focus_right_or_next_tab(client_id)?;
screen.unblock_input(); screen.unblock_input()?;
screen.render()?; screen.render()?;
}, },
ScreenInstruction::MoveFocusUp(client_id) => { ScreenInstruction::MoveFocusUp(client_id) => {
@ -1311,22 +1325,22 @@ pub(crate) fn screen_thread_main(
}, },
ScreenInstruction::SwitchTabNext(client_id) => { ScreenInstruction::SwitchTabNext(client_id) => {
screen.switch_tab_next(client_id)?; screen.switch_tab_next(client_id)?;
screen.unblock_input(); screen.unblock_input()?;
screen.render()?; screen.render()?;
}, },
ScreenInstruction::SwitchTabPrev(client_id) => { ScreenInstruction::SwitchTabPrev(client_id) => {
screen.switch_tab_prev(client_id)?; screen.switch_tab_prev(client_id)?;
screen.unblock_input(); screen.unblock_input()?;
screen.render()?; screen.render()?;
}, },
ScreenInstruction::CloseTab(client_id) => { ScreenInstruction::CloseTab(client_id) => {
screen.close_tab(client_id)?; screen.close_tab(client_id)?;
screen.unblock_input(); screen.unblock_input()?;
screen.render()?; screen.render()?;
}, },
ScreenInstruction::NewTab(layout, new_pane_pids, client_id) => { ScreenInstruction::NewTab(layout, new_pane_pids, client_id) => {
screen.new_tab(layout, new_pane_pids, client_id)?; screen.new_tab(layout, new_pane_pids, client_id)?;
screen.unblock_input(); screen.unblock_input()?;
screen.render()?; screen.render()?;
}, },
ScreenInstruction::GoToTab(tab_index, client_id) => { ScreenInstruction::GoToTab(tab_index, client_id) => {
@ -1334,7 +1348,7 @@ pub(crate) fn screen_thread_main(
client_id.or_else(|| screen.active_tab_indices.keys().next().copied()) client_id.or_else(|| screen.active_tab_indices.keys().next().copied())
{ {
screen.go_to_tab(tab_index as usize, client_id)?; screen.go_to_tab(tab_index as usize, client_id)?;
screen.unblock_input(); screen.unblock_input()?;
screen.render()?; screen.render()?;
} }
}, },
@ -1433,7 +1447,7 @@ pub(crate) fn screen_thread_main(
}, },
ScreenInstruction::ToggleTab(client_id) => { ScreenInstruction::ToggleTab(client_id) => {
screen.toggle_tab(client_id)?; screen.toggle_tab(client_id)?;
screen.unblock_input(); screen.unblock_input()?;
screen.render()?; screen.render()?;
}, },
ScreenInstruction::AddClient(client_id) => { ScreenInstruction::AddClient(client_id) => {
@ -1448,25 +1462,30 @@ pub(crate) fn screen_thread_main(
ScreenInstruction::AddOverlay(overlay, _client_id) => { ScreenInstruction::AddOverlay(overlay, _client_id) => {
screen.get_active_overlays_mut().pop(); screen.get_active_overlays_mut().pop();
screen.get_active_overlays_mut().push(overlay); screen.get_active_overlays_mut().push(overlay);
screen.unblock_input(); screen.unblock_input()?;
}, },
ScreenInstruction::RemoveOverlay(_client_id) => { ScreenInstruction::RemoveOverlay(_client_id) => {
screen.get_active_overlays_mut().pop(); screen.get_active_overlays_mut().pop();
screen.render()?; screen.render()?;
screen.unblock_input(); screen.unblock_input()?;
}, },
ScreenInstruction::ConfirmPrompt(_client_id) => { ScreenInstruction::ConfirmPrompt(_client_id) => {
let overlay = screen.get_active_overlays_mut().pop(); let overlay = screen.get_active_overlays_mut().pop();
let instruction = overlay.and_then(|o| o.prompt_confirm()); let instruction = overlay.and_then(|o| o.prompt_confirm());
if let Some(instruction) = instruction { if let Some(instruction) = instruction {
screen.bus.senders.send_to_server(*instruction).unwrap(); screen
.bus
.senders
.send_to_server(*instruction)
.context("failed to send message to server")
.context("failed to confirm prompt")?;
} }
screen.unblock_input(); screen.unblock_input()?;
}, },
ScreenInstruction::DenyPrompt(_client_id) => { ScreenInstruction::DenyPrompt(_client_id) => {
screen.get_active_overlays_mut().pop(); screen.get_active_overlays_mut().pop();
screen.render()?; screen.render()?;
screen.unblock_input(); screen.unblock_input()?;
}, },
ScreenInstruction::UpdateSearch(c, client_id) => { ScreenInstruction::UpdateSearch(c, client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| tab active_tab!(screen, client_id, |tab: &mut Tab| tab