improve error handling in ui module (#1870)
* improve error handling in ui module * resolve problems in the review
This commit is contained in:
parent
668df6bbd7
commit
c5b1eb0a9e
12 changed files with 157 additions and 77 deletions
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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}");
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue