Server: Remove panics in tab module (#1748)

* utils/errors: Add `ToAnyhow` trait

for converting `Result` types that don't satisfy `anyhow`s trait
constraints (`Display + Send + Sync + 'static`) conveniently.

An example of such a Result is the `SendError` returned from
`send_to_plugins`, which sends `PluginInstruction`s as message type.
One of the enum variants can contain a `mpsc::Sender`, which is `!Sync`
and hence makes the whole `SendError` be `!Sync` in this case. Add an
implementation for this case that takes the message and converts it into
an error containing the message formatted as string, with the additional
`ErrorContext` as anyhow context.

* server/tab: Remove calls to `unwrap()`

and apply error reporting via `anyhow` instead. Make all relevant
functions return `Result`s where previously a panic could occur and
attach error context.

* server/screen: Modify `update_tab!`

to accept an optional 4th parameter, a literal "?". If present, this
will append a `?` to the given closure verbatim to handle/propagate
errors from within the generated macro code.

* server/screen: Handle new `Result`s from `Tab`

and apply appropriate error context and propagate errors further up.

* server/tab/unit: `unwrap` on new `Result`s

* server/unit: Unwrap `Results` in screen tests

* server/tab: Better message for ad-hoc errors

created with `anyhow!`. Since these errors don't have an underlying
cause, we describe the cause in the macro instead and then attach the
error context as usual before `?`ing the error back up.

* utils/cargo: Activate `anyhow`s "backtrace" feature

to capture error backtraces at the error origins (i.e. where we first
receive an error and convert it to a `anyhow::Error`). Since we
propagate error back up the call stack now, the place where we `unwrap`
on errors doesn't match the place where the error originated. Hence, the
callstack, too, is quite misleading since it contains barely any
references of the functions that triggered the error.

As a consequence, we have 2 backtraces now when zellij crashes: One from
`anyhow` (that is implicitly attached to anyhows error reports), and one
from the custom panic handler (which is displayed through `miette`).

* utils/errors: Separate stack traces

in the output of miette. Since we record backtraces with `anyhow` now,
we end up having two backtraces in the output: One from the `anyhow`
error and one from the actual call to `panic`. Adds a comment explaining
the situation and another "section" to the error output of miette: We
print the backtrace from anyhow as "Stack backtrace", and the output
from the panic handler as "Panic backtrace". We keep both for the
(hopefully unlikely) case that the anyhow backtrace isn't existent, so
we still have at least something to work with.

* server/screen: Remove calls to `fatal`

and leave the `panic`ing to the calling function instead.

* server/screen: Remove needless macro

which extended `active_tab!` by passing the client IDs to the closure.
However, this isn't necessary because closures capture their environment
already, and the client_id needn't be mutable.

* server/screen: Handle unused result

* server/screen: Reintroduce arcane macro

that defaults to some default client_id if it isn't valid (e.g. when the
ScreenInstruction is sent via CLI).

* server/tab/unit: Unwrap new results
This commit is contained in:
har7an 2022-10-06 06:46:18 +00:00 committed by GitHub
parent 46edc590ec
commit 6715f4629c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 1517 additions and 903 deletions

3
Cargo.lock generated
View file

@ -40,6 +40,9 @@ name = "anyhow"
version = "1.0.57" version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
dependencies = [
"backtrace",
]
[[package]] [[package]]
name = "arc-swap" name = "arc-swap"

View file

@ -40,8 +40,10 @@ use zellij_utils::{
/// ///
/// - screen: An instance of `Screen` to operate on /// - screen: An instance of `Screen` to operate on
/// - client_id: The client_id, usually taken from the `ScreenInstruction` that's being processed /// - client_id: The client_id, usually taken from the `ScreenInstruction` that's being processed
/// - closure: A closure satisfying `|tab: &mut Tab| -> ()` /// - closure: A closure satisfying `|tab: &mut Tab| -> ()` OR `|tab: &mut Tab| -> Result<T>` (see
/// '?' below)
/// - ?: A literal "?", to append a `?` to the closure when it returns a `Result` type. This
/// argument is optional and not needed when the closure returns `()`
macro_rules! active_tab { macro_rules! active_tab {
($screen:ident, $client_id:ident, $closure:expr) => { ($screen:ident, $client_id:ident, $closure:expr) => {
if let Some(active_tab) = $screen.get_active_tab_mut($client_id) { if let Some(active_tab) = $screen.get_active_tab_mut($client_id) {
@ -54,7 +56,16 @@ macro_rules! active_tab {
log::error!("Active tab not found for client id: {:?}", $client_id); log::error!("Active tab not found for client id: {:?}", $client_id);
} }
}; };
// Same as above, but with an added `?` for when the close returns a `Result` type.
($screen:ident, $client_id:ident, $closure:expr, ?) => {
if let Some(active_tab) = $screen.get_active_tab_mut($client_id) {
$closure(active_tab)?;
} else {
log::error!("Active tab not found for client id: {:?}", $client_id);
}
};
} }
macro_rules! active_tab_and_connected_client_id { macro_rules! active_tab_and_connected_client_id {
($screen:ident, $client_id:ident, $closure:expr) => { ($screen:ident, $client_id:ident, $closure:expr) => {
match $screen.get_active_tab_mut($client_id) { match $screen.get_active_tab_mut($client_id) {
@ -71,6 +82,30 @@ macro_rules! active_tab_and_connected_client_id {
log::error!("Active tab not found for client id: {:?}", $client_id); log::error!("Active tab not found for client id: {:?}", $client_id);
}, },
} }
} else {
log::error!("No client ids in screen found");
};
},
}
};
// Same as above, but with an added `?` for when the closure returns a `Result` type.
($screen:ident, $client_id:ident, $closure:expr, ?) => {
match $screen.get_active_tab_mut($client_id) {
Some(active_tab) => {
$closure(active_tab, $client_id)?;
},
None => {
if let Some(client_id) = $screen.get_first_client_id() {
match $screen.get_active_tab_mut(client_id) {
Some(active_tab) => {
$closure(active_tab, client_id)?;
},
None => {
log::error!("Active tab not found for client id: {:?}", $client_id);
},
}
} else {
log::error!("No client ids in screen found");
}; };
}, },
} }
@ -393,32 +428,44 @@ impl Screen {
fn move_clients_from_closed_tab( fn move_clients_from_closed_tab(
&mut self, &mut self,
client_ids_and_mode_infos: Vec<(ClientId, ModeInfo)>, client_ids_and_mode_infos: Vec<(ClientId, ModeInfo)>,
) { ) -> Result<()> {
let err_context = || "failed to move clients from closed tab".to_string();
if self.tabs.is_empty() { if self.tabs.is_empty() {
log::error!( log::error!(
"No tabs left, cannot move clients: {:?} from closed tab", "No tabs left, cannot move clients: {:?} from closed tab",
client_ids_and_mode_infos client_ids_and_mode_infos
); );
return; return Ok(());
} }
let first_tab_index = *self.tabs.keys().next().unwrap(); let first_tab_index = *self
.tabs
.keys()
.next()
.context("screen contained no tabs")
.with_context(err_context)?;
for (client_id, client_mode_info) in client_ids_and_mode_infos { for (client_id, client_mode_info) in client_ids_and_mode_infos {
let client_tab_history = self.tab_history.entry(client_id).or_insert_with(Vec::new); let client_tab_history = self.tab_history.entry(client_id).or_insert_with(Vec::new);
if let Some(client_previous_tab) = client_tab_history.pop() { if let Some(client_previous_tab) = client_tab_history.pop() {
if let Some(client_active_tab) = self.tabs.get_mut(&client_previous_tab) { if let Some(client_active_tab) = self.tabs.get_mut(&client_previous_tab) {
self.active_tab_indices self.active_tab_indices
.insert(client_id, client_previous_tab); .insert(client_id, client_previous_tab);
client_active_tab.add_client(client_id, Some(client_mode_info)); client_active_tab
.add_client(client_id, Some(client_mode_info))
.with_context(err_context)?;
continue; continue;
} }
} }
self.active_tab_indices.insert(client_id, first_tab_index); self.active_tab_indices.insert(client_id, first_tab_index);
self.tabs self.tabs
.get_mut(&first_tab_index) .get_mut(&first_tab_index)
.unwrap() .with_context(err_context)?
.add_client(client_id, Some(client_mode_info)); .add_client(client_id, Some(client_mode_info))
.with_context(err_context)?;
} }
Ok(())
} }
fn move_clients_between_tabs( fn move_clients_between_tabs(
&mut self, &mut self,
source_tab_index: usize, source_tab_index: usize,
@ -440,13 +487,18 @@ impl Screen {
.get_indexed_tab_mut(destination_tab_index) .get_indexed_tab_mut(destination_tab_index)
.context("failed to get destination tab by index") .context("failed to get destination tab by index")
.with_context(err_context)?; .with_context(err_context)?;
destination_tab.add_multiple_clients(client_mode_info_in_source_tab); destination_tab
destination_tab.update_input_modes(); .add_multiple_clients(client_mode_info_in_source_tab)
.with_context(err_context)?;
destination_tab
.update_input_modes()
.with_context(err_context)?;
destination_tab.set_force_render(); destination_tab.set_force_render();
destination_tab.visible(true); destination_tab.visible(true).with_context(err_context)?;
} }
Ok(()) 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) {
Some(old_active_index) => { Some(old_active_index) => {
@ -498,7 +550,7 @@ impl Screen {
if let Some(current_tab) = self.get_indexed_tab_mut(current_tab_index) { if let Some(current_tab) = self.get_indexed_tab_mut(current_tab_index) {
if current_tab.has_no_connected_clients() { if current_tab.has_no_connected_clients() {
current_tab.visible(false); current_tab.visible(false).with_context(err_context)?;
} }
} else { } else {
log::error!("Tab index: {:?} not found", current_tab_index); log::error!("Tab index: {:?} not found", current_tab_index);
@ -564,6 +616,7 @@ impl Screen {
fn close_tab_at_index(&mut self, tab_index: usize) -> Result<()> { fn close_tab_at_index(&mut self, tab_index: usize) -> Result<()> {
let err_context = || format!("failed to close tab at index {tab_index:?}"); let err_context = || format!("failed to close tab at index {tab_index:?}");
let mut tab_to_close = self.tabs.remove(&tab_index).with_context(err_context)?; let mut tab_to_close = self.tabs.remove(&tab_index).with_context(err_context)?;
let pane_ids = tab_to_close.get_all_pane_ids(); let pane_ids = tab_to_close.get_all_pane_ids();
// below we don't check the result of sending the CloseTab instruction to the pty thread // below we don't check the result of sending the CloseTab instruction to the pty thread
@ -581,13 +634,14 @@ impl Screen {
.with_context(err_context) .with_context(err_context)
} else { } else {
let client_mode_infos_in_closed_tab = tab_to_close.drain_connected_clients(None); let client_mode_infos_in_closed_tab = tab_to_close.drain_connected_clients(None);
self.move_clients_from_closed_tab(client_mode_infos_in_closed_tab); self.move_clients_from_closed_tab(client_mode_infos_in_closed_tab)
.with_context(err_context)?;
let visible_tab_indices: HashSet<usize> = let visible_tab_indices: HashSet<usize> =
self.active_tab_indices.values().copied().collect(); self.active_tab_indices.values().copied().collect();
for t in self.tabs.values_mut() { for t in self.tabs.values_mut() {
if visible_tab_indices.contains(&t.index) { if visible_tab_indices.contains(&t.index) {
t.set_force_render(); t.set_force_render();
t.visible(true); t.visible(true).with_context(err_context)?;
} }
if t.position > tab_to_close.position { if t.position > tab_to_close.position {
t.position -= 1; t.position -= 1;
@ -683,7 +737,8 @@ impl Screen {
for (tab_index, tab) in &mut self.tabs { for (tab_index, tab) in &mut self.tabs {
if tab.has_selectable_tiled_panes() { if tab.has_selectable_tiled_panes() {
let vte_overlay = overlay.generate_overlay(size); let vte_overlay = overlay.generate_overlay(size);
tab.render(&mut output, Some(vte_overlay)); tab.render(&mut output, Some(vte_overlay))
.context(err_context)?;
} else { } else {
tabs_to_close.push(*tab_index); tabs_to_close.push(*tab_index);
} }
@ -793,13 +848,15 @@ impl Screen {
self.terminal_emulator_colors.clone(), self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(), self.terminal_emulator_color_codes.clone(),
); );
tab.apply_layout(layout, new_pids, tab_index, client_id); tab.apply_layout(layout, new_pids, tab_index, client_id)
.with_context(err_context)?;
if self.session_is_mirrored { if self.session_is_mirrored {
if let Some(active_tab) = self.get_active_tab_mut(client_id) { if let Some(active_tab) = self.get_active_tab_mut(client_id) {
let client_mode_infos_in_source_tab = active_tab.drain_connected_clients(None); let client_mode_infos_in_source_tab = active_tab.drain_connected_clients(None);
tab.add_multiple_clients(client_mode_infos_in_source_tab); tab.add_multiple_clients(client_mode_infos_in_source_tab)
.with_context(err_context)?;
if active_tab.has_no_connected_clients() { if active_tab.has_no_connected_clients() {
active_tab.visible(false); active_tab.visible(false).with_context(err_context)?;
} }
} }
let all_connected_clients: Vec<ClientId> = let all_connected_clients: Vec<ClientId> =
@ -810,14 +867,15 @@ impl Screen {
} else if let Some(active_tab) = self.get_active_tab_mut(client_id) { } else if let Some(active_tab) = self.get_active_tab_mut(client_id) {
let client_mode_info_in_source_tab = let client_mode_info_in_source_tab =
active_tab.drain_connected_clients(Some(vec![client_id])); active_tab.drain_connected_clients(Some(vec![client_id]));
tab.add_multiple_clients(client_mode_info_in_source_tab); tab.add_multiple_clients(client_mode_info_in_source_tab)
.with_context(err_context)?;
if active_tab.has_no_connected_clients() { if active_tab.has_no_connected_clients() {
active_tab.visible(false); active_tab.visible(false).with_context(err_context)?;
} }
self.update_client_tab_focus(client_id, tab_index); self.update_client_tab_focus(client_id, tab_index);
} }
tab.update_input_modes(); tab.update_input_modes().with_context(err_context)?;
tab.visible(true); tab.visible(true).with_context(err_context)?;
self.tabs.insert(tab_index, tab); self.tabs.insert(tab_index, tab);
if !self.active_tab_indices.contains_key(&client_id) { if !self.active_tab_indices.contains_key(&client_id) {
// this means this is a new client and we need to add it to our state properly // this means this is a new client and we need to add it to our state properly
@ -829,6 +887,10 @@ impl Screen {
} }
pub fn add_client(&mut self, client_id: ClientId) -> Result<()> { pub fn add_client(&mut self, client_id: ClientId) -> Result<()> {
let err_context = |tab_index| {
format!("failed to attach client {client_id} to tab with index {tab_index}")
};
let mut tab_history = vec![]; let mut tab_history = vec![];
if let Some((_first_client, first_tab_history)) = self.tab_history.iter().next() { if let Some((_first_client, first_tab_history)) = self.tab_history.iter().next() {
tab_history = first_tab_history.clone(); tab_history = first_tab_history.clone();
@ -843,7 +905,7 @@ impl Screen {
} else if let Some(tab_index) = self.tabs.keys().next() { } else if let Some(tab_index) = self.tabs.keys().next() {
tab_index.to_owned() tab_index.to_owned()
} else { } else {
panic!("Can't find a valid tab to attach client to!"); bail!("Can't find a valid tab to attach client to!");
}; };
self.active_tab_indices.insert(client_id, tab_index); self.active_tab_indices.insert(client_id, tab_index);
@ -851,18 +913,20 @@ impl Screen {
self.tab_history.insert(client_id, tab_history); self.tab_history.insert(client_id, tab_history);
self.tabs self.tabs
.get_mut(&tab_index) .get_mut(&tab_index)
.with_context(|| format!("Failed to attach client to tab with index {tab_index}"))? .with_context(|| err_context(tab_index))?
.add_client(client_id, None); .add_client(client_id, None)
Ok(()) .with_context(|| err_context(tab_index))
} }
pub fn remove_client(&mut self, client_id: ClientId) -> Result<()> { pub fn remove_client(&mut self, client_id: ClientId) -> Result<()> {
self.tabs.iter_mut().for_each(|(_, tab)| { let err_context = || format!("failed to remove client {client_id}");
for (_, tab) in self.tabs.iter_mut() {
tab.remove_client(client_id); tab.remove_client(client_id);
if tab.has_no_connected_clients() { if tab.has_no_connected_clients() {
tab.visible(false); tab.visible(false).with_context(err_context)?;
} }
}); }
if self.active_tab_indices.contains_key(&client_id) { if self.active_tab_indices.contains_key(&client_id) {
self.active_tab_indices.remove(&client_id); self.active_tab_indices.remove(&client_id);
} }
@ -870,8 +934,7 @@ impl Screen {
self.tab_history.remove(&client_id); self.tab_history.remove(&client_id);
} }
self.connected_clients.borrow_mut().remove(&client_id); self.connected_clients.borrow_mut().remove(&client_id);
self.update_tabs() self.update_tabs().with_context(err_context)
.with_context(|| format!("failed to remove client {client_id:?}"))
} }
pub fn update_tabs(&self) -> Result<()> { pub fn update_tabs(&self) -> Result<()> {
@ -908,12 +971,8 @@ impl Screen {
Some(*client_id), Some(*client_id),
Event::TabUpdate(tab_data), Event::TabUpdate(tab_data),
)) ))
.or_else(|err| { .to_anyhow()
let (_, error_context) = err.0; .context("failed to update tabs")?;
Err(anyhow!("failed to send data to plugins"))
.context(error_context)
.context("failed to update tabs")
})?;
} }
Ok(()) Ok(())
} }
@ -978,13 +1037,20 @@ impl Screen {
} }
} }
pub fn change_mode(&mut self, mode_info: ModeInfo, client_id: ClientId) { pub fn change_mode(&mut self, mode_info: ModeInfo, client_id: ClientId) -> Result<()> {
let previous_mode = self let previous_mode = self
.mode_info .mode_info
.get(&client_id) .get(&client_id)
.unwrap_or(&self.default_mode_info) .unwrap_or(&self.default_mode_info)
.mode; .mode;
let err_context = || {
format!(
"failed to change from mode '{:?}' to mode '{:?}' for client {client_id}",
previous_mode, mode_info.mode
)
};
// If we leave the Search-related modes, we need to clear all previous searches // If we leave the Search-related modes, we need to clear all previous searches
let search_related_modes = [InputMode::EnterSearch, InputMode::Search, InputMode::Scroll]; let search_related_modes = [InputMode::EnterSearch, InputMode::Search, InputMode::Scroll];
if search_related_modes.contains(&previous_mode) if search_related_modes.contains(&previous_mode)
@ -997,7 +1063,9 @@ impl Screen {
&& (mode_info.mode == InputMode::Normal || mode_info.mode == InputMode::Locked) && (mode_info.mode == InputMode::Normal || mode_info.mode == InputMode::Locked)
{ {
if let Some(active_tab) = self.get_active_tab_mut(client_id) { if let Some(active_tab) = self.get_active_tab_mut(client_id) {
active_tab.clear_active_terminal_scroll(client_id); active_tab
.clear_active_terminal_scroll(client_id)
.with_context(err_context)?;
} }
} }
@ -1023,16 +1091,27 @@ impl Screen {
tab.change_mode_info(mode_info.clone(), client_id); tab.change_mode_info(mode_info.clone(), client_id);
tab.mark_active_pane_for_rerender(client_id); tab.mark_active_pane_for_rerender(client_id);
} }
Ok(())
} }
pub fn change_mode_for_all_clients(&mut self, mode_info: ModeInfo) {
pub fn change_mode_for_all_clients(&mut self, mode_info: ModeInfo) -> Result<()> {
let err_context = || {
format!(
"failed to change input mode to {:?} for all clients",
mode_info.mode
)
};
let connected_client_ids: Vec<ClientId> = self.active_tab_indices.keys().copied().collect(); let connected_client_ids: Vec<ClientId> = self.active_tab_indices.keys().copied().collect();
for client_id in connected_client_ids { for client_id in connected_client_ids {
self.change_mode(mode_info.clone(), client_id); self.change_mode(mode_info.clone(), client_id)
.with_context(err_context)?;
if let Some(os_input) = &mut self.bus.os_input { if let Some(os_input) = &mut self.bus.os_input {
let _ = os_input let _ = os_input
.send_to_client(client_id, ServerToClientMsg::SwitchToMode(mode_info.mode)); .send_to_client(client_id, ServerToClientMsg::SwitchToMode(mode_info.mode));
} }
} }
Ok(())
} }
pub fn move_focus_left_or_previous_tab(&mut self, client_id: ClientId) -> Result<()> { pub fn move_focus_left_or_previous_tab(&mut self, client_id: ClientId) -> Result<()> {
let client_id = if self.get_active_tab(client_id).is_some() { let client_id = if self.get_active_tab(client_id).is_some() {
@ -1131,8 +1210,7 @@ pub(crate) fn screen_thread_main(
let (event, mut err_ctx) = screen let (event, mut err_ctx) = screen
.bus .bus
.recv() .recv()
.context("failed to receive event on channel") .context("failed to receive event on channel")?;
.fatal();
err_ctx.add_call(ContextType::Screen((&event).into())); err_ctx.add_call(ContextType::Screen((&event).into()));
match event { match event {
@ -1140,7 +1218,8 @@ pub(crate) fn screen_thread_main(
let all_tabs = screen.get_tabs_mut(); let all_tabs = screen.get_tabs_mut();
for tab in all_tabs.values_mut() { for tab in all_tabs.values_mut() {
if tab.has_terminal_pid(pid) { if tab.has_terminal_pid(pid) {
tab.handle_pty_bytes(pid, vte_bytes); tab.handle_pty_bytes(pid, vte_bytes)
.context("failed to process pty bytes")?;
break; break;
} }
} }
@ -1151,15 +1230,14 @@ pub(crate) fn screen_thread_main(
ScreenInstruction::NewPane(pid, client_or_tab_index) => { ScreenInstruction::NewPane(pid, client_or_tab_index) => {
match client_or_tab_index { match client_or_tab_index {
ClientOrTabIndex::ClientId(client_id) => { ClientOrTabIndex::ClientId(client_id) => {
active_tab_and_connected_client_id!( active_tab_and_connected_client_id!(screen, client_id, |tab: &mut Tab,
screen, client_id: ClientId| tab .new_pane(pid,
client_id, Some(client_id)),
|tab: &mut Tab, client_id: ClientId| tab.new_pane(pid, Some(client_id)) ?);
);
}, },
ClientOrTabIndex::TabIndex(tab_index) => { ClientOrTabIndex::TabIndex(tab_index) => {
if let Some(active_tab) = screen.tabs.get_mut(&tab_index) { if let Some(active_tab) = screen.tabs.get_mut(&tab_index) {
active_tab.new_pane(pid, None); active_tab.new_pane(pid, None)?;
} else { } else {
log::error!("Tab index not found: {:?}", tab_index); log::error!("Tab index not found: {:?}", tab_index);
} }
@ -1172,32 +1250,26 @@ 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()?;
}, },
ScreenInstruction::TogglePaneEmbedOrFloating(client_id) => { ScreenInstruction::TogglePaneEmbedOrFloating(client_id) => {
active_tab_and_connected_client_id!( active_tab_and_connected_client_id!(screen, client_id, |tab: &mut Tab, client_id: ClientId| tab
screen, .toggle_pane_embed_or_floating(client_id), ?);
client_id,
|tab: &mut Tab, client_id: ClientId| tab
.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_and_connected_client_id!( active_tab_and_connected_client_id!(screen, client_id, |tab: &mut Tab, client_id: ClientId| tab
screen, .toggle_floating_panes(client_id, default_shell), ?);
client_id,
|tab: &mut Tab, client_id: ClientId| tab
.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::ShowFloatingPanes(client_id) => { ScreenInstruction::ShowFloatingPanes(client_id) => {
@ -1208,6 +1280,7 @@ pub(crate) fn screen_thread_main(
); );
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::HideFloatingPanes(client_id) => { ScreenInstruction::HideFloatingPanes(client_id) => {
@ -1218,13 +1291,15 @@ pub(crate) fn screen_thread_main(
); );
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_and_connected_client_id!( active_tab_and_connected_client_id!(
screen, screen,
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab.horizontal_split(pid, client_id) |tab: &mut Tab, client_id: ClientId| tab.horizontal_split(pid, client_id),
?
); );
screen.unblock_input()?; screen.unblock_input()?;
screen.update_tabs()?; screen.update_tabs()?;
@ -1234,7 +1309,8 @@ pub(crate) fn screen_thread_main(
active_tab_and_connected_client_id!( active_tab_and_connected_client_id!(
screen, screen,
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab.vertical_split(pid, client_id) |tab: &mut Tab, client_id: ClientId| tab.vertical_split(pid, client_id),
?
); );
screen.unblock_input()?; screen.unblock_input()?;
screen.update_tabs()?; screen.update_tabs()?;
@ -1249,7 +1325,8 @@ pub(crate) fn screen_thread_main(
true => tab.write_to_terminals_on_current_tab(bytes), true => tab.write_to_terminals_on_current_tab(bytes),
false => tab.write_to_active_terminal(bytes, client_id), false => tab.write_to_active_terminal(bytes, client_id),
} }
} },
?
); );
}, },
ScreenInstruction::ResizeLeft(client_id) => { ScreenInstruction::ResizeLeft(client_id) => {
@ -1393,7 +1470,8 @@ pub(crate) fn screen_thread_main(
active_tab_and_connected_client_id!( active_tab_and_connected_client_id!(
screen, screen,
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab.edit_scrollback(client_id) |tab: &mut Tab, client_id: ClientId| tab.edit_scrollback(client_id),
?
); );
screen.render()?; screen.render()?;
}, },
@ -1456,7 +1534,7 @@ pub(crate) fn screen_thread_main(
screen, screen,
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab |tab: &mut Tab, client_id: ClientId| tab
.handle_scrollwheel_up(&point, 3, client_id) .handle_scrollwheel_up(&point, 3, client_id), ?
); );
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
@ -1465,7 +1543,7 @@ pub(crate) fn screen_thread_main(
active_tab_and_connected_client_id!( active_tab_and_connected_client_id!(
screen, screen,
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab.scroll_active_terminal_down(client_id) |tab: &mut Tab, client_id: ClientId| tab.scroll_active_terminal_down(client_id), ?
); );
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
@ -1475,7 +1553,7 @@ pub(crate) fn screen_thread_main(
screen, screen,
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab |tab: &mut Tab, client_id: ClientId| tab
.handle_scrollwheel_down(&point, 3, client_id) .handle_scrollwheel_down(&point, 3, client_id), ?
); );
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
@ -1485,7 +1563,7 @@ pub(crate) fn screen_thread_main(
screen, screen,
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab |tab: &mut Tab, client_id: ClientId| tab
.scroll_active_terminal_to_bottom(client_id) .scroll_active_terminal_to_bottom(client_id), ?
); );
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
@ -1505,7 +1583,7 @@ pub(crate) fn screen_thread_main(
screen, screen,
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab |tab: &mut Tab, client_id: ClientId| tab
.scroll_active_terminal_down_page(client_id) .scroll_active_terminal_down_page(client_id), ?
); );
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
@ -1525,7 +1603,7 @@ pub(crate) fn screen_thread_main(
screen, screen,
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab |tab: &mut Tab, client_id: ClientId| tab
.scroll_active_terminal_down_half_page(client_id) .scroll_active_terminal_down_half_page(client_id), ?
); );
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
@ -1535,7 +1613,7 @@ pub(crate) fn screen_thread_main(
screen, screen,
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab |tab: &mut Tab, client_id: ClientId| tab
.clear_active_terminal_scroll(client_id) .clear_active_terminal_scroll(client_id), ?
); );
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
@ -1544,7 +1622,7 @@ pub(crate) fn screen_thread_main(
active_tab_and_connected_client_id!( active_tab_and_connected_client_id!(
screen, screen,
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab.close_focused_pane(client_id) |tab: &mut Tab, client_id: ClientId| tab.close_focused_pane(client_id), ?
); );
screen.update_tabs()?; screen.update_tabs()?;
screen.render()?; screen.render()?;
@ -1585,7 +1663,7 @@ pub(crate) fn screen_thread_main(
active_tab_and_connected_client_id!( active_tab_and_connected_client_id!(
screen, screen,
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab.update_active_pane_name(c, client_id) |tab: &mut Tab, client_id: ClientId| tab.update_active_pane_name(c, client_id), ?
); );
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
@ -1594,7 +1672,7 @@ pub(crate) fn screen_thread_main(
active_tab_and_connected_client_id!( active_tab_and_connected_client_id!(
screen, screen,
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab.undo_active_rename_pane(client_id) |tab: &mut Tab, client_id: ClientId| tab.undo_active_rename_pane(client_id), ?
); );
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
@ -1641,7 +1719,10 @@ pub(crate) fn screen_thread_main(
ScreenInstruction::GoToTab(tab_index, client_id) => { ScreenInstruction::GoToTab(tab_index, client_id) => {
let client_id = if client_id.is_none() { let client_id = if client_id.is_none() {
None None
} else if screen.active_tab_indices.contains_key(&client_id.unwrap()) { } else if screen
.active_tab_indices
.contains_key(&client_id.expect("This is checked above"))
{
client_id client_id
} else { } else {
screen.active_tab_indices.keys().next().copied() screen.active_tab_indices.keys().next().copied()
@ -1679,12 +1760,12 @@ pub(crate) fn screen_thread_main(
screen.update_terminal_color_registers(color_registers); screen.update_terminal_color_registers(color_registers);
}, },
ScreenInstruction::ChangeMode(mode_info, client_id) => { ScreenInstruction::ChangeMode(mode_info, client_id) => {
screen.change_mode(mode_info, client_id); screen.change_mode(mode_info, client_id)?;
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
}, },
ScreenInstruction::ChangeModeForAllClients(mode_info) => { ScreenInstruction::ChangeModeForAllClients(mode_info) => {
screen.change_mode_for_all_clients(mode_info); screen.change_mode_for_all_clients(mode_info)?;
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
}, },
@ -1700,62 +1781,59 @@ pub(crate) fn screen_thread_main(
}, },
ScreenInstruction::LeftClick(point, client_id) => { ScreenInstruction::LeftClick(point, client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| tab active_tab!(screen, client_id, |tab: &mut Tab| tab
.handle_left_click(&point, client_id)); .handle_left_click(&point, client_id), ?);
screen.update_tabs()?; screen.update_tabs()?;
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
}, },
ScreenInstruction::RightClick(point, client_id) => { ScreenInstruction::RightClick(point, client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| tab active_tab!(screen, client_id, |tab: &mut Tab| tab
.handle_right_click(&point, client_id)); .handle_right_click(&point, client_id), ?);
screen.update_tabs()?; screen.update_tabs()?;
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
}, },
ScreenInstruction::MiddleClick(point, client_id) => { ScreenInstruction::MiddleClick(point, client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| tab active_tab!(screen, client_id, |tab: &mut Tab| tab
.handle_middle_click(&point, client_id)); .handle_middle_click(&point, client_id), ?);
screen.update_tabs()?; screen.update_tabs()?;
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
}, },
ScreenInstruction::LeftMouseRelease(point, client_id) => { ScreenInstruction::LeftMouseRelease(point, client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| tab active_tab!(screen, client_id, |tab: &mut Tab| tab
.handle_left_mouse_release(&point, client_id)); .handle_left_mouse_release(&point, client_id), ?);
screen.render()?; screen.render()?;
screen.unblock_input()?; screen.unblock_input()?;
}, },
ScreenInstruction::RightMouseRelease(point, client_id) => { ScreenInstruction::RightMouseRelease(point, client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| tab active_tab!(screen, client_id, |tab: &mut Tab| tab
.handle_right_mouse_release(&point, client_id)); .handle_right_mouse_release(&point, client_id), ?);
screen.render()?; screen.render()?;
}, },
ScreenInstruction::MiddleMouseRelease(point, client_id) => { ScreenInstruction::MiddleMouseRelease(point, client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| tab active_tab!(screen, client_id, |tab: &mut Tab| tab
.handle_middle_mouse_release(&point, client_id)); .handle_middle_mouse_release(&point, client_id), ?);
screen.render()?; screen.render()?;
}, },
ScreenInstruction::MouseHoldLeft(point, client_id) => { ScreenInstruction::MouseHoldLeft(point, client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| { active_tab!(screen, client_id, |tab: &mut Tab| tab
tab.handle_mouse_hold_left(&point, client_id); .handle_mouse_hold_left(&point, client_id), ?);
});
screen.render()?; screen.render()?;
}, },
ScreenInstruction::MouseHoldRight(point, client_id) => { ScreenInstruction::MouseHoldRight(point, client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| { active_tab!(screen, client_id, |tab: &mut Tab| tab
tab.handle_mouse_hold_right(&point, client_id); .handle_mouse_hold_right(&point, client_id), ?);
});
screen.render()?; screen.render()?;
}, },
ScreenInstruction::MouseHoldMiddle(point, client_id) => { ScreenInstruction::MouseHoldMiddle(point, client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| { active_tab!(screen, client_id, |tab: &mut Tab| tab
tab.handle_mouse_hold_middle(&point, client_id); .handle_mouse_hold_middle(&point, client_id), ?);
});
screen.render()?; screen.render()?;
}, },
ScreenInstruction::Copy(client_id) => { ScreenInstruction::Copy(client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| tab active_tab!(screen, client_id, |tab: &mut Tab| tab
.copy_selection(client_id)); .copy_selection(client_id), ?);
screen.render()?; screen.render()?;
}, },
ScreenInstruction::Exit => { ScreenInstruction::Exit => {
@ -1807,7 +1885,7 @@ pub(crate) fn screen_thread_main(
active_tab_and_connected_client_id!( active_tab_and_connected_client_id!(
screen, screen,
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| tab.update_search_term(c, client_id) |tab: &mut Tab, client_id: ClientId| tab.update_search_term(c, client_id), ?
); );
screen.render()?; screen.render()?;
}, },

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -746,7 +746,7 @@ fn switch_to_tab_with_fullscreen() {
new_tab(&mut screen, 1); new_tab(&mut screen, 1);
{ {
let active_tab = screen.get_active_tab_mut(1).unwrap(); let active_tab = screen.get_active_tab_mut(1).unwrap();
active_tab.new_pane(PaneId::Terminal(2), Some(1)); active_tab.new_pane(PaneId::Terminal(2), Some(1)).unwrap();
active_tab.toggle_active_pane_fullscreen(1); active_tab.toggle_active_pane_fullscreen(1);
} }
new_tab(&mut screen, 2); new_tab(&mut screen, 2);
@ -859,7 +859,7 @@ fn attach_after_first_tab_closed() {
new_tab(&mut screen, 1); new_tab(&mut screen, 1);
{ {
let active_tab = screen.get_active_tab_mut(1).unwrap(); let active_tab = screen.get_active_tab_mut(1).unwrap();
active_tab.new_pane(PaneId::Terminal(2), Some(1)); active_tab.new_pane(PaneId::Terminal(2), Some(1)).unwrap();
active_tab.toggle_active_pane_fullscreen(1); active_tab.toggle_active_pane_fullscreen(1);
} }
new_tab(&mut screen, 2); new_tab(&mut screen, 2);

View file

@ -9,7 +9,7 @@ license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
anyhow = "1.0.45" anyhow = { version = "1.0.45", features = ["backtrace"] }
backtrace = "0.3.55" backtrace = "0.3.55"
rmp-serde = "1.1.0" rmp-serde = "1.1.0"
clap = { version = "3.2.2", features = ["derive", "env"] } clap = { version = "3.2.2", features = ["derive", "env"] }

View file

@ -22,6 +22,8 @@ use thiserror::Error as ThisError;
pub mod prelude { pub mod prelude {
pub use super::FatalError; pub use super::FatalError;
pub use super::LoggableError; pub use super::LoggableError;
#[cfg(not(target_family = "wasm"))]
pub use super::ToAnyhow;
pub use anyhow::anyhow; pub use anyhow::anyhow;
pub use anyhow::bail; pub use anyhow::bail;
pub use anyhow::Context; pub use anyhow::Context;
@ -38,10 +40,18 @@ pub trait ErrorInstruction {
struct Panic(String); struct Panic(String);
impl Panic { impl Panic {
// We already capture a backtrace with `anyhow` using the `backtrace` crate in the background.
// The advantage is that this is the backtrace of the real errors source (i.e. where we first
// encountered the error and turned it into an `anyhow::Error`), whereas the backtrace recorded
// here is the backtrace leading to the call to any `panic`ing function. Since now we propagate
// errors up before `unwrap`ing them (e.g. in `zellij_server::screen::screen_thread_main`), the
// former is what we really want to diagnose.
// We still keep the second one around just in case the first backtrace isn't meaningful or
// non-existent in the first place (Which really shouldn't happen, but you never know).
fn show_backtrace(&self) -> String { fn show_backtrace(&self) -> String {
if let Ok(var) = std::env::var("RUST_BACKTRACE") { if let Ok(var) = std::env::var("RUST_BACKTRACE") {
if !var.is_empty() && var != "0" { if !var.is_empty() && var != "0" {
return format!("\n{:?}", backtrace::Backtrace::new()); return format!("\n\nPanic backtrace:\n{:?}", backtrace::Backtrace::new());
} }
} }
"".into() "".into()
@ -513,4 +523,34 @@ mod not_wasm {
Ok(()) Ok(())
} }
} }
/// Helper trait to convert error types that don't satisfy `anyhow`s trait requirements to
/// anyhow errors.
pub trait ToAnyhow<U> {
fn to_anyhow(self) -> crate::anyhow::Result<U>;
}
/// `SendError` doesn't satisfy `anyhow`s trait requirements due to `T` possibly being a
/// `PluginInstruction` type, which wraps an `mpsc::Send` and isn't `Sync`. Due to this, in turn,
/// the whole error type isn't `Sync` and doesn't work with `anyhow` (or pretty much any other
/// error handling crate).
///
/// Takes the `SendError` and creates an `anyhow` error type with the message that was sent
/// (formatted as string), attaching the [`ErrorContext`] as anyhow context to it.
impl<T: std::fmt::Debug, U> ToAnyhow<U>
for Result<U, crate::channels::SendError<(T, ErrorContext)>>
{
fn to_anyhow(self) -> crate::anyhow::Result<U> {
match self {
Ok(val) => crate::anyhow::Ok(val),
Err(e) => {
let (msg, context) = e.into_inner();
Err(
crate::anyhow::anyhow!("failed to send message to client: {:#?}", msg)
.context(context.to_string()),
)
},
}
}
}
} }