improve error handling in ui module (#1870)

* improve error handling in ui module

* resolve problems in the review
This commit is contained in:
哇呜哇呜呀咦耶 2022-10-28 22:15:16 +08:00 committed by GitHub
parent 668df6bbd7
commit c5b1eb0a9e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 157 additions and 77 deletions

View file

@ -17,6 +17,7 @@ use std::rc::Rc;
use std::time::Instant; use std::time::Instant;
use zellij_utils::{ use zellij_utils::{
data::{ModeInfo, Style}, data::{ModeInfo, Style},
errors::prelude::*,
input::command::RunCommand, input::command::RunCommand,
pane_size::{Offset, PaneGeom, Size, Viewport}, pane_size::{Offset, PaneGeom, Size, Viewport},
}; };
@ -234,7 +235,8 @@ impl FloatingPanes {
resize_pty!(pane, os_api); resize_pty!(pane, os_api);
} }
} }
pub fn render(&mut self, output: &mut Output) { pub fn render(&mut self, output: &mut Output) -> Result<()> {
let err_context = || "failed to render output";
let connected_clients: Vec<ClientId> = let connected_clients: Vec<ClientId> =
{ self.connected_clients.borrow().iter().copied().collect() }; { self.connected_clients.borrow().iter().copied().collect() };
let mut floating_panes: Vec<_> = self.panes.iter_mut().collect(); let mut floating_panes: Vec<_> = self.panes.iter_mut().collect();
@ -242,8 +244,16 @@ impl FloatingPanes {
self.z_indices self.z_indices
.iter() .iter()
.position(|id| id == *a_id) .position(|id| id == *a_id)
.unwrap() .with_context(err_context)
.cmp(&self.z_indices.iter().position(|id| id == *b_id).unwrap()) .fatal()
.cmp(
&self
.z_indices
.iter()
.position(|id| id == *b_id)
.with_context(err_context)
.fatal(),
)
}); });
for (z_index, (kind, pane)) in floating_panes.iter_mut().enumerate() { for (z_index, (kind, pane)) in floating_panes.iter_mut().enumerate() {
@ -266,23 +276,27 @@ impl FloatingPanes {
.get(client_id) .get(client_id)
.unwrap_or(&self.default_mode_info) .unwrap_or(&self.default_mode_info)
.mode; .mode;
pane_contents_and_ui.render_pane_frame( pane_contents_and_ui
*client_id, .render_pane_frame(*client_id, client_mode, self.session_is_mirrored)
client_mode, .with_context(err_context)?;
self.session_is_mirrored,
);
if let PaneId::Plugin(..) = kind { if let PaneId::Plugin(..) = kind {
pane_contents_and_ui.render_pane_contents_for_client(*client_id); pane_contents_and_ui
.render_pane_contents_for_client(*client_id)
.with_context(err_context)?;
} }
// this is done for panes that don't have their own cursor (eg. panes of // this is done for panes that don't have their own cursor (eg. panes of
// another user) // another user)
pane_contents_and_ui.render_fake_cursor_if_needed(*client_id); pane_contents_and_ui
.render_fake_cursor_if_needed(*client_id)
.with_context(err_context)?;
} }
if let PaneId::Terminal(..) = kind { if let PaneId::Terminal(..) = kind {
pane_contents_and_ui pane_contents_and_ui
.render_pane_contents_to_multiple_clients(connected_clients.iter().copied()); .render_pane_contents_to_multiple_clients(connected_clients.iter().copied())
.with_context(err_context)?;
} }
} }
Ok(())
} }
pub fn resize(&mut self, new_screen_size: Size) { pub fn resize(&mut self, new_screen_size: Size) {
let display_area = *self.display_area.borrow(); let display_area = *self.display_area.borrow();

View file

@ -231,10 +231,10 @@ impl Pane for PluginPane {
_client_id: ClientId, _client_id: ClientId,
frame_params: FrameParams, frame_params: FrameParams,
input_mode: InputMode, input_mode: InputMode,
) -> Option<(Vec<CharacterChunk>, Option<String>)> { ) -> Result<Option<(Vec<CharacterChunk>, Option<String>)>> {
// FIXME: This is a hack that assumes all fixed-size panes are borderless. This // FIXME: This is a hack that assumes all fixed-size panes are borderless. This
// will eventually need fixing! // will eventually need fixing!
if self.frame && !(self.geom.rows.is_fixed() || self.geom.cols.is_fixed()) { let res = if self.frame && !(self.geom.rows.is_fixed() || self.geom.cols.is_fixed()) {
let pane_title = if self.pane_name.is_empty() let pane_title = if self.pane_name.is_empty()
&& input_mode == InputMode::RenamePane && input_mode == InputMode::RenamePane
&& frame_params.is_main_client && frame_params.is_main_client
@ -251,10 +251,15 @@ impl Pane for PluginPane {
pane_title, pane_title,
frame_params, frame_params,
); );
Some(frame.render()) Some(
frame
.render()
.with_context(|| format!("failed to render frame for client {_client_id}"))?,
)
} else { } else {
None None
} };
Ok(res)
} }
fn render_fake_cursor( fn render_fake_cursor(
&mut self, &mut self,

View file

@ -347,7 +347,8 @@ impl Pane for TerminalPane {
client_id: ClientId, client_id: ClientId,
frame_params: FrameParams, frame_params: FrameParams,
input_mode: InputMode, input_mode: InputMode,
) -> Option<(Vec<CharacterChunk>, Option<String>)> { ) -> Result<Option<(Vec<CharacterChunk>, Option<String>)>> {
let err_context = || format!("failed to render frame for client {client_id}");
// TODO: remove the cursor stuff from here // TODO: remove the cursor stuff from here
let pane_title = if self.pane_name.is_empty() let pane_title = if self.pane_name.is_empty()
&& input_mode == InputMode::RenamePane && input_mode == InputMode::RenamePane
@ -398,12 +399,12 @@ impl Pane for TerminalPane {
frame.add_exit_status(exit_status.as_ref().copied()); frame.add_exit_status(exit_status.as_ref().copied());
} }
match self.frame.get(&client_id) { let res = match self.frame.get(&client_id) {
// TODO: use and_then or something? // TODO: use and_then or something?
Some(last_frame) => { Some(last_frame) => {
if &frame != last_frame { if &frame != last_frame {
if !self.borderless { if !self.borderless {
let frame_output = frame.render(); let frame_output = frame.render().with_context(err_context)?;
self.frame.insert(client_id, frame); self.frame.insert(client_id, frame);
Some(frame_output) Some(frame_output)
} else { } else {
@ -415,14 +416,15 @@ impl Pane for TerminalPane {
}, },
None => { None => {
if !self.borderless { if !self.borderless {
let frame_output = frame.render(); let frame_output = frame.render().with_context(err_context)?;
self.frame.insert(client_id, frame); self.frame.insert(client_id, frame);
Some(frame_output) Some(frame_output)
} else { } else {
None None
} }
}, },
} };
Ok(res)
} }
fn render_fake_cursor( fn render_fake_cursor(
&mut self, &mut self,

View file

@ -12,6 +12,7 @@ use std::cell::RefCell;
use std::collections::{BTreeMap, HashMap, HashSet}; use std::collections::{BTreeMap, HashMap, HashSet};
use std::rc::Rc; use std::rc::Rc;
use std::time::Instant; use std::time::Instant;
use zellij_utils::errors::prelude::*;
use zellij_utils::{ use zellij_utils::{
data::{ModeInfo, Style}, data::{ModeInfo, Style},
input::command::RunCommand, input::command::RunCommand,
@ -378,7 +379,8 @@ impl TiledPanes {
pub fn has_panes(&self) -> bool { pub fn has_panes(&self) -> bool {
!self.panes.is_empty() !self.panes.is_empty()
} }
pub fn render(&mut self, output: &mut Output, floating_panes_are_visible: bool) { pub fn render(&mut self, output: &mut Output, floating_panes_are_visible: bool) -> Result<()> {
let err_context = || "failed to render tiled panes";
let connected_clients: Vec<ClientId> = let connected_clients: Vec<ClientId> =
{ self.connected_clients.borrow().iter().copied().collect() }; { self.connected_clients.borrow().iter().copied().collect() };
let multiple_users_exist_in_session = { self.connected_clients_in_app.borrow().len() > 1 }; let multiple_users_exist_in_session = { self.connected_clients_in_app.borrow().len() > 1 };
@ -409,15 +411,17 @@ impl TiledPanes {
.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 render tiled panes for client {client_id}");
if let PaneId::Plugin(..) = kind { if let PaneId::Plugin(..) = kind {
pane_contents_and_ui.render_pane_contents_for_client(*client_id); pane_contents_and_ui
.render_pane_contents_for_client(*client_id)
.with_context(err_context)?;
} }
if self.draw_pane_frames { if self.draw_pane_frames {
pane_contents_and_ui.render_pane_frame( pane_contents_and_ui
*client_id, .render_pane_frame(*client_id, client_mode, self.session_is_mirrored)
client_mode, .with_context(err_context)?;
self.session_is_mirrored,
);
} else { } else {
let boundaries = client_id_to_boundaries let boundaries = client_id_to_boundaries
.entry(*client_id) .entry(*client_id)
@ -432,20 +436,27 @@ impl TiledPanes {
pane_contents_and_ui.render_terminal_title_if_needed(*client_id, client_mode); pane_contents_and_ui.render_terminal_title_if_needed(*client_id, client_mode);
// this is done for panes that don't have their own cursor (eg. panes of // this is done for panes that don't have their own cursor (eg. panes of
// another user) // another user)
pane_contents_and_ui.render_fake_cursor_if_needed(*client_id); pane_contents_and_ui
.render_fake_cursor_if_needed(*client_id)
.with_context(err_context)?;
} }
if let PaneId::Terminal(..) = kind { if let PaneId::Terminal(..) = kind {
pane_contents_and_ui.render_pane_contents_to_multiple_clients( pane_contents_and_ui
connected_clients.iter().copied(), .render_pane_contents_to_multiple_clients(connected_clients.iter().copied())
); .with_context(err_context)?;
} }
} }
} }
// render boundaries if needed // render boundaries if needed
for (client_id, boundaries) in &mut client_id_to_boundaries { for (client_id, boundaries) in &mut client_id_to_boundaries {
// TODO: add some conditional rendering here so this isn't rendered for every character // TODO: add some conditional rendering here so this isn't rendered for every character
output.add_character_chunks_to_client(*client_id, boundaries.render(), None); output.add_character_chunks_to_client(
*client_id,
boundaries.render().with_context(err_context)?,
None,
);
} }
Ok(())
} }
pub fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> { pub fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> {
self.panes.iter() self.panes.iter()

View file

@ -452,7 +452,7 @@ impl Pty {
.bus .bus
.os_input .os_input
.as_mut() .as_mut()
.unwrap() .ok_or_else(|| SpawnTerminalError::GenericSpawnError("os input is none"))?
.spawn_terminal(terminal_action, quit_cb, self.default_editor.clone())?; .spawn_terminal(terminal_action, quit_cb, self.default_editor.clone())?;
let terminal_bytes = task::spawn({ let terminal_bytes = task::spawn({
let err_context = || format!("failed to run async task for terminal {terminal_id}"); let err_context = || format!("failed to run async task for terminal {terminal_id}");

View file

@ -736,7 +736,7 @@ impl Screen {
let overlay = self.overlay.clone(); let overlay = self.overlay.clone();
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).context(err_context)?;
tab.render(&mut output, Some(vte_overlay)) tab.render(&mut output, Some(vte_overlay))
.context(err_context)?; .context(err_context)?;
} else { } else {

View file

@ -148,7 +148,7 @@ pub trait Pane {
client_id: ClientId, client_id: ClientId,
frame_params: FrameParams, frame_params: FrameParams,
input_mode: InputMode, input_mode: InputMode,
) -> Option<(Vec<CharacterChunk>, Option<String>)>; // TODO: better ) -> Result<Option<(Vec<CharacterChunk>, Option<String>)>>; // TODO: better
fn render_fake_cursor( fn render_fake_cursor(
&mut self, &mut self,
cursor_color: PaletteColor, cursor_color: PaletteColor,
@ -1329,9 +1329,12 @@ impl Tab {
self.hide_cursor_and_clear_display_as_needed(output); self.hide_cursor_and_clear_display_as_needed(output);
self.tiled_panes self.tiled_panes
.render(output, self.floating_panes.panes_are_visible()); .render(output, self.floating_panes.panes_are_visible())
.with_context(err_context)?;
if self.floating_panes.panes_are_visible() && self.floating_panes.has_active_panes() { if self.floating_panes.panes_are_visible() && self.floating_panes.has_active_panes() {
self.floating_panes.render(output); self.floating_panes
.render(output)
.with_context(err_context)?;
} }
// FIXME: Once clients can be distinguished // FIXME: Once clients can be distinguished

View file

@ -5,6 +5,7 @@ use crate::panes::terminal_character::{TerminalCharacter, EMPTY_TERMINAL_CHARACT
use crate::tab::Pane; use crate::tab::Pane;
use ansi_term::Colour::{Fixed, RGB}; use ansi_term::Colour::{Fixed, RGB};
use std::collections::HashMap; use std::collections::HashMap;
use zellij_utils::errors::prelude::*;
use zellij_utils::{data::PaletteColor, shared::colors}; use zellij_utils::{data::PaletteColor, shared::colors};
use std::fmt::{Display, Error, Formatter}; use std::fmt::{Display, Error, Formatter};
@ -47,18 +48,29 @@ impl BoundarySymbol {
self.color = color; self.color = color;
*self *self
} }
pub fn as_terminal_character(&self) -> TerminalCharacter { pub fn as_terminal_character(&self) -> Result<TerminalCharacter> {
if self.invisible { let tc = if self.invisible {
EMPTY_TERMINAL_CHARACTER EMPTY_TERMINAL_CHARACTER
} else { } else {
let character = self.boundary_type.chars().next().unwrap(); let character = self
.boundary_type
.chars()
.next()
.context("no boundary symbols defined")
.with_context(|| {
format!(
"failed to convert boundary symbol {} into terminal character",
self.boundary_type
)
})?;
TerminalCharacter { TerminalCharacter {
character, character,
width: 1, width: 1,
styles: RESET_STYLES styles: RESET_STYLES
.foreground(self.color.map(|palette_color| palette_color.into())), .foreground(self.color.map(|palette_color| palette_color.into())),
} }
} };
Ok(tc)
} }
} }
@ -528,16 +540,18 @@ impl Boundaries {
} }
} }
} }
pub fn render(&self) -> Vec<CharacterChunk> { pub fn render(&self) -> Result<Vec<CharacterChunk>> {
let mut character_chunks = vec![]; let mut character_chunks = vec![];
for (coordinates, boundary_character) in &self.boundary_characters { for (coordinates, boundary_character) in &self.boundary_characters {
character_chunks.push(CharacterChunk::new( character_chunks.push(CharacterChunk::new(
vec![boundary_character.as_terminal_character()], vec![boundary_character
.as_terminal_character()
.context("failed to render as terminal character")?],
coordinates.x, coordinates.x,
coordinates.y, coordinates.y,
)); ));
} }
character_chunks Ok(character_chunks)
} }
fn rect_right_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool { fn rect_right_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool {
rect.x() + rect.cols() < self.viewport.cols rect.x() + rect.cols() < self.viewport.cols

View file

@ -9,6 +9,7 @@
pub mod prompt; pub mod prompt;
use crate::ServerInstruction; use crate::ServerInstruction;
use zellij_utils::errors::prelude::*;
use zellij_utils::pane_size::Size; use zellij_utils::pane_size::Size;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -19,7 +20,7 @@ pub struct Overlay {
pub trait Overlayable { pub trait Overlayable {
/// Generates vte_output that can be passed into /// Generates vte_output that can be passed into
/// the `render()` function /// the `render()` function
fn generate_overlay(&self, size: Size) -> String; fn generate_overlay(&self, size: Size) -> Result<String>;
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -28,9 +29,11 @@ pub enum OverlayType {
} }
impl Overlayable for OverlayType { impl Overlayable for OverlayType {
fn generate_overlay(&self, size: Size) -> String { fn generate_overlay(&self, size: Size) -> Result<String> {
match &self { match &self {
OverlayType::Prompt(prompt) => prompt.generate_overlay(size), OverlayType::Prompt(prompt) => prompt
.generate_overlay(size)
.context("failed to generate VTE output from overlay type"),
} }
} }
} }
@ -44,15 +47,17 @@ pub struct OverlayWindow {
} }
impl Overlayable for OverlayWindow { impl Overlayable for OverlayWindow {
fn generate_overlay(&self, size: Size) -> String { fn generate_overlay(&self, size: Size) -> Result<String> {
let mut output = String::new(); let mut output = String::new();
//let clear_display = "\u{1b}[2J"; //let clear_display = "\u{1b}[2J";
//output.push_str(&clear_display); //output.push_str(&clear_display);
for overlay in &self.overlay_stack { for overlay in &self.overlay_stack {
let vte_output = overlay.generate_overlay(size); let vte_output = overlay
.generate_overlay(size)
.context("failed to generate VTE output from overlay window")?;
output.push_str(&vte_output); output.push_str(&vte_output);
} }
output Ok(output)
} }
} }
@ -70,8 +75,10 @@ impl Overlay {
} }
impl Overlayable for Overlay { impl Overlayable for Overlay {
fn generate_overlay(&self, size: Size) -> String { fn generate_overlay(&self, size: Size) -> Result<String> {
self.overlay_type.generate_overlay(size) self.overlay_type
.generate_overlay(size)
.context("failed to generate VTE output from overlay")
} }
} }

View file

@ -2,6 +2,7 @@ use zellij_utils::pane_size::Size;
use super::{Overlay, OverlayType, Overlayable}; use super::{Overlay, OverlayType, Overlayable};
use crate::{ClientId, ServerInstruction}; use crate::{ClientId, ServerInstruction};
use zellij_utils::errors::prelude::*;
use std::fmt::Write; use std::fmt::Write;
@ -33,7 +34,7 @@ impl Prompt {
} }
impl Overlayable for Prompt { impl Overlayable for Prompt {
fn generate_overlay(&self, size: Size) -> String { fn generate_overlay(&self, size: Size) -> Result<String> {
let mut output = String::new(); let mut output = String::new();
let rows = size.rows; let rows = size.rows;
let mut vte_output = self.message.clone(); let mut vte_output = self.message.clone();
@ -46,9 +47,9 @@ impl Overlayable for Prompt {
x + 1, x + 1,
h, h,
) )
.unwrap(); .context("failed to generate VTE output from prompt")?;
} }
output Ok(output)
} }
} }

View file

@ -3,6 +3,7 @@ use crate::panes::{AnsiCode, CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_
use crate::ui::boundaries::boundary_type; use crate::ui::boundaries::boundary_type;
use crate::ClientId; use crate::ClientId;
use zellij_utils::data::{client_id_to_colors, PaletteColor, Style}; use zellij_utils::data::{client_id_to_colors, PaletteColor, Style};
use zellij_utils::errors::prelude::*;
use zellij_utils::pane_size::Viewport; use zellij_utils::pane_size::Viewport;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
@ -600,24 +601,26 @@ impl PaneFrame {
_ => self.empty_title_line(), _ => self.empty_title_line(),
} }
} }
fn render_title(&self) -> Vec<TerminalCharacter> { fn render_title(&self) -> Result<Vec<TerminalCharacter>> {
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
self.render_title_middle(total_title_length) self.render_title_middle(total_title_length)
.map(|(middle, middle_length)| self.title_line_with_middle(middle, &middle_length)) .map(|(middle, middle_length)| self.title_line_with_middle(middle, &middle_length))
.or_else(|| Some(self.title_line_without_middle())) .or_else(|| Some(self.title_line_without_middle()))
.unwrap() .with_context(|| format!("failed to render title '{}'", self.title))
} }
fn render_held_undertitle(&self) -> Vec<TerminalCharacter> { fn render_held_undertitle(&self) -> Result<Vec<TerminalCharacter>> {
let max_undertitle_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners let max_undertitle_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
let exit_status = self.exit_status.unwrap(); // unwrap is safe because we only call this if let exit_status = self
.exit_status
.with_context(|| format!("failed to render command pane status '{}'", self.title))?; // unwrap is safe because we only call this if
let (mut first_part, first_part_len) = self.first_held_title_part_full(exit_status); let (mut first_part, first_part_len) = self.first_held_title_part_full(exit_status);
let mut left_boundary = let mut left_boundary =
foreground_color(self.get_corner(boundary_type::BOTTOM_LEFT), self.color); foreground_color(self.get_corner(boundary_type::BOTTOM_LEFT), self.color);
let mut right_boundary = let mut right_boundary =
foreground_color(self.get_corner(boundary_type::BOTTOM_RIGHT), self.color); foreground_color(self.get_corner(boundary_type::BOTTOM_RIGHT), self.color);
if self.is_main_client { let res = if self.is_main_client {
let (mut second_part, second_part_len) = self.second_held_title_part_full(); let (mut second_part, second_part_len) = self.second_held_title_part_full();
let full_text_len = first_part_len + second_part_len; let full_text_len = first_part_len + second_part_len;
if full_text_len <= max_undertitle_length { if full_text_len <= max_undertitle_length {
@ -665,14 +668,16 @@ impl PaneFrame {
} else { } else {
self.empty_undertitle(max_undertitle_length) self.empty_undertitle(max_undertitle_length)
} }
} };
Ok(res)
} }
pub fn render(&self) -> (Vec<CharacterChunk>, Option<String>) { pub fn render(&self) -> Result<(Vec<CharacterChunk>, Option<String>)> {
let err_context = || "failed to render pane frame";
let mut character_chunks = vec![]; let mut character_chunks = vec![];
for row in 0..self.geom.rows { for row in 0..self.geom.rows {
if row == 0 { if row == 0 {
// top row // top row
let title = self.render_title(); let title = self.render_title().with_context(err_context)?;
let x = self.geom.x; let x = self.geom.x;
let y = self.geom.y + row; let y = self.geom.y + row;
character_chunks.push(CharacterChunk::new(title, x, y)); character_chunks.push(CharacterChunk::new(title, x, y));
@ -681,7 +686,11 @@ impl PaneFrame {
if self.exit_status.is_some() { if self.exit_status.is_some() {
let x = self.geom.x; let x = self.geom.x;
let y = self.geom.y + row; let y = self.geom.y + row;
character_chunks.push(CharacterChunk::new(self.render_held_undertitle(), x, y)); character_chunks.push(CharacterChunk::new(
self.render_held_undertitle().with_context(err_context)?,
x,
y,
));
} else { } else {
let mut bottom_row = vec![]; let mut bottom_row = vec![];
for col in 0..self.geom.cols { for col in 0..self.geom.cols {
@ -716,7 +725,7 @@ impl PaneFrame {
character_chunks.push(CharacterChunk::new(boundary_character_right, x, y)); character_chunks.push(CharacterChunk::new(boundary_character_right, x, y));
} }
} }
(character_chunks, None) Ok((character_chunks, None))
} }
fn first_held_title_part_full( fn first_held_title_part_full(
&self, &self,

View file

@ -8,6 +8,7 @@ use std::collections::HashMap;
use zellij_utils::data::{ use zellij_utils::data::{
client_id_to_colors, single_client_color, InputMode, PaletteColor, Style, client_id_to_colors, single_client_color, InputMode, PaletteColor, Style,
}; };
use zellij_utils::errors::prelude::*;
pub struct PaneContentsAndUi<'a> { pub struct PaneContentsAndUi<'a> {
pane: &'a mut Box<dyn Pane>, pane: &'a mut Box<dyn Pane>,
output: &'a mut Output, output: &'a mut Output,
@ -44,9 +45,11 @@ impl<'a> PaneContentsAndUi<'a> {
pub fn render_pane_contents_to_multiple_clients( pub fn render_pane_contents_to_multiple_clients(
&mut self, &mut self,
clients: impl Iterator<Item = ClientId>, clients: impl Iterator<Item = ClientId>,
) { ) -> Result<()> {
if let Some((character_chunks, raw_vte_output, sixel_image_chunks)) = if let Some((character_chunks, raw_vte_output, sixel_image_chunks)) = self
self.pane.render(None).unwrap() .pane
.render(None)
.context("failed to render pane contents to multiple clients")?
{ {
let clients: Vec<ClientId> = clients.collect(); let clients: Vec<ClientId> = clients.collect();
self.output.add_character_chunks_to_multiple_clients( self.output.add_character_chunks_to_multiple_clients(
@ -71,10 +74,13 @@ impl<'a> PaneContentsAndUi<'a> {
); );
} }
} }
Ok(())
} }
pub fn render_pane_contents_for_client(&mut self, client_id: ClientId) { pub fn render_pane_contents_for_client(&mut self, client_id: ClientId) -> Result<()> {
if let Some((character_chunks, raw_vte_output, sixel_image_chunks)) = if let Some((character_chunks, raw_vte_output, sixel_image_chunks)) = self
self.pane.render(Some(client_id)).unwrap() .pane
.render(Some(client_id))
.with_context(|| format!("failed to render pane contents for client {client_id}"))?
{ {
self.output self.output
.add_character_chunks_to_client(client_id, character_chunks, self.z_index); .add_character_chunks_to_client(client_id, character_chunks, self.z_index);
@ -95,8 +101,9 @@ impl<'a> PaneContentsAndUi<'a> {
); );
} }
} }
Ok(())
} }
pub fn render_fake_cursor_if_needed(&mut self, client_id: ClientId) { pub fn render_fake_cursor_if_needed(&mut self, client_id: ClientId) -> Result<()> {
let pane_focused_for_client_id = self.focused_clients.contains(&client_id); let pane_focused_for_client_id = self.focused_clients.contains(&client_id);
let pane_focused_for_different_client = self let pane_focused_for_different_client = self
.focused_clients .focused_clients
@ -109,7 +116,9 @@ impl<'a> PaneContentsAndUi<'a> {
.focused_clients .focused_clients
.iter() .iter()
.find(|&&c_id| c_id != client_id) .find(|&&c_id| c_id != client_id)
.unwrap(); .with_context(|| {
format!("failed to render fake cursor if needed for client {client_id}")
})?;
if let Some(colors) = client_id_to_colors(*fake_cursor_client_id, self.style.colors) { if let Some(colors) = client_id_to_colors(*fake_cursor_client_id, self.style.colors) {
if let Some(vte_output) = self.pane.render_fake_cursor(colors.0, colors.1) { if let Some(vte_output) = self.pane.render_fake_cursor(colors.0, colors.1) {
self.output.add_post_vte_instruction_to_client( self.output.add_post_vte_instruction_to_client(
@ -124,6 +133,7 @@ impl<'a> PaneContentsAndUi<'a> {
} }
} }
} }
Ok(())
} }
pub fn render_terminal_title_if_needed(&mut self, client_id: ClientId, client_mode: InputMode) { pub fn render_terminal_title_if_needed(&mut self, client_id: ClientId, client_mode: InputMode) {
if !self.focused_clients.contains(&client_id) { if !self.focused_clients.contains(&client_id) {
@ -138,7 +148,8 @@ impl<'a> PaneContentsAndUi<'a> {
client_id: ClientId, client_id: ClientId,
client_mode: InputMode, client_mode: InputMode,
session_is_mirrored: bool, session_is_mirrored: bool,
) { ) -> Result<()> {
let err_context = || format!("failed to render pane frame for client {client_id}");
let pane_focused_for_client_id = self.focused_clients.contains(&client_id); let pane_focused_for_client_id = self.focused_clients.contains(&client_id);
let other_focused_clients: Vec<ClientId> = self let other_focused_clients: Vec<ClientId> = self
.focused_clients .focused_clients
@ -152,7 +163,7 @@ impl<'a> PaneContentsAndUi<'a> {
let focused_client = if pane_focused_for_client_id { let focused_client = if pane_focused_for_client_id {
Some(client_id) Some(client_id)
} else if pane_focused_for_differet_client { } else if pane_focused_for_differet_client {
Some(*other_focused_clients.first().unwrap()) Some(*other_focused_clients.first().with_context(err_context)?)
} else { } else {
None None
}; };
@ -175,8 +186,10 @@ impl<'a> PaneContentsAndUi<'a> {
other_cursors_exist_in_session: self.multiple_users_exist_in_session, other_cursors_exist_in_session: self.multiple_users_exist_in_session,
} }
}; };
if let Some((frame_terminal_characters, vte_output)) = if let Some((frame_terminal_characters, vte_output)) = self
self.pane.render_frame(client_id, frame_params, client_mode) .pane
.render_frame(client_id, frame_params, client_mode)
.with_context(err_context)?
{ {
self.output.add_character_chunks_to_client( self.output.add_character_chunks_to_client(
client_id, client_id,
@ -188,6 +201,7 @@ impl<'a> PaneContentsAndUi<'a> {
.add_post_vte_instruction_to_client(client_id, &vte_output); .add_post_vte_instruction_to_client(client_id, &vte_output);
} }
} }
Ok(())
} }
pub fn render_pane_boundaries( pub fn render_pane_boundaries(
&self, &self,