Getting back to where we started... (Buggy Resizing)

This commit is contained in:
Brooks J Rady 2021-05-29 23:12:11 +01:00
parent fe299325eb
commit f2c5ee44f7
15 changed files with 327 additions and 594 deletions

7
Cargo.lock generated
View File

@ -271,6 +271,12 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba"
[[package]]
name = "cassowary"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.67" version = "1.0.67"
@ -2345,6 +2351,7 @@ version = "0.13.0"
dependencies = [ dependencies = [
"ansi_term 0.12.1", "ansi_term 0.12.1",
"async-trait", "async-trait",
"cassowary",
"daemonize", "daemonize",
"insta", "insta",
"serde_json", "serde_json",

View File

@ -135,7 +135,7 @@ impl ZellijPlugin for State {
fn load(&mut self) { fn load(&mut self) {
set_selectable(false); set_selectable(false);
set_invisible_borders(true); set_invisible_borders(true);
set_max_height(2); set_fixed_height(2);
subscribe(&[EventType::ModeUpdate]); subscribe(&[EventType::ModeUpdate]);
} }

View File

@ -26,7 +26,7 @@ impl ZellijPlugin for State {
fn load(&mut self) { fn load(&mut self) {
set_selectable(false); set_selectable(false);
set_invisible_borders(true); set_invisible_borders(true);
set_max_height(1); set_fixed_height(1);
subscribe(&[EventType::TabUpdate, EventType::ModeUpdate]); subscribe(&[EventType::TabUpdate, EventType::ModeUpdate]);
} }

View File

@ -16,6 +16,7 @@ serde_json = "1.0"
unicode-width = "0.1.8" unicode-width = "0.1.8"
wasmer = "1.0.0" wasmer = "1.0.0"
wasmer-wasi = "1.0.0" wasmer-wasi = "1.0.0"
cassowary = "0.3.0"
zellij-utils = { path = "../zellij-utils/", version = "0.13.0" } zellij-utils = { path = "../zellij-utils/", version = "0.13.0" }
[dev-dependencies] [dev-dependencies]

View File

@ -112,6 +112,8 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
daemonize::Daemonize::new() daemonize::Daemonize::new()
.working_directory(std::env::current_dir().unwrap()) .working_directory(std::env::current_dir().unwrap())
.umask(0o077) .umask(0o077)
// FIXME: My cherished `dbg!` was broken, so this is a hack to bring it back
.stderr(std::fs::File::create("dbg.log").unwrap())
.start() .start()
.expect("could not daemonize the server process"); .expect("could not daemonize the server process");

View File

@ -16,8 +16,6 @@ pub(crate) struct PluginPane {
pub position_and_size: PositionAndSize, pub position_and_size: PositionAndSize,
pub position_and_size_override: Option<PositionAndSize>, pub position_and_size_override: Option<PositionAndSize>,
pub send_plugin_instructions: SenderWithContext<PluginInstruction>, pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
pub max_height: Option<usize>,
pub max_width: Option<usize>,
pub active_at: Instant, pub active_at: Instant,
} }
@ -35,8 +33,6 @@ impl PluginPane {
position_and_size, position_and_size,
position_and_size_override: None, position_and_size_override: None,
send_plugin_instructions, send_plugin_instructions,
max_height: None,
max_width: None,
active_at: Instant::now(), active_at: Instant::now(),
} }
} }
@ -95,7 +91,9 @@ impl Pane for PluginPane {
fn adjust_input_to_terminal(&self, _input_bytes: Vec<u8>) -> Vec<u8> { fn adjust_input_to_terminal(&self, _input_bytes: Vec<u8>) -> Vec<u8> {
unimplemented!() // FIXME: Shouldn't need this implmented? unimplemented!() // FIXME: Shouldn't need this implmented?
} }
fn position_and_size(&self) -> PositionAndSize {
self.position_and_size
}
fn position_and_size_override(&self) -> Option<PositionAndSize> { fn position_and_size_override(&self) -> Option<PositionAndSize> {
self.position_and_size_override self.position_and_size_override
} }
@ -114,11 +112,13 @@ impl Pane for PluginPane {
fn set_invisible_borders(&mut self, invisible_borders: bool) { fn set_invisible_borders(&mut self, invisible_borders: bool) {
self.invisible_borders = invisible_borders; self.invisible_borders = invisible_borders;
} }
fn set_max_height(&mut self, max_height: usize) { fn set_fixed_height(&mut self, fixed_height: usize) {
self.max_height = Some(max_height); self.position_and_size.rows = fixed_height;
self.position_and_size.rows_fixed = true;
} }
fn set_max_width(&mut self, max_width: usize) { fn set_fixed_width(&mut self, fixed_width: usize) {
self.max_width = Some(max_width); self.position_and_size.columns = fixed_width;
self.position_and_size.cols_fixed = true;
} }
fn render(&mut self) -> Option<String> { fn render(&mut self) -> Option<String> {
// if self.should_render { // if self.should_render {
@ -204,11 +204,21 @@ impl Pane for PluginPane {
fn clear_scroll(&mut self) { fn clear_scroll(&mut self) {
unimplemented!() unimplemented!()
} }
// FIXME: This need to be reevaluated and deleted if possible.
// `max` doesn't make sense when things are fixed...
fn max_height(&self) -> Option<usize> { fn max_height(&self) -> Option<usize> {
self.max_height if self.position_and_size.rows_fixed {
Some(self.position_and_size.rows)
} else {
None
}
} }
fn max_width(&self) -> Option<usize> { fn max_width(&self) -> Option<usize> {
self.max_width if self.position_and_size.cols_fixed {
Some(self.position_and_size.columns)
} else {
None
}
} }
fn invisible_borders(&self) -> bool { fn invisible_borders(&self) -> bool {
self.invisible_borders self.invisible_borders

View File

@ -27,8 +27,6 @@ pub struct TerminalPane {
pub selectable: bool, pub selectable: bool,
pub position_and_size: PositionAndSize, pub position_and_size: PositionAndSize,
pub position_and_size_override: Option<PositionAndSize>, pub position_and_size_override: Option<PositionAndSize>,
pub max_height: Option<usize>,
pub max_width: Option<usize>,
pub active_at: Instant, pub active_at: Instant,
pub colors: Palette, pub colors: Palette,
vte_parser: vte::Parser, vte_parser: vte::Parser,
@ -52,8 +50,7 @@ impl Pane for TerminalPane {
self.reflow_lines(); self.reflow_lines();
} }
fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) { fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) {
self.position_and_size.columns = position_and_size.columns; self.position_and_size = *position_and_size;
self.position_and_size.rows = position_and_size.rows;
self.reflow_lines(); self.reflow_lines();
} }
fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize) { fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize) {
@ -119,7 +116,9 @@ impl Pane for TerminalPane {
}; };
input_bytes input_bytes
} }
fn position_and_size(&self) -> PositionAndSize {
self.position_and_size
}
fn position_and_size_override(&self) -> Option<PositionAndSize> { fn position_and_size_override(&self) -> Option<PositionAndSize> {
self.position_and_size_override self.position_and_size_override
} }
@ -135,21 +134,17 @@ impl Pane for TerminalPane {
fn set_selectable(&mut self, selectable: bool) { fn set_selectable(&mut self, selectable: bool) {
self.selectable = selectable; self.selectable = selectable;
} }
fn set_max_height(&mut self, max_height: usize) { fn set_fixed_height(&mut self, fixed_height: usize) {
self.max_height = Some(max_height); self.position_and_size.rows = fixed_height;
self.position_and_size.rows_fixed = true;
} }
fn set_max_width(&mut self, max_width: usize) { fn set_fixed_width(&mut self, fixed_width: usize) {
self.max_width = Some(max_width); self.position_and_size.columns = fixed_width;
self.position_and_size.cols_fixed = true;
} }
fn set_invisible_borders(&mut self, _invisible_borders: bool) { fn set_invisible_borders(&mut self, _invisible_borders: bool) {
unimplemented!(); unimplemented!();
} }
fn max_height(&self) -> Option<usize> {
self.max_height
}
fn max_width(&self) -> Option<usize> {
self.max_width
}
fn render(&mut self) -> Option<String> { fn render(&mut self) -> Option<String> {
if self.should_render() { if self.should_render() {
let mut vte_output = String::new(); let mut vte_output = String::new();
@ -294,8 +289,6 @@ impl TerminalPane {
selectable: true, selectable: true,
position_and_size, position_and_size,
position_and_size_override: None, position_and_size_override: None,
max_height: None,
max_width: None,
vte_parser: vte::Parser::new(), vte_parser: vte::Parser::new(),
active_at: Instant::now(), active_at: Instant::now(),
colors: palette, colors: palette,

View File

@ -55,7 +55,8 @@ pub(crate) enum ScreenInstruction {
CloseFocusedPane, CloseFocusedPane,
ToggleActiveTerminalFullscreen, ToggleActiveTerminalFullscreen,
SetSelectable(PaneId, bool), SetSelectable(PaneId, bool),
SetMaxHeight(PaneId, usize), SetFixedHeight(PaneId, usize),
SetFixedWidth(PaneId, usize),
SetInvisibleBorders(PaneId, bool), SetInvisibleBorders(PaneId, bool),
ClosePane(PaneId), ClosePane(PaneId),
ApplyLayout(Layout, Vec<RawFd>), ApplyLayout(Layout, Vec<RawFd>),
@ -106,7 +107,8 @@ impl From<&ScreenInstruction> for ScreenContext {
} }
ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable, ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable,
ScreenInstruction::SetInvisibleBorders(..) => ScreenContext::SetInvisibleBorders, ScreenInstruction::SetInvisibleBorders(..) => ScreenContext::SetInvisibleBorders,
ScreenInstruction::SetMaxHeight(..) => ScreenContext::SetMaxHeight, ScreenInstruction::SetFixedHeight(..) => ScreenContext::SetFixedHeight,
ScreenInstruction::SetFixedWidth(..) => ScreenContext::SetFixedWidth,
ScreenInstruction::ClosePane(_) => ScreenContext::ClosePane, ScreenInstruction::ClosePane(_) => ScreenContext::ClosePane,
ScreenInstruction::ApplyLayout(..) => ScreenContext::ApplyLayout, ScreenInstruction::ApplyLayout(..) => ScreenContext::ApplyLayout,
ScreenInstruction::NewTab(_) => ScreenContext::NewTab, ScreenInstruction::NewTab(_) => ScreenContext::NewTab,
@ -574,11 +576,17 @@ pub(crate) fn screen_thread_main(
.unwrap() .unwrap()
.set_pane_selectable(id, selectable); .set_pane_selectable(id, selectable);
} }
ScreenInstruction::SetMaxHeight(id, max_height) => { ScreenInstruction::SetFixedHeight(id, fixed_height) => {
screen screen
.get_active_tab_mut() .get_active_tab_mut()
.unwrap() .unwrap()
.set_pane_max_height(id, max_height); .set_pane_fixed_height(id, fixed_height);
}
ScreenInstruction::SetFixedWidth(id, fixed_width) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_fixed_width(id, fixed_width);
} }
ScreenInstruction::SetInvisibleBorders(id, invisible_borders) => { ScreenInstruction::SetInvisibleBorders(id, invisible_borders) => {
screen screen

View File

@ -104,15 +104,15 @@ pub trait Pane {
fn handle_pty_bytes(&mut self, bytes: VteBytes); fn handle_pty_bytes(&mut self, bytes: VteBytes);
fn cursor_coordinates(&self) -> Option<(usize, usize)>; fn cursor_coordinates(&self) -> Option<(usize, usize)>;
fn adjust_input_to_terminal(&self, input_bytes: Vec<u8>) -> Vec<u8>; fn adjust_input_to_terminal(&self, input_bytes: Vec<u8>) -> Vec<u8>;
fn position_and_size(&self) -> PositionAndSize;
fn position_and_size_override(&self) -> Option<PositionAndSize>; fn position_and_size_override(&self) -> Option<PositionAndSize>;
fn should_render(&self) -> bool; fn should_render(&self) -> bool;
fn set_should_render(&mut self, should_render: bool); fn set_should_render(&mut self, should_render: bool);
fn selectable(&self) -> bool; fn selectable(&self) -> bool;
fn set_selectable(&mut self, selectable: bool); fn set_selectable(&mut self, selectable: bool);
fn set_invisible_borders(&mut self, invisible_borders: bool); fn set_invisible_borders(&mut self, invisible_borders: bool);
fn set_max_height(&mut self, max_height: usize); fn set_fixed_height(&mut self, fixed_height: usize);
fn set_max_width(&mut self, max_width: usize); fn set_fixed_width(&mut self, fixed_width: usize);
fn render(&mut self) -> Option<String>; fn render(&mut self) -> Option<String>;
fn pid(&self) -> PaneId; fn pid(&self) -> PaneId;
fn reduce_height_down(&mut self, count: usize); fn reduce_height_down(&mut self, count: usize);
@ -178,15 +178,6 @@ pub trait Pane {
std::cmp::min(self.x() + self.columns(), other.x() + other.columns()) std::cmp::min(self.x() + self.columns(), other.x() + other.columns())
- std::cmp::max(self.x(), other.x()) - std::cmp::max(self.x(), other.x())
} }
fn position_and_size(&self) -> PositionAndSize {
PositionAndSize {
x: self.x(),
y: self.y(),
columns: self.columns(),
rows: self.rows(),
..Default::default()
}
}
fn can_increase_height_by(&self, increase_by: usize) -> bool { fn can_increase_height_by(&self, increase_by: usize) -> bool {
self.max_height() self.max_height()
.map(|max_height| self.rows() + increase_by <= max_height) .map(|max_height| self.rows() + increase_by <= max_height)
@ -294,12 +285,6 @@ impl Tab {
match positions_and_size.next() { match positions_and_size.next() {
Some((_, position_and_size)) => { Some((_, position_and_size)) => {
terminal_pane.reset_size_and_position_override(); terminal_pane.reset_size_and_position_override();
if let Some(max_rows) = position_and_size.max_rows {
terminal_pane.set_max_height(max_rows);
}
if let Some(max_columns) = position_and_size.max_columns {
terminal_pane.set_max_width(max_columns);
}
terminal_pane.change_pos_and_size(&position_and_size); terminal_pane.change_pos_and_size(&position_and_size);
self.os_api.set_terminal_size_using_fd( self.os_api.set_terminal_size_using_fd(
*pid, *pid,
@ -317,24 +302,18 @@ impl Tab {
} }
let mut new_pids = new_pids.iter(); let mut new_pids = new_pids.iter();
for (layout, position_and_size) in positions_and_size { for (layout, position_and_size) in positions_and_size {
// Just a regular terminal // A plugin pane
if let Some(plugin) = &layout.plugin { if let Some(plugin) = &layout.plugin {
let (pid_tx, pid_rx) = channel(); let (pid_tx, pid_rx) = channel();
self.senders self.senders
.send_to_plugin(PluginInstruction::Load(pid_tx, plugin.clone())) .send_to_plugin(PluginInstruction::Load(pid_tx, plugin.clone()))
.unwrap(); .unwrap();
let pid = pid_rx.recv().unwrap(); let pid = pid_rx.recv().unwrap();
let mut new_plugin = PluginPane::new( let new_plugin = PluginPane::new(
pid, pid,
*position_and_size, *position_and_size,
self.senders.to_plugin.as_ref().unwrap().clone(), self.senders.to_plugin.as_ref().unwrap().clone(),
); );
if let Some(max_rows) = position_and_size.max_rows {
new_plugin.set_max_height(max_rows);
}
if let Some(max_columns) = position_and_size.max_columns {
new_plugin.set_max_width(max_columns);
}
self.panes.insert(PaneId::Plugin(pid), Box::new(new_plugin)); self.panes.insert(PaneId::Plugin(pid), Box::new(new_plugin));
// Send an initial mode update to the newly loaded plugin only! // Send an initial mode update to the newly loaded plugin only!
self.senders self.senders
@ -2121,9 +2100,14 @@ impl Tab {
pane.set_invisible_borders(invisible_borders); pane.set_invisible_borders(invisible_borders);
} }
} }
pub fn set_pane_max_height(&mut self, id: PaneId, max_height: usize) { pub fn set_pane_fixed_height(&mut self, id: PaneId, fixed_height: usize) {
if let Some(pane) = self.panes.get_mut(&id) { if let Some(pane) = self.panes.get_mut(&id) {
pane.set_max_height(max_height); pane.set_fixed_height(fixed_height);
}
}
pub fn set_pane_fixed_width(&mut self, id: PaneId, fixed_width: usize) {
if let Some(pane) = self.panes.get_mut(&id) {
pane.set_fixed_width(fixed_width);
} }
} }
pub fn close_pane(&mut self, id: PaneId) { pub fn close_pane(&mut self, id: PaneId) {

View File

@ -19,17 +19,14 @@ fn split_space_to_parts_vertically(
// First fit in the parameterized sizes // First fit in the parameterized sizes
for size in sizes { for size in sizes {
let (columns, max_columns) = match size { let columns = match size {
Some(SplitSize::Percent(percent)) => { Some(SplitSize::Percent(percent)) => {
((max_width as f32 * (percent as f32 / 100.0)) as usize, None) (max_width as f32 * (percent as f32 / 100.0)) as usize
} // TODO: round properly } // TODO: round properly
Some(SplitSize::Fixed(size)) => (size as usize, Some(size as usize)), Some(SplitSize::Fixed(size)) => size as usize,
None => { None => {
parts_to_grow.push(current_x_position); parts_to_grow.push(current_x_position);
( 1 // This is grown later on
1, // This is grown later on
None,
)
} }
}; };
split_parts.push(PositionAndSize { split_parts.push(PositionAndSize {
@ -37,7 +34,6 @@ fn split_space_to_parts_vertically(
y: space_to_split.y, y: space_to_split.y,
columns, columns,
rows: space_to_split.rows, rows: space_to_split.rows,
max_columns,
..Default::default() ..Default::default()
}); });
current_width += columns; current_width += columns;
@ -87,18 +83,14 @@ fn split_space_to_parts_horizontally(
let mut parts_to_grow = Vec::new(); let mut parts_to_grow = Vec::new();
for size in sizes { for size in sizes {
let (rows, max_rows) = match size { let rows = match size {
Some(SplitSize::Percent(percent)) => ( Some(SplitSize::Percent(percent)) => {
(max_height as f32 * (percent as f32 / 100.0)) as usize, (max_height as f32 * (percent as f32 / 100.0)) as usize
None, } // TODO: round properly
), // TODO: round properly Some(SplitSize::Fixed(size)) => size as usize,
Some(SplitSize::Fixed(size)) => (size as usize, Some(size as usize)),
None => { None => {
parts_to_grow.push(current_y_position); parts_to_grow.push(current_y_position);
( 1 // This is grown later on
1, // This is grown later on
None,
)
} }
}; };
split_parts.push(PositionAndSize { split_parts.push(PositionAndSize {
@ -106,7 +98,6 @@ fn split_space_to_parts_horizontally(
y: current_y_position, y: current_y_position,
columns: space_to_split.columns, columns: space_to_split.columns,
rows, rows,
max_rows,
..Default::default() ..Default::default()
}); });
current_height += rows; current_height += rows;

View File

@ -1,531 +1,247 @@
use crate::{os_input_output::ServerOsApi, panes::PaneId, tab::Pane}; use crate::{os_input_output::ServerOsApi, panes::PaneId, tab::Pane};
use cassowary::{
strength::{REQUIRED, STRONG},
Constraint, Solver, Variable,
WeightedRelation::*,
};
use std::{ use std::{
cmp::Ordering,
collections::{BTreeMap, HashSet}, collections::{BTreeMap, HashSet},
ops::Not,
}; };
use zellij_utils::pane_size::PositionAndSize; use zellij_utils::pane_size::PositionAndSize;
pub(crate) struct PaneResizer<'a> { const GAP_SIZE: usize = 1; // Panes are separated by this number of rows / columns
pub struct PaneResizer<'a> {
panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>, panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>,
vars: BTreeMap<PaneId, (Variable, Variable)>,
solver: Solver,
os_api: &'a mut Box<dyn ServerOsApi>, os_api: &'a mut Box<dyn ServerOsApi>,
} }
#[derive(Debug, Clone, Copy)]
enum Direction {
Horizontal,
Vertical,
}
impl Not for Direction {
type Output = Self;
fn not(self) -> Self::Output {
match self {
Direction::Horizontal => Direction::Vertical,
Direction::Vertical => Direction::Horizontal,
}
}
}
#[derive(Debug, Clone, Copy)]
struct Span {
pid: PaneId,
direction: Direction,
fixed: bool,
pos: usize,
size: usize,
pos_var: Variable,
size_var: Variable,
}
// TODO: currently there are some functions here duplicated with Tab // TODO: currently there are some functions here duplicated with Tab
// all resizing functions should move here // all resizing functions should move here
// FIXME:
// 1. Rounding causes a loss of ratios, I need to store an internal f64 for
// each pane as well as the displayed usize and add custom rounding logic.
// 2. Vertical resizing doesn't seem to respect the space consumed by the tab
// and status bars?
// 3. A 2x2 layout and simultaneous vertical + horizontal resizing sometimes
// leads to unsolvable constraints? Maybe related to 2 (and possibly 1).
// I should sanity-check the `spans_in_boundary()` here!
impl<'a> PaneResizer<'a> { impl<'a> PaneResizer<'a> {
pub fn new( pub fn new(
panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>, panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>,
os_api: &'a mut Box<dyn ServerOsApi>, os_api: &'a mut Box<dyn ServerOsApi>,
) -> Self { ) -> Self {
PaneResizer { panes, os_api } let mut vars = BTreeMap::new();
for &k in panes.keys() {
vars.insert(k, (Variable::new(), Variable::new()));
} }
PaneResizer {
panes,
vars,
solver: Solver::new(),
os_api,
}
}
pub fn resize( pub fn resize(
&mut self, &mut self,
mut current_size: PositionAndSize, current_size: PositionAndSize,
new_size: PositionAndSize, new_size: PositionAndSize,
) -> Option<(isize, isize)> { ) -> Option<(isize, isize)> {
// (column_difference, row_difference) let col_delta = new_size.columns as isize - current_size.columns as isize;
let mut successfully_resized = false; let row_delta = new_size.rows as isize - current_size.rows as isize;
let mut column_difference: isize = 0; if col_delta != 0 {
let mut row_difference: isize = 0; let spans = self.solve_direction(Direction::Horizontal, new_size.columns)?;
match new_size.columns.cmp(&current_size.columns) { self.collapse_spans(&spans);
Ordering::Greater => {
let increase_by = new_size.columns - current_size.columns;
if let Some(panes_to_resize) = find_increasable_vertical_chain(
&self.panes,
increase_by,
current_size.columns,
current_size.rows,
) {
self.increase_panes_right_and_push_adjacents_right(
panes_to_resize,
increase_by,
);
column_difference = new_size.columns as isize - current_size.columns as isize;
current_size.columns =
(current_size.columns as isize + column_difference) as usize;
successfully_resized = true;
};
} }
Ordering::Less => { self.solver.reset();
let reduce_by = current_size.columns - new_size.columns; if row_delta != 0 {
if let Some(panes_to_resize) = find_reducible_vertical_chain( let spans = self.solve_direction(Direction::Vertical, new_size.rows)?;
&self.panes, self.collapse_spans(&spans);
reduce_by,
current_size.columns,
current_size.rows,
) {
self.reduce_panes_left_and_pull_adjacents_left(panes_to_resize, reduce_by);
column_difference = new_size.columns as isize - current_size.columns as isize;
current_size.columns =
(current_size.columns as isize + column_difference) as usize;
successfully_resized = true;
};
} }
Ordering::Equal => (), Some((col_delta, row_delta))
} }
match new_size.rows.cmp(&current_size.rows) {
Ordering::Greater => { fn solve_direction(&mut self, direction: Direction, space: usize) -> Option<Vec<Span>> {
let increase_by = new_size.rows - current_size.rows; let mut grid = Vec::new();
if let Some(panes_to_resize) = find_increasable_horizontal_chain( for boundary in self.grid_boundaries(direction) {
&self.panes, grid.push(self.spans_in_boundary(direction, boundary));
increase_by,
current_size.columns,
current_size.rows,
) {
self.increase_panes_down_and_push_down_adjacents(panes_to_resize, increase_by);
row_difference = new_size.rows as isize - current_size.rows as isize;
current_size.rows = (current_size.rows as isize + row_difference) as usize;
successfully_resized = true;
};
} }
Ordering::Less => {
let reduce_by = current_size.rows - new_size.rows; let constraints: Vec<_> = grid
if let Some(panes_to_resize) = find_reducible_horizontal_chain( .iter()
&self.panes, .flat_map(|s| constrain_spans(space, s))
reduce_by, .collect();
current_size.columns,
current_size.rows, // FIXME: This line needs to be restored before merging!
) { //self.solver.add_constraints(&constraints).ok()?;
self.reduce_panes_up_and_pull_adjacents_up(panes_to_resize, reduce_by); self.solver.add_constraints(&constraints).unwrap();
row_difference = new_size.rows as isize - current_size.rows as isize; Some(grid.into_iter().flatten().collect())
current_size.rows = (current_size.rows as isize + row_difference) as usize;
successfully_resized = true;
};
} }
Ordering::Equal => (),
} fn grid_boundaries(&self, direction: Direction) -> Vec<(usize, usize)> {
if successfully_resized { // Select the spans running *perpendicular* to the direction of resize
Some((column_difference, row_difference)) let spans: Vec<Span> = self
.panes
.values()
.map(|p| self.get_span(!direction, p.as_ref()))
.collect();
let mut last_edge = 0;
let mut bounds = Vec::new();
loop {
let mut spans_on_edge: Vec<&Span> =
spans.iter().filter(|p| p.pos == last_edge).collect();
spans_on_edge.sort_unstable_by_key(|s| s.size);
if let Some(next) = spans_on_edge.first() {
let next_edge = last_edge + next.size;
bounds.push((last_edge, next_edge));
last_edge = next_edge + GAP_SIZE;
} else { } else {
None break;
} }
} }
fn reduce_panes_left_and_pull_adjacents_left( bounds
&mut self, }
panes_to_reduce: Vec<PaneId>,
reduce_by: usize, fn spans_in_boundary(&self, direction: Direction, boundary: (usize, usize)) -> Vec<Span> {
) { let (start, end) = boundary;
let mut pulled_panes: HashSet<PaneId> = HashSet::new(); let bwn = |v| start <= v && v < end;
for pane_id in panes_to_reduce { let mut spans: Vec<_> = self
let (pane_x, pane_y, pane_columns, pane_rows) = { .panes
let pane = self.panes.get(&pane_id).unwrap(); .values()
(pane.x(), pane.y(), pane.columns(), pane.rows()) .filter(|p| {
}; let s = self.get_span(!direction, p.as_ref());
let panes_to_pull = self.panes.values_mut().filter(|p| { bwn(s.pos) || bwn(s.pos + s.size)
p.x() > pane_x + pane_columns })
&& (p.y() <= pane_y && p.y() + p.rows() >= pane_y .map(|p| self.get_span(direction, p.as_ref()))
|| p.y() >= pane_y && p.y() + p.rows() <= pane_y + pane_rows) .collect();
}); spans.sort_unstable_by_key(|s| s.pos);
for pane in panes_to_pull { spans
if !pulled_panes.contains(&pane.pid()) { }
pane.pull_left(reduce_by);
pulled_panes.insert(pane.pid()); fn get_span(&self, direction: Direction, pane: &dyn Pane) -> Span {
let pas = pane.position_and_size();
let (pos_var, size_var) = self.vars[&pane.pid()];
match direction {
Direction::Horizontal => Span {
pid: pane.pid(),
direction,
fixed: pas.cols_fixed,
pos: pas.x,
size: pas.columns,
pos_var,
size_var,
},
Direction::Vertical => Span {
pid: pane.pid(),
direction,
fixed: pas.rows_fixed,
pos: pas.y,
size: pas.rows,
pos_var,
size_var,
},
} }
} }
self.reduce_pane_width_left(&pane_id, reduce_by);
fn collapse_spans(&mut self, spans: &[Span]) {
for span in spans {
let solver = &self.solver; // Hand-holding the borrow-checker
let pane = self.panes.get_mut(&span.pid).unwrap();
let fetch_usize = |v| solver.get_value(v).round() as usize;
match span.direction {
Direction::Horizontal => pane.change_pos_and_size(&PositionAndSize {
x: fetch_usize(span.pos_var),
columns: fetch_usize(span.size_var),
..pane.position_and_size()
}),
Direction::Vertical => pane.change_pos_and_size(&PositionAndSize {
y: fetch_usize(span.pos_var),
rows: fetch_usize(span.size_var),
..pane.position_and_size()
}),
} }
}
fn reduce_panes_up_and_pull_adjacents_up(
&mut self,
panes_to_reduce: Vec<PaneId>,
reduce_by: usize,
) {
let mut pulled_panes: HashSet<PaneId> = HashSet::new();
for pane_id in panes_to_reduce {
let (pane_x, pane_y, pane_columns, pane_rows) = {
let pane = self.panes.get(&pane_id).unwrap();
(pane.x(), pane.y(), pane.columns(), pane.rows())
};
let panes_to_pull = self.panes.values_mut().filter(|p| {
p.y() > pane_y + pane_rows
&& (p.x() <= pane_x && p.x() + p.columns() >= pane_x
|| p.x() >= pane_x && p.x() + p.columns() <= pane_x + pane_columns)
});
for pane in panes_to_pull {
if !pulled_panes.contains(&pane.pid()) {
pane.pull_up(reduce_by);
pulled_panes.insert(pane.pid());
}
}
self.reduce_pane_height_up(&pane_id, reduce_by);
}
}
fn increase_panes_down_and_push_down_adjacents(
&mut self,
panes_to_increase: Vec<PaneId>,
increase_by: usize,
) {
let mut pushed_panes: HashSet<PaneId> = HashSet::new();
for pane_id in panes_to_increase {
let (pane_x, pane_y, pane_columns, pane_rows) = {
let pane = self.panes.get(&pane_id).unwrap();
(pane.x(), pane.y(), pane.columns(), pane.rows())
};
let panes_to_push = self.panes.values_mut().filter(|p| {
p.y() > pane_y + pane_rows
&& (p.x() <= pane_x && p.x() + p.columns() >= pane_x
|| p.x() >= pane_x && p.x() + p.columns() <= pane_x + pane_columns)
});
for pane in panes_to_push {
if !pushed_panes.contains(&pane.pid()) {
pane.push_down(increase_by);
pushed_panes.insert(pane.pid());
}
}
self.increase_pane_height_down(&pane_id, increase_by);
}
}
fn increase_panes_right_and_push_adjacents_right(
&mut self,
panes_to_increase: Vec<PaneId>,
increase_by: usize,
) {
let mut pushed_panes: HashSet<PaneId> = HashSet::new();
for pane_id in panes_to_increase {
let (pane_x, pane_y, pane_columns, pane_rows) = {
let pane = self.panes.get(&pane_id).unwrap();
(pane.x(), pane.y(), pane.columns(), pane.rows())
};
let panes_to_push = self.panes.values_mut().filter(|p| {
p.x() > pane_x + pane_columns
&& (p.y() <= pane_y && p.y() + p.rows() >= pane_y
|| p.y() >= pane_y && p.y() + p.rows() <= pane_y + pane_rows)
});
for pane in panes_to_push {
if !pushed_panes.contains(&pane.pid()) {
pane.push_right(increase_by);
pushed_panes.insert(pane.pid());
}
}
self.increase_pane_width_right(&pane_id, increase_by);
}
}
fn reduce_pane_height_up(&mut self, id: &PaneId, count: usize) {
let pane = self.panes.get_mut(id).unwrap();
pane.reduce_height_up(count);
if let PaneId::Terminal(pid) = id {
self.os_api
.set_terminal_size_using_fd(*pid, pane.columns() as u16, pane.rows() as u16);
}
}
fn increase_pane_height_down(&mut self, id: &PaneId, count: usize) {
let pane = self.panes.get_mut(id).unwrap();
pane.increase_height_down(count);
if let PaneId::Terminal(pid) = pane.pid() { if let PaneId::Terminal(pid) = pane.pid() {
self.os_api self.os_api.set_terminal_size_using_fd(
.set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16); pid,
} pane.columns() as u16,
} pane.rows() as u16,
fn increase_pane_width_right(&mut self, id: &PaneId, count: usize) {
let pane = self.panes.get_mut(id).unwrap();
pane.increase_width_right(count);
if let PaneId::Terminal(pid) = pane.pid() {
self.os_api
.set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16);
}
}
fn reduce_pane_width_left(&mut self, id: &PaneId, count: usize) {
let pane = self.panes.get_mut(id).unwrap();
pane.reduce_width_left(count);
if let PaneId::Terminal(pid) = pane.pid() {
self.os_api
.set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16);
}
}
}
fn find_next_increasable_horizontal_pane(
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
right_of: &dyn Pane,
increase_by: usize,
) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter(
|p| {
p.x() == right_of.x() + right_of.columns() + 1 && p.horizontally_overlaps_with(right_of)
}, // TODO: the name here is wrong, it should be vertically_overlaps_with
); );
let resizable_candidates =
next_pane_candidates.filter(|p| p.can_increase_height_by(increase_by));
resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id {
Some(next_pane) => {
let next_pane = panes.get(&next_pane).unwrap();
if next_pane.y() < p.y() {
next_pane_id
} else {
Some(p.pid())
} }
} }
None => Some(p.pid()), }
})
} }
fn find_next_increasable_vertical_pane( fn constrain_spans(space: usize, spans: &[Span]) -> HashSet<Constraint> {
panes: &BTreeMap<PaneId, Box<dyn Pane>>, let mut constraints = HashSet::new();
below: &dyn Pane,
increase_by: usize, // The first span needs to start at 0
) -> Option<PaneId> { constraints.insert(spans[0].pos_var | EQ(REQUIRED) | 0.0);
let next_pane_candidates = panes.values().filter(
|p| p.y() == below.y() + below.rows() + 1 && p.vertically_overlaps_with(below), // TODO: the name here is wrong, it should be horizontally_overlaps_with // Calculating "flexible" space (space not consumed by fixed-size spans)
let gap_space = GAP_SIZE * (spans.len() - 1);
let old_flex_space = spans
.iter()
.fold(0, |a, s| if !s.fixed { a + s.size } else { a });
let new_flex_space = spans.iter().fold(
space - gap_space,
|a, s| if s.fixed { a - s.size } else { a },
); );
let resizable_candidates =
next_pane_candidates.filter(|p| p.can_increase_width_by(increase_by)); // Keep spans stuck together
resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id { for pair in spans.windows(2) {
Some(next_pane) => { let (ls, rs) = (pair[0], pair[1]);
let next_pane = panes.get(&next_pane).unwrap(); constraints
if next_pane.x() < p.x() { .insert((ls.pos_var + ls.size_var + GAP_SIZE as f64) | EQ(REQUIRED) | rs.pos_var);
next_pane_id }
// Try to maintain ratios and lock non-flexible sizes
for span in spans {
if span.fixed {
constraints.insert(span.size_var | EQ(REQUIRED) | span.size as f64);
} else { } else {
Some(p.pid()) let ratio = span.size as f64 / old_flex_space as f64;
} constraints.insert((span.size_var / new_flex_space as f64) | EQ(STRONG) | ratio);
}
None => Some(p.pid()),
})
}
fn find_next_reducible_vertical_pane(
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
below: &dyn Pane,
reduce_by: usize,
) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter(
|p| p.y() == below.y() + below.rows() + 1 && p.vertically_overlaps_with(below), // TODO: the name here is wrong, it should be horizontally_overlaps_with
);
let resizable_candidates = next_pane_candidates.filter(|p| p.can_reduce_width_by(reduce_by));
resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id {
Some(next_pane) => {
let next_pane = panes.get(&next_pane).unwrap();
if next_pane.x() < p.x() {
next_pane_id
} else {
Some(p.pid())
}
}
None => Some(p.pid()),
})
}
fn find_next_reducible_horizontal_pane(
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
right_of: &dyn Pane,
reduce_by: usize,
) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter(
|p| {
p.x() == right_of.x() + right_of.columns() + 1 && p.horizontally_overlaps_with(right_of)
}, // TODO: the name here is wrong, it should be vertically_overlaps_with
);
let resizable_candidates = next_pane_candidates.filter(|p| p.can_reduce_height_by(reduce_by));
resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id {
Some(next_pane) => {
let next_pane = panes.get(&next_pane).unwrap();
if next_pane.y() < p.y() {
next_pane_id
} else {
Some(p.pid())
}
}
None => Some(p.pid()),
})
}
fn find_increasable_horizontal_chain(
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
increase_by: usize,
screen_width: usize,
screen_height: usize, // TODO: this is the previous size (make this clearer)
) -> Option<Vec<PaneId>> {
let mut horizontal_coordinate = 0;
loop {
if horizontal_coordinate == screen_height {
return None;
}
match panes
.values()
.find(|p| p.x() == 0 && p.y() == horizontal_coordinate)
{
Some(leftmost_pane) => {
if !leftmost_pane.can_increase_height_by(increase_by) {
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1;
continue;
}
let mut panes_to_resize = vec![];
let mut current_pane = leftmost_pane;
loop {
panes_to_resize.push(current_pane.pid());
if current_pane.x() + current_pane.columns() == screen_width {
return Some(panes_to_resize);
}
match find_next_increasable_horizontal_pane(
panes,
current_pane.as_ref(),
increase_by,
) {
Some(next_pane_id) => {
current_pane = panes.get(&next_pane_id).unwrap();
}
None => {
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1;
break;
}
};
}
}
None => {
return None;
}
}
}
}
fn find_increasable_vertical_chain(
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
increase_by: usize,
screen_width: usize,
screen_height: usize, // TODO: this is the previous size (make this clearer)
) -> Option<Vec<PaneId>> {
let mut vertical_coordinate = 0;
loop {
if vertical_coordinate == screen_width {
return None;
}
match panes
.values()
.find(|p| p.y() == 0 && p.x() == vertical_coordinate)
{
Some(topmost_pane) => {
if !topmost_pane.can_increase_width_by(increase_by) {
vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1;
continue;
}
let mut panes_to_resize = vec![];
let mut current_pane = topmost_pane;
loop {
panes_to_resize.push(current_pane.pid());
if current_pane.y() + current_pane.rows() == screen_height {
return Some(panes_to_resize);
}
match find_next_increasable_vertical_pane(
panes,
current_pane.as_ref(),
increase_by,
) {
Some(next_pane_id) => {
current_pane = panes.get(&next_pane_id).unwrap();
}
None => {
vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1;
break;
}
};
}
}
None => {
return None;
}
}
}
}
fn find_reducible_horizontal_chain(
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
reduce_by: usize,
screen_width: usize,
screen_height: usize, // TODO: this is the previous size (make this clearer)
) -> Option<Vec<PaneId>> {
let mut horizontal_coordinate = 0;
loop {
if horizontal_coordinate == screen_height {
return None;
}
match panes
.values()
.find(|p| p.x() == 0 && p.y() == horizontal_coordinate)
{
Some(leftmost_pane) => {
if !leftmost_pane.can_reduce_height_by(reduce_by) {
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1;
continue;
}
let mut panes_to_resize = vec![];
let mut current_pane = leftmost_pane;
loop {
panes_to_resize.push(current_pane.pid());
if current_pane.x() + current_pane.columns() == screen_width {
return Some(panes_to_resize);
}
match find_next_reducible_horizontal_pane(
panes,
current_pane.as_ref(),
reduce_by,
) {
Some(next_pane_id) => {
current_pane = panes.get(&next_pane_id).unwrap();
}
None => {
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1;
break;
}
};
}
}
None => {
return None;
}
}
}
}
fn find_reducible_vertical_chain(
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
increase_by: usize,
screen_width: usize,
screen_height: usize, // TODO: this is the previous size (make this clearer)
) -> Option<Vec<PaneId>> {
let mut vertical_coordinate = 0;
loop {
if vertical_coordinate == screen_width {
return None;
}
match panes
.values()
.find(|p| p.y() == 0 && p.x() == vertical_coordinate)
{
Some(topmost_pane) => {
if !topmost_pane.can_reduce_width_by(increase_by) {
vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1;
continue;
}
let mut panes_to_resize = vec![];
let mut current_pane = topmost_pane;
loop {
panes_to_resize.push(current_pane.pid());
if current_pane.y() + current_pane.rows() == screen_height {
return Some(panes_to_resize);
}
match find_next_reducible_vertical_pane(
panes,
current_pane.as_ref(),
increase_by,
) {
Some(next_pane_id) => {
current_pane = panes.get(&next_pane_id).unwrap();
}
None => {
vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1;
break;
}
};
}
}
None => {
return None;
}
} }
} }
// The last pane needs to end at the end of the space
let last = spans.last().unwrap();
constraints.insert((last.pos_var + last.size_var) | EQ(REQUIRED) | space as f64);
constraints
} }

View File

@ -158,7 +158,8 @@ pub(crate) fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObj
host_subscribe, host_subscribe,
host_unsubscribe, host_unsubscribe,
host_set_invisible_borders, host_set_invisible_borders,
host_set_max_height, host_set_fixed_height,
host_set_fixed_width,
host_set_selectable, host_set_selectable,
host_get_plugin_ids, host_get_plugin_ids,
host_open_file, host_open_file,
@ -189,13 +190,24 @@ fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) {
.unwrap() .unwrap()
} }
fn host_set_max_height(plugin_env: &PluginEnv, max_height: i32) { fn host_set_fixed_height(plugin_env: &PluginEnv, fixed_height: i32) {
let max_height = max_height as usize; let fixed_height = fixed_height as usize;
plugin_env plugin_env
.senders .senders
.send_to_screen(ScreenInstruction::SetMaxHeight( .send_to_screen(ScreenInstruction::SetFixedHeight(
PaneId::Plugin(plugin_env.plugin_id), PaneId::Plugin(plugin_env.plugin_id),
max_height, fixed_height,
))
.unwrap()
}
fn host_set_fixed_width(plugin_env: &PluginEnv, fixed_width: i32) {
let fixed_width = fixed_width as usize;
plugin_env
.senders
.send_to_screen(ScreenInstruction::SetFixedWidth(
PaneId::Plugin(plugin_env.plugin_id),
fixed_width,
)) ))
.unwrap() .unwrap()
} }

View File

@ -17,8 +17,12 @@ pub fn unsubscribe(event_types: &[EventType]) {
// Plugin Settings // Plugin Settings
pub fn set_max_height(max_height: i32) { pub fn set_fixed_height(fixed_height: i32) {
unsafe { host_set_max_height(max_height) }; unsafe { host_set_fixed_height(fixed_height) };
}
pub fn set_fixed_width(fixed_width: i32) {
unsafe { host_set_fixed_width(fixed_width) };
} }
pub fn set_selectable(selectable: bool) { pub fn set_selectable(selectable: bool) {
@ -64,7 +68,8 @@ pub fn object_to_stdout(object: &impl Serialize) {
extern "C" { extern "C" {
fn host_subscribe(); fn host_subscribe();
fn host_unsubscribe(); fn host_unsubscribe();
fn host_set_max_height(max_height: i32); fn host_set_fixed_height(fixed_height: i32);
fn host_set_fixed_width(fixed_width: i32);
fn host_set_selectable(selectable: i32); fn host_set_selectable(selectable: i32);
fn host_set_invisible_borders(invisible_borders: i32); fn host_set_invisible_borders(invisible_borders: i32);
fn host_get_plugin_ids(); fn host_get_plugin_ids();

View File

@ -207,7 +207,8 @@ pub enum ScreenContext {
ToggleActiveTerminalFullscreen, ToggleActiveTerminalFullscreen,
SetSelectable, SetSelectable,
SetInvisibleBorders, SetInvisibleBorders,
SetMaxHeight, SetFixedHeight,
SetFixedWidth,
ClosePane, ClosePane,
ApplyLayout, ApplyLayout,
NewTab, NewTab,

View File

@ -9,8 +9,11 @@ pub struct PositionAndSize {
pub y: usize, pub y: usize,
pub rows: usize, pub rows: usize,
pub columns: usize, pub columns: usize,
pub max_rows: Option<usize>, // FIXME: Honestly, these shouldn't exist and rows / columns should be enums like:
pub max_columns: Option<usize>, // Dimension::Flex(usize) / Dimension::Fixed(usize), but 400+ compiler errors is more than
// I'm in the mood for right now...
pub rows_fixed: bool,
pub cols_fixed: bool,
} }
impl From<Winsize> for PositionAndSize { impl From<Winsize> for PositionAndSize {