fix(panes): show visual error when failing to resize panes (#2036)
* fix(panes): show visual error when failing to resize pane vertically/horizontally * fix(resize): retry pane resize on rounding errors * fix(resize): proper error when resizing other panes into fixed panes * style(fmt): rustfmt
This commit is contained in:
parent
d1f50150f6
commit
1b5f3c52a4
12 changed files with 428 additions and 74 deletions
|
|
@ -148,18 +148,14 @@ impl InputHandler {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Ok((InputInstruction::StartedParsing, _error_context)) => {
|
Ok((InputInstruction::StartedParsing, _error_context)) => {
|
||||||
log::info!("sending done loading");
|
|
||||||
self.send_client_instructions
|
self.send_client_instructions
|
||||||
.send(ClientInstruction::StartedParsingStdinQuery)
|
.send(ClientInstruction::StartedParsingStdinQuery)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
log::info!("done sent done loading");
|
|
||||||
},
|
},
|
||||||
Ok((InputInstruction::DoneParsing, _error_context)) => {
|
Ok((InputInstruction::DoneParsing, _error_context)) => {
|
||||||
log::info!("sending done loading");
|
|
||||||
self.send_client_instructions
|
self.send_client_instructions
|
||||||
.send(ClientInstruction::DoneParsingStdinQuery)
|
.send(ClientInstruction::DoneParsingStdinQuery)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
log::info!("done sent done loading");
|
|
||||||
},
|
},
|
||||||
Err(err) => panic!("Encountered read error: {:?}", err),
|
Err(err) => panic!("Encountered read error: {:?}", err),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use crate::thread_bus::Bus;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum BackgroundJob {
|
pub enum BackgroundJob {
|
||||||
DisplayPaneError(PaneId, String),
|
DisplayPaneError(Vec<PaneId>, String),
|
||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,7 +34,7 @@ pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
|
||||||
err_ctx.add_call(ContextType::BackgroundJob((&event).into()));
|
err_ctx.add_call(ContextType::BackgroundJob((&event).into()));
|
||||||
let job = event.clone();
|
let job = event.clone();
|
||||||
match event {
|
match event {
|
||||||
BackgroundJob::DisplayPaneError(pane_id, text) => {
|
BackgroundJob::DisplayPaneError(pane_ids, text) => {
|
||||||
if job_already_running(job, &mut running_jobs) {
|
if job_already_running(job, &mut running_jobs) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -42,11 +42,14 @@ pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
|
||||||
let senders = bus.senders.clone();
|
let senders = bus.senders.clone();
|
||||||
async move {
|
async move {
|
||||||
let _ = senders.send_to_screen(
|
let _ = senders.send_to_screen(
|
||||||
ScreenInstruction::AddRedPaneFrameColorOverride(pane_id, Some(text)),
|
ScreenInstruction::AddRedPaneFrameColorOverride(
|
||||||
|
pane_ids.clone(),
|
||||||
|
Some(text),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
task::sleep(std::time::Duration::from_millis(FLASH_DURATION_MS)).await;
|
task::sleep(std::time::Duration::from_millis(FLASH_DURATION_MS)).await;
|
||||||
let _ = senders.send_to_screen(
|
let _ = senders.send_to_screen(
|
||||||
ScreenInstruction::ClearPaneFrameColorOverride(pane_id),
|
ScreenInstruction::ClearPaneFrameColorOverride(pane_ids),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ use zellij_utils::{
|
||||||
channels::SenderWithContext,
|
channels::SenderWithContext,
|
||||||
data::{Event, InputMode, Mouse, Palette, PaletteColor, Style},
|
data::{Event, InputMode, Mouse, Palette, PaletteColor, Style},
|
||||||
errors::prelude::*,
|
errors::prelude::*,
|
||||||
pane_size::{Dimension, PaneGeom},
|
pane_size::PaneGeom,
|
||||||
shared::make_terminal_title,
|
shared::make_terminal_title,
|
||||||
vte,
|
vte,
|
||||||
};
|
};
|
||||||
|
|
@ -341,28 +341,28 @@ impl Pane for PluginPane {
|
||||||
}
|
}
|
||||||
fn reduce_height(&mut self, percent: f64) {
|
fn reduce_height(&mut self, percent: f64) {
|
||||||
if let Some(p) = self.geom.rows.as_percent() {
|
if let Some(p) = self.geom.rows.as_percent() {
|
||||||
self.geom.rows = Dimension::percent(p - percent);
|
self.geom.rows.set_percent(p - percent);
|
||||||
self.resize_grids();
|
self.resize_grids();
|
||||||
self.set_should_render(true);
|
self.set_should_render(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn increase_height(&mut self, percent: f64) {
|
fn increase_height(&mut self, percent: f64) {
|
||||||
if let Some(p) = self.geom.rows.as_percent() {
|
if let Some(p) = self.geom.rows.as_percent() {
|
||||||
self.geom.rows = Dimension::percent(p + percent);
|
self.geom.rows.set_percent(p + percent);
|
||||||
self.resize_grids();
|
self.resize_grids();
|
||||||
self.set_should_render(true);
|
self.set_should_render(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn reduce_width(&mut self, percent: f64) {
|
fn reduce_width(&mut self, percent: f64) {
|
||||||
if let Some(p) = self.geom.cols.as_percent() {
|
if let Some(p) = self.geom.cols.as_percent() {
|
||||||
self.geom.cols = Dimension::percent(p - percent);
|
self.geom.cols.set_percent(p - percent);
|
||||||
self.resize_grids();
|
self.resize_grids();
|
||||||
self.set_should_render(true);
|
self.set_should_render(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn increase_width(&mut self, percent: f64) {
|
fn increase_width(&mut self, percent: f64) {
|
||||||
if let Some(p) = self.geom.cols.as_percent() {
|
if let Some(p) = self.geom.cols.as_percent() {
|
||||||
self.geom.cols = Dimension::percent(p + percent);
|
self.geom.cols.set_percent(p + percent);
|
||||||
self.resize_grids();
|
self.resize_grids();
|
||||||
self.set_should_render(true);
|
self.set_should_render(true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ use zellij_utils::pane_size::Offset;
|
||||||
use zellij_utils::{
|
use zellij_utils::{
|
||||||
data::{InputMode, Palette, PaletteColor, Style},
|
data::{InputMode, Palette, PaletteColor, Style},
|
||||||
errors::prelude::*,
|
errors::prelude::*,
|
||||||
|
pane_size::PaneGeom,
|
||||||
pane_size::SizeInPixels,
|
pane_size::SizeInPixels,
|
||||||
pane_size::{Dimension, PaneGeom},
|
|
||||||
position::Position,
|
position::Position,
|
||||||
shared::make_terminal_title,
|
shared::make_terminal_title,
|
||||||
vte,
|
vte,
|
||||||
|
|
@ -446,25 +446,25 @@ impl Pane for TerminalPane {
|
||||||
}
|
}
|
||||||
fn reduce_height(&mut self, percent: f64) {
|
fn reduce_height(&mut self, percent: f64) {
|
||||||
if let Some(p) = self.geom.rows.as_percent() {
|
if let Some(p) = self.geom.rows.as_percent() {
|
||||||
self.geom.rows = Dimension::percent(p - percent);
|
self.geom.rows.set_percent(p - percent);
|
||||||
self.set_should_render(true);
|
self.set_should_render(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn increase_height(&mut self, percent: f64) {
|
fn increase_height(&mut self, percent: f64) {
|
||||||
if let Some(p) = self.geom.rows.as_percent() {
|
if let Some(p) = self.geom.rows.as_percent() {
|
||||||
self.geom.rows = Dimension::percent(p + percent);
|
self.geom.rows.set_percent(p + percent);
|
||||||
self.set_should_render(true);
|
self.set_should_render(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn reduce_width(&mut self, percent: f64) {
|
fn reduce_width(&mut self, percent: f64) {
|
||||||
if let Some(p) = self.geom.cols.as_percent() {
|
if let Some(p) = self.geom.cols.as_percent() {
|
||||||
self.geom.cols = Dimension::percent(p - percent);
|
self.geom.cols.set_percent(p - percent);
|
||||||
self.set_should_render(true);
|
self.set_should_render(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn increase_width(&mut self, percent: f64) {
|
fn increase_width(&mut self, percent: f64) {
|
||||||
if let Some(p) = self.geom.cols.as_percent() {
|
if let Some(p) = self.geom.cols.as_percent() {
|
||||||
self.geom.cols = Dimension::percent(p + percent);
|
self.geom.cols.set_percent(p + percent);
|
||||||
self.set_should_render(true);
|
self.set_should_render(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -577,9 +577,38 @@ impl TiledPanes {
|
||||||
*self.viewport.borrow(),
|
*self.viewport.borrow(),
|
||||||
);
|
);
|
||||||
|
|
||||||
pane_grid
|
match pane_grid
|
||||||
.change_pane_size(&active_pane_id, strategy, (RESIZE_PERCENT, RESIZE_PERCENT))
|
.change_pane_size(&active_pane_id, strategy, (RESIZE_PERCENT, RESIZE_PERCENT))
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)
|
||||||
|
{
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(err) => match err.downcast_ref::<ZellijError>() {
|
||||||
|
Some(ZellijError::PaneSizeUnchanged) => {
|
||||||
|
// try once more with double the resize percent, but let's keep it at that
|
||||||
|
match pane_grid
|
||||||
|
.change_pane_size(
|
||||||
|
&active_pane_id,
|
||||||
|
strategy,
|
||||||
|
(RESIZE_PERCENT * 2.0, RESIZE_PERCENT * 2.0),
|
||||||
|
)
|
||||||
|
.with_context(err_context)
|
||||||
|
{
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(err) => match err.downcast_ref::<ZellijError>() {
|
||||||
|
Some(ZellijError::PaneSizeUnchanged) => {
|
||||||
|
Err::<(), _>(err).non_fatal()
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err(err);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err(err);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
for pane in self.panes.values_mut() {
|
for pane in self.panes.values_mut() {
|
||||||
resize_pty!(pane, self.os_api, self.senders).unwrap();
|
resize_pty!(pane, self.os_api, self.senders).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ impl<'a> PaneResizer<'a> {
|
||||||
let spans = self
|
let spans = self
|
||||||
.discretize_spans(grid, space)
|
.discretize_spans(grid, space)
|
||||||
.map_err(|err| anyhow!("{}", err))?;
|
.map_err(|err| anyhow!("{}", err))?;
|
||||||
self.apply_spans(spans);
|
self.apply_spans(spans)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,10 +127,13 @@ impl<'a> PaneResizer<'a> {
|
||||||
Ok(grid.into_iter().flatten().collect())
|
Ok(grid.into_iter().flatten().collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_spans(&mut self, spans: Vec<Span>) {
|
fn apply_spans(&mut self, spans: Vec<Span>) -> Result<()> {
|
||||||
|
let err_context = || format!("Failed to apply spans");
|
||||||
let mut panes = self.panes.borrow_mut();
|
let mut panes = self.panes.borrow_mut();
|
||||||
|
let mut geoms_changed = false;
|
||||||
for span in spans {
|
for span in spans {
|
||||||
let pane = panes.get_mut(&span.pid).unwrap();
|
let pane = panes.get_mut(&span.pid).unwrap();
|
||||||
|
let current_geom = pane.position_and_size();
|
||||||
let new_geom = match span.direction {
|
let new_geom = match span.direction {
|
||||||
SplitDirection::Horizontal => PaneGeom {
|
SplitDirection::Horizontal => PaneGeom {
|
||||||
x: span.pos,
|
x: span.pos,
|
||||||
|
|
@ -143,12 +146,26 @@ impl<'a> PaneResizer<'a> {
|
||||||
..pane.current_geom()
|
..pane.current_geom()
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
if new_geom.rows.as_usize() != current_geom.rows.as_usize()
|
||||||
|
|| new_geom.cols.as_usize() != current_geom.cols.as_usize()
|
||||||
|
{
|
||||||
|
geoms_changed = true;
|
||||||
|
}
|
||||||
if pane.geom_override().is_some() {
|
if pane.geom_override().is_some() {
|
||||||
pane.set_geom_override(new_geom);
|
pane.set_geom_override(new_geom);
|
||||||
} else {
|
} else {
|
||||||
pane.set_geom(new_geom);
|
pane.set_geom(new_geom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if geoms_changed {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
// probably a rounding issue - this might be considered an error depending on who
|
||||||
|
// called us - if it's an explicit resize operation, it's clearly an error (the user
|
||||||
|
// wanted to resize and doesn't care about percentage rounding), if it's resizing the
|
||||||
|
// terminal window as a whole, it might not be
|
||||||
|
Err(ZellijError::PaneSizeUnchanged).with_context(err_context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Functions like this should have unit tests!
|
// FIXME: Functions like this should have unit tests!
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,56 @@ impl<'a> TiledPaneGrid<'a> {
|
||||||
}
|
}
|
||||||
.is_fixed())
|
.is_fixed())
|
||||||
}
|
}
|
||||||
|
fn neighbor_pane_ids(&self, pane_id: &PaneId, direction: Direction) -> Result<Vec<PaneId>> {
|
||||||
|
let err_context = || format!("Failed to get neighboring panes");
|
||||||
|
// Shorthand
|
||||||
|
use Direction as Dir;
|
||||||
|
let mut neighbor_terminals = self
|
||||||
|
.pane_ids_directly_next_to(pane_id, &direction)
|
||||||
|
.with_context(err_context)?;
|
||||||
|
|
||||||
|
let neighbor_terminal_borders: HashSet<_> = if direction.is_horizontal() {
|
||||||
|
neighbor_terminals
|
||||||
|
.iter()
|
||||||
|
.map(|t| self.panes.borrow().get(t).unwrap().y())
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
neighbor_terminals
|
||||||
|
.iter()
|
||||||
|
.map(|t| self.panes.borrow().get(t).unwrap().x())
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only return those neighbors that are aligned and between pane borders
|
||||||
|
let (some_direction, other_direction) = match direction {
|
||||||
|
Dir::Left | Dir::Right => (Dir::Up, Dir::Down),
|
||||||
|
Dir::Down | Dir::Up => (Dir::Left, Dir::Right),
|
||||||
|
};
|
||||||
|
let (some_borders, _some_terminals) = self
|
||||||
|
.contiguous_panes_with_alignment(
|
||||||
|
pane_id,
|
||||||
|
&neighbor_terminal_borders,
|
||||||
|
&direction,
|
||||||
|
&some_direction,
|
||||||
|
)
|
||||||
|
.with_context(err_context)?;
|
||||||
|
let (other_borders, _other_terminals) = self
|
||||||
|
.contiguous_panes_with_alignment(
|
||||||
|
pane_id,
|
||||||
|
&neighbor_terminal_borders,
|
||||||
|
&direction,
|
||||||
|
&other_direction,
|
||||||
|
)
|
||||||
|
.with_context(err_context)?;
|
||||||
|
neighbor_terminals.retain(|t| {
|
||||||
|
if direction.is_horizontal() {
|
||||||
|
self.pane_is_between_horizontal_borders(t, some_borders, other_borders)
|
||||||
|
} else {
|
||||||
|
self.pane_is_between_vertical_borders(t, some_borders, other_borders)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(neighbor_terminals)
|
||||||
|
}
|
||||||
|
|
||||||
// Check if panes in the desired direction can be resized. Returns the maximum resize that's
|
// Check if panes in the desired direction can be resized. Returns the maximum resize that's
|
||||||
// possible (at most `change_by`).
|
// possible (at most `change_by`).
|
||||||
|
|
@ -100,12 +150,39 @@ impl<'a> TiledPaneGrid<'a> {
|
||||||
let err_context = || format!("failed to determine if pane {pane_id:?} can {strategy}");
|
let err_context = || format!("failed to determine if pane {pane_id:?} can {strategy}");
|
||||||
|
|
||||||
if let Some(direction) = strategy.direction {
|
if let Some(direction) = strategy.direction {
|
||||||
let mut pane_ids = self
|
if !self
|
||||||
.pane_ids_directly_next_to(pane_id, &direction)
|
.pane_is_flexible(direction.into(), pane_id)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
let pane_ids = match pane_id {
|
||||||
|
PaneId::Terminal(id) => vec![(*id, true)],
|
||||||
|
PaneId::Plugin(id) => vec![(*id, false)],
|
||||||
|
};
|
||||||
|
return Err(ZellijError::CantResizeFixedPanes { pane_ids })
|
||||||
|
.with_context(err_context);
|
||||||
|
}
|
||||||
|
let pane_ids = self
|
||||||
|
.neighbor_pane_ids(pane_id, direction)
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
pane_ids.retain(|id| self.pane_is_flexible(direction.into(), id).unwrap_or(false));
|
|
||||||
|
|
||||||
|
let fixed_panes: Vec<PaneId> = pane_ids
|
||||||
|
.iter()
|
||||||
|
.filter(|p| !self.pane_is_flexible(direction.into(), p).unwrap_or(false))
|
||||||
|
.copied()
|
||||||
|
.collect();
|
||||||
|
if !fixed_panes.is_empty() {
|
||||||
|
let mut pane_ids = vec![];
|
||||||
|
for fixed_pane in fixed_panes {
|
||||||
|
match fixed_pane {
|
||||||
|
PaneId::Terminal(id) => pane_ids.push((id, true)),
|
||||||
|
PaneId::Plugin(id) => pane_ids.push((id, false)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return Err(ZellijError::CantResizeFixedPanes { pane_ids })
|
||||||
|
.with_context(err_context);
|
||||||
|
}
|
||||||
if pane_ids.is_empty() {
|
if pane_ids.is_empty() {
|
||||||
|
// TODO: proper error
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,37 +237,56 @@ impl<'a> TiledPaneGrid<'a> {
|
||||||
change_by: (f64, f64),
|
change_by: (f64, f64),
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
let err_context = || format!("failed to {strategy} by {change_by:?} for pane {pane_id:?}");
|
let err_context = || format!("failed to {strategy} by {change_by:?} for pane {pane_id:?}");
|
||||||
|
|
||||||
// Shorthand
|
// Shorthand
|
||||||
use Direction as Dir;
|
use Direction as Dir;
|
||||||
|
let mut fixed_panes_blocking_resize = vec![];
|
||||||
|
|
||||||
// Default behavior is to only increase pane size, unless the direction being resized to is
|
// Default behavior is to only increase pane size, unless the direction being resized to is
|
||||||
// a boundary. In this case, decrease size from the other side (invert strategy)!
|
// a boundary. In this case, decrease size from the other side (invert strategy)!
|
||||||
let strategy = if strategy.resize_increase() // Only invert when increasing
|
let can_invert_strategy_if_needed = strategy.resize_increase() // Only invert when increasing
|
||||||
&& strategy.invert_on_boundaries // Only invert if configured to do so
|
&& strategy.invert_on_boundaries // Only invert if configured to do so
|
||||||
&& strategy.direction.is_some() // Only invert if there's a direction
|
&& strategy.direction.is_some(); // Only invert if there's a direction
|
||||||
&& strategy
|
|
||||||
.direction
|
|
||||||
.and_then(|direction| {
|
|
||||||
// Only invert if there are no neighbor IDs in the given direction
|
|
||||||
let mut neighbors = self.pane_ids_directly_next_to(pane_id, &direction)
|
|
||||||
.unwrap_or_default();
|
|
||||||
neighbors.retain(|pane_id| self.pane_is_flexible(direction.into(), pane_id).unwrap_or(false));
|
|
||||||
neighbors.is_empty().then_some(true)
|
|
||||||
})
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
strategy.invert()
|
|
||||||
} else {
|
|
||||||
*strategy
|
|
||||||
};
|
|
||||||
|
|
||||||
if !self
|
let can_change_pane_size_in_main_direction = self
|
||||||
.can_change_pane_size(pane_id, &strategy, change_by)
|
.can_change_pane_size(pane_id, &strategy, change_by)
|
||||||
.with_context(err_context)?
|
.unwrap_or_else(|err| {
|
||||||
|
if let Some(ZellijError::CantResizeFixedPanes { pane_ids }) =
|
||||||
|
err.downcast_ref::<ZellijError>()
|
||||||
|
{
|
||||||
|
fixed_panes_blocking_resize.append(&mut pane_ids.clone());
|
||||||
|
}
|
||||||
|
false
|
||||||
|
});
|
||||||
|
let can_change_pane_size_in_inverted_direction = if can_invert_strategy_if_needed {
|
||||||
|
let strategy = strategy.invert();
|
||||||
|
self.can_change_pane_size(pane_id, &strategy, change_by)
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
if let Some(ZellijError::CantResizeFixedPanes { pane_ids }) =
|
||||||
|
err.downcast_ref::<ZellijError>()
|
||||||
|
{
|
||||||
|
fixed_panes_blocking_resize.append(&mut pane_ids.clone());
|
||||||
|
}
|
||||||
|
false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
if strategy.direction.is_some()
|
||||||
|
&& !can_change_pane_size_in_main_direction
|
||||||
|
&& !can_change_pane_size_in_inverted_direction
|
||||||
{
|
{
|
||||||
// Resize not possible, quit
|
// we can't resize in any direction, not playing the blame game, but I'm looking at
|
||||||
return Ok(false);
|
// you: fixed_panes_blocking_resize
|
||||||
|
return Err(ZellijError::CantResizeFixedPanes {
|
||||||
|
pane_ids: fixed_panes_blocking_resize,
|
||||||
|
})
|
||||||
|
.with_context(err_context);
|
||||||
}
|
}
|
||||||
|
let strategy = if can_change_pane_size_in_main_direction {
|
||||||
|
*strategy
|
||||||
|
} else {
|
||||||
|
strategy.invert()
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(direction) = strategy.direction {
|
if let Some(direction) = strategy.direction {
|
||||||
let mut neighbor_terminals = self
|
let mut neighbor_terminals = self
|
||||||
|
|
@ -407,7 +503,7 @@ impl<'a> TiledPaneGrid<'a> {
|
||||||
};
|
};
|
||||||
if self
|
if self
|
||||||
.change_pane_size(pane_id, &new_strategy, change_by)
|
.change_pane_size(pane_id, &new_strategy, change_by)
|
||||||
.with_context(err_context)?
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -224,8 +224,8 @@ pub enum ScreenInstruction {
|
||||||
SearchToggleCaseSensitivity(ClientId),
|
SearchToggleCaseSensitivity(ClientId),
|
||||||
SearchToggleWholeWord(ClientId),
|
SearchToggleWholeWord(ClientId),
|
||||||
SearchToggleWrap(ClientId),
|
SearchToggleWrap(ClientId),
|
||||||
AddRedPaneFrameColorOverride(PaneId, Option<String>), // Option<String> => optional error text
|
AddRedPaneFrameColorOverride(Vec<PaneId>, Option<String>), // Option<String> => optional error text
|
||||||
ClearPaneFrameColorOverride(PaneId),
|
ClearPaneFrameColorOverride(Vec<PaneId>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&ScreenInstruction> for ScreenContext {
|
impl From<&ScreenInstruction> for ScreenContext {
|
||||||
|
|
@ -2120,22 +2120,26 @@ pub(crate) fn screen_thread_main(
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::AddRedPaneFrameColorOverride(pane_id, error_text) => {
|
ScreenInstruction::AddRedPaneFrameColorOverride(pane_ids, error_text) => {
|
||||||
let all_tabs = screen.get_tabs_mut();
|
let all_tabs = screen.get_tabs_mut();
|
||||||
for tab in all_tabs.values_mut() {
|
for pane_id in pane_ids {
|
||||||
if tab.has_pane_with_pid(&pane_id) {
|
for tab in all_tabs.values_mut() {
|
||||||
tab.add_red_pane_frame_color_override(pane_id, error_text);
|
if tab.has_pane_with_pid(&pane_id) {
|
||||||
break;
|
tab.add_red_pane_frame_color_override(pane_id, error_text.clone());
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::ClearPaneFrameColorOverride(pane_id) => {
|
ScreenInstruction::ClearPaneFrameColorOverride(pane_ids) => {
|
||||||
let all_tabs = screen.get_tabs_mut();
|
let all_tabs = screen.get_tabs_mut();
|
||||||
for tab in all_tabs.values_mut() {
|
for pane_id in pane_ids {
|
||||||
if tab.has_pane_with_pid(&pane_id) {
|
for tab in all_tabs.values_mut() {
|
||||||
tab.clear_pane_frame_color_override(pane_id);
|
if tab.has_pane_with_pid(&pane_id) {
|
||||||
break;
|
tab.clear_pane_frame_color_override(pane_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
screen.render()?;
|
screen.render()?;
|
||||||
|
|
|
||||||
|
|
@ -1048,7 +1048,7 @@ impl Tab {
|
||||||
if let Some(active_pane_id) = self.tiled_panes.get_active_pane_id(client_id) {
|
if let Some(active_pane_id) = self.tiled_panes.get_active_pane_id(client_id) {
|
||||||
self.senders
|
self.senders
|
||||||
.send_to_background_jobs(BackgroundJob::DisplayPaneError(
|
.send_to_background_jobs(BackgroundJob::DisplayPaneError(
|
||||||
active_pane_id,
|
vec![active_pane_id],
|
||||||
"TOO SMALL!".into(),
|
"TOO SMALL!".into(),
|
||||||
))
|
))
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
|
|
@ -1102,7 +1102,7 @@ impl Tab {
|
||||||
if let Some(active_pane_id) = self.tiled_panes.get_active_pane_id(client_id) {
|
if let Some(active_pane_id) = self.tiled_panes.get_active_pane_id(client_id) {
|
||||||
self.senders
|
self.senders
|
||||||
.send_to_background_jobs(BackgroundJob::DisplayPaneError(
|
.send_to_background_jobs(BackgroundJob::DisplayPaneError(
|
||||||
active_pane_id,
|
vec![active_pane_id],
|
||||||
"TOO SMALL!".into(),
|
"TOO SMALL!".into(),
|
||||||
))
|
))
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
|
|
@ -1630,6 +1630,7 @@ impl Tab {
|
||||||
self.should_clear_display_before_rendering = true;
|
self.should_clear_display_before_rendering = true;
|
||||||
}
|
}
|
||||||
pub fn resize(&mut self, client_id: ClientId, strategy: ResizeStrategy) -> Result<()> {
|
pub fn resize(&mut self, client_id: ClientId, strategy: ResizeStrategy) -> Result<()> {
|
||||||
|
let err_context = || format!("unable to resize pane");
|
||||||
if self.floating_panes.panes_are_visible() {
|
if self.floating_panes.panes_are_visible() {
|
||||||
let successfully_resized = self
|
let successfully_resized = self
|
||||||
.floating_panes
|
.floating_panes
|
||||||
|
|
@ -1639,9 +1640,28 @@ impl Tab {
|
||||||
self.set_force_render(); // we force render here to make sure the panes under the floating pane render and don't leave "garbage" in case of a decrease
|
self.set_force_render(); // we force render here to make sure the panes under the floating pane render and don't leave "garbage" in case of a decrease
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.tiled_panes
|
match self.tiled_panes.resize_active_pane(client_id, &strategy) {
|
||||||
.resize_active_pane(client_id, &strategy)
|
Ok(_) => {},
|
||||||
.unwrap();
|
Err(err) => match err.downcast_ref::<ZellijError>() {
|
||||||
|
Some(ZellijError::CantResizeFixedPanes { pane_ids }) => {
|
||||||
|
let mut pane_ids_to_error = vec![];
|
||||||
|
for (id, is_terminal) in pane_ids {
|
||||||
|
if *is_terminal {
|
||||||
|
pane_ids_to_error.push(PaneId::Terminal(*id));
|
||||||
|
} else {
|
||||||
|
pane_ids_to_error.push(PaneId::Plugin(*id));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
self.senders
|
||||||
|
.send_to_background_jobs(BackgroundJob::DisplayPaneError(
|
||||||
|
pane_ids_to_error,
|
||||||
|
"FIXED!".into(),
|
||||||
|
))
|
||||||
|
.with_context(err_context)?;
|
||||||
|
},
|
||||||
|
_ => Err::<(), _>(err).fatal(),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -231,15 +231,8 @@ fn create_new_tab_with_layout(size: Size, layout: PaneLayout) -> Tab {
|
||||||
for i in 0..layout.extract_run_instructions().len() {
|
for i in 0..layout.extract_run_instructions().len() {
|
||||||
new_terminal_ids.push((i as u32, None));
|
new_terminal_ids.push((i as u32, None));
|
||||||
}
|
}
|
||||||
tab.apply_layout(
|
tab.apply_layout(layout, new_terminal_ids, HashMap::new(), index, client_id)
|
||||||
layout,
|
.unwrap();
|
||||||
// vec![(1, None), (2, None)],
|
|
||||||
new_terminal_ids,
|
|
||||||
HashMap::new(),
|
|
||||||
index,
|
|
||||||
client_id,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
tab
|
tab
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5932,6 +5925,144 @@ pub fn cannot_resize_down_when_pane_below_is_at_minimum_height() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn cannot_resize_down_when_pane_has_fixed_rows() {
|
||||||
|
// ┌───────────┐ ┌───────────┐
|
||||||
|
// │███████████│ │███████████│
|
||||||
|
// ├───────────┤ ==resize=down==> ├───────────┤
|
||||||
|
// │ │ │ │
|
||||||
|
// └───────────┘ └───────────┘
|
||||||
|
// █ == focused pane
|
||||||
|
|
||||||
|
let size = Size {
|
||||||
|
cols: 121,
|
||||||
|
rows: 20,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut initial_layout = PaneLayout::default();
|
||||||
|
initial_layout.children_split_direction = SplitDirection::Horizontal;
|
||||||
|
let mut fixed_child = PaneLayout::default();
|
||||||
|
fixed_child.split_size = Some(SplitSize::Fixed(10));
|
||||||
|
initial_layout.children = vec![fixed_child, PaneLayout::default()];
|
||||||
|
let mut tab = create_new_tab_with_layout(size, initial_layout);
|
||||||
|
tab_resize_down(&mut tab, 1);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
tab.tiled_panes
|
||||||
|
.panes
|
||||||
|
.get(&PaneId::Terminal(0))
|
||||||
|
.unwrap()
|
||||||
|
.position_and_size()
|
||||||
|
.rows
|
||||||
|
.as_usize(),
|
||||||
|
10,
|
||||||
|
"pane 1 height stayed the same"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
tab.tiled_panes
|
||||||
|
.panes
|
||||||
|
.get(&PaneId::Terminal(1))
|
||||||
|
.unwrap()
|
||||||
|
.position_and_size()
|
||||||
|
.rows
|
||||||
|
.as_usize(),
|
||||||
|
10,
|
||||||
|
"pane 2 height stayed the same"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn cannot_resize_down_when_pane_below_has_fixed_rows() {
|
||||||
|
// ┌───────────┐ ┌───────────┐
|
||||||
|
// │███████████│ │███████████│
|
||||||
|
// ├───────────┤ ==resize=down==> ├───────────┤
|
||||||
|
// │ │ │ │
|
||||||
|
// └───────────┘ └───────────┘
|
||||||
|
// █ == focused pane
|
||||||
|
|
||||||
|
let size = Size {
|
||||||
|
cols: 121,
|
||||||
|
rows: 20,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut initial_layout = PaneLayout::default();
|
||||||
|
initial_layout.children_split_direction = SplitDirection::Horizontal;
|
||||||
|
let mut fixed_child = PaneLayout::default();
|
||||||
|
fixed_child.split_size = Some(SplitSize::Fixed(10));
|
||||||
|
initial_layout.children = vec![PaneLayout::default(), fixed_child];
|
||||||
|
let mut tab = create_new_tab_with_layout(size, initial_layout);
|
||||||
|
tab_resize_down(&mut tab, 1);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
tab.tiled_panes
|
||||||
|
.panes
|
||||||
|
.get(&PaneId::Terminal(0))
|
||||||
|
.unwrap()
|
||||||
|
.position_and_size()
|
||||||
|
.rows
|
||||||
|
.as_usize(),
|
||||||
|
10,
|
||||||
|
"pane 1 height stayed the same"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
tab.tiled_panes
|
||||||
|
.panes
|
||||||
|
.get(&PaneId::Terminal(1))
|
||||||
|
.unwrap()
|
||||||
|
.position_and_size()
|
||||||
|
.rows
|
||||||
|
.as_usize(),
|
||||||
|
10,
|
||||||
|
"pane 2 height stayed the same"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn cannot_resize_up_when_pane_below_has_fixed_rows() {
|
||||||
|
// ┌───────────┐ ┌───────────┐
|
||||||
|
// │███████████│ │███████████│
|
||||||
|
// ├───────────┤ ==resize=down==> ├───────────┤
|
||||||
|
// │ │ │ │
|
||||||
|
// └───────────┘ └───────────┘
|
||||||
|
// █ == focused pane
|
||||||
|
|
||||||
|
let size = Size {
|
||||||
|
cols: 121,
|
||||||
|
rows: 20,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut initial_layout = PaneLayout::default();
|
||||||
|
initial_layout.children_split_direction = SplitDirection::Horizontal;
|
||||||
|
let mut fixed_child = PaneLayout::default();
|
||||||
|
fixed_child.split_size = Some(SplitSize::Fixed(10));
|
||||||
|
initial_layout.children = vec![PaneLayout::default(), fixed_child];
|
||||||
|
let mut tab = create_new_tab_with_layout(size, initial_layout);
|
||||||
|
tab_resize_up(&mut tab, 1);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
tab.tiled_panes
|
||||||
|
.panes
|
||||||
|
.get(&PaneId::Terminal(0))
|
||||||
|
.unwrap()
|
||||||
|
.position_and_size()
|
||||||
|
.rows
|
||||||
|
.as_usize(),
|
||||||
|
10,
|
||||||
|
"pane 1 height stayed the same"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
tab.tiled_panes
|
||||||
|
.panes
|
||||||
|
.get(&PaneId::Terminal(1))
|
||||||
|
.unwrap()
|
||||||
|
.position_and_size()
|
||||||
|
.rows
|
||||||
|
.as_usize(),
|
||||||
|
10,
|
||||||
|
"pane 2 height stayed the same"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn resize_left_with_pane_to_the_left() {
|
pub fn resize_left_with_pane_to_the_left() {
|
||||||
// ┌─────┬─────┐ ┌───┬───────┐
|
// ┌─────┬─────┐ ┌───┬───────┐
|
||||||
|
|
@ -11217,6 +11348,52 @@ pub fn cannot_resize_right_when_pane_to_the_left_is_at_minimum_width() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn cannot_resize_right_when_pane_has_fixed_columns() {
|
||||||
|
// ┌──┬──┐ ┌──┬──┐
|
||||||
|
// │██│ │ │██│ │
|
||||||
|
// │██│ │ ==resize=right==> │██│ │
|
||||||
|
// │██│ │ │██│ │
|
||||||
|
// └──┴──┘ └──┴──┘
|
||||||
|
// █ == focused pane
|
||||||
|
|
||||||
|
let size = Size {
|
||||||
|
cols: 121,
|
||||||
|
rows: 20,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut initial_layout = PaneLayout::default();
|
||||||
|
initial_layout.children_split_direction = SplitDirection::Vertical;
|
||||||
|
let mut fixed_child = PaneLayout::default();
|
||||||
|
fixed_child.split_size = Some(SplitSize::Fixed(60));
|
||||||
|
initial_layout.children = vec![fixed_child, PaneLayout::default()];
|
||||||
|
let mut tab = create_new_tab_with_layout(size, initial_layout);
|
||||||
|
tab_resize_down(&mut tab, 1);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
tab.tiled_panes
|
||||||
|
.panes
|
||||||
|
.get(&PaneId::Terminal(0))
|
||||||
|
.unwrap()
|
||||||
|
.position_and_size()
|
||||||
|
.cols
|
||||||
|
.as_usize(),
|
||||||
|
60,
|
||||||
|
"pane 1 height stayed the same"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
tab.tiled_panes
|
||||||
|
.panes
|
||||||
|
.get(&PaneId::Terminal(1))
|
||||||
|
.unwrap()
|
||||||
|
.position_and_size()
|
||||||
|
.cols
|
||||||
|
.as_usize(),
|
||||||
|
61,
|
||||||
|
"pane 2 height stayed the same"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn resize_up_with_pane_above() {
|
pub fn resize_up_with_pane_above() {
|
||||||
// ┌───────────┐ ┌───────────┐
|
// ┌───────────┐ ┌───────────┐
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,14 @@ open an issue on GitHub:
|
||||||
source: anyhow::Error,
|
source: anyhow::Error,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// this is a temporary hack until we're able to merge custom errors from within the various
|
||||||
|
// crates themselves without having to move their payload types here
|
||||||
|
#[error("Cannot resize fixed panes")]
|
||||||
|
CantResizeFixedPanes { pane_ids: Vec<(u32, bool)> }, // bool: 0 => terminal_pane, 1 =>
|
||||||
|
// plugin_pane
|
||||||
|
#[error("Pane size remains unchanged")]
|
||||||
|
PaneSizeUnchanged,
|
||||||
|
|
||||||
#[error("an error occured")]
|
#[error("an error occured")]
|
||||||
GenericError { source: anyhow::Error },
|
GenericError { source: anyhow::Error },
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,10 @@ impl Dimension {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_percent(&mut self, percent: f64) {
|
||||||
|
self.constraint = Constraint::Percent(percent);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_inner(&mut self, inner: usize) {
|
pub fn set_inner(&mut self, inner: usize) {
|
||||||
self.inner = inner;
|
self.inner = inner;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue