From 18dca848e6c4d59bc7c672a0863158daf8182ef2 Mon Sep 17 00:00:00 2001 From: Brooks Rady Date: Tue, 31 Aug 2021 16:42:34 +0100 Subject: [PATCH 01/42] fix(ui): offset content after viewport construction --- zellij-server/src/tab.rs | 5 +++-- zellij-server/src/ui/pane_resizer.rs | 16 ++-------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/zellij-server/src/tab.rs b/zellij-server/src/tab.rs index 2f646574..16e196a1 100644 --- a/zellij-server/src/tab.rs +++ b/zellij-server/src/tab.rs @@ -385,6 +385,7 @@ impl Tab { for geom in boundary_geom { self.offset_viewport(&geom) } + self.set_pane_frames(self.draw_pane_frames); // This is the end of the nasty viewport hack... // FIXME: Active / new / current terminal, should be pane self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next(); @@ -1642,7 +1643,7 @@ impl Tab { }) } pub fn relayout_tab(&mut self, direction: Direction) { - let mut resizer = PaneResizer::new(&mut self.panes.iter_mut(), &mut self.os_api); + let mut resizer = PaneResizer::new(self.panes.iter_mut()); let result = match direction { Direction::Horizontal => resizer.layout(direction, self.display_area.cols), Direction::Vertical => resizer.layout(direction, self.display_area.rows), @@ -1662,7 +1663,7 @@ impl Tab { .iter_mut() .filter(|(pid, _)| !temp_panes_to_hide.contains(pid)); let Size { rows, cols } = new_screen_size; - let mut resizer = PaneResizer::new(panes, &mut self.os_api); + let mut resizer = PaneResizer::new(panes); if resizer.layout(Direction::Horizontal, cols).is_ok() { let column_difference = cols as isize - self.display_area.cols as isize; // FIXME: Should the viewport be an Offset? diff --git a/zellij-server/src/ui/pane_resizer.rs b/zellij-server/src/ui/pane_resizer.rs index 0213f6d5..76153597 100644 --- a/zellij-server/src/ui/pane_resizer.rs +++ b/zellij-server/src/ui/pane_resizer.rs @@ -1,4 +1,4 @@ -use crate::{os_input_output::ServerOsApi, panes::PaneId, tab::Pane}; +use crate::{panes::PaneId, tab::Pane}; use cassowary::{ strength::{REQUIRED, STRONG}, Expression, Solver, Variable, @@ -12,7 +12,6 @@ use zellij_utils::{ pub struct PaneResizer<'a> { panes: HashMap<&'a PaneId, &'a mut Box>, - os_api: &'a mut Box, vars: HashMap, solver: Solver, } @@ -31,10 +30,7 @@ struct Span { type Grid = Vec>; impl<'a> PaneResizer<'a> { - pub fn new( - panes: impl Iterator)>, - os_api: &'a mut Box, - ) -> Self { + pub fn new(panes: impl Iterator)>) -> Self { let panes: HashMap<_, _> = panes.collect(); let mut vars = HashMap::new(); for &&k in panes.keys() { @@ -42,7 +38,6 @@ impl<'a> PaneResizer<'a> { } PaneResizer { panes, - os_api, vars, solver: Solver::new(), } @@ -146,13 +141,6 @@ impl<'a> PaneResizer<'a> { } else { pane.set_geom(new_geom); } - if let PaneId::Terminal(pid) = pane.pid() { - self.os_api.set_terminal_size_using_fd( - pid, - pane.get_content_columns() as u16, - pane.get_content_rows() as u16, - ); - } } } From 6ec51952d0593d414498a507c53b227ab88dfa64 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Wed, 1 Sep 2021 16:43:42 +0200 Subject: [PATCH 02/42] fix(tabs): force render by index rather than by position (#684) * fix(tabs): force render by index rather than by position * docs(changelog): document change --- CHANGELOG.md | 2 ++ zellij-server/src/screen.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1da63b00..11a74967 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] +* Fix bug when opening new tab the new pane's viewport would sometimes be calculated incorrectly (https://github.com/zellij-org/zellij/pull/683) +* Fix bug when in some cases closing a tab would not clear the previous pane's contents (https://github.com/zellij-org/zellij/pull/684) ## [0.16.0] - 2021-08-31 * Plugins don't crash zellij anymore on receiving mouse events (https://github.com/zellij-org/zellij/pull/620) diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 432b15fc..d751f81c 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -266,7 +266,7 @@ impl Screen { } else { self.active_tab_index = self.tab_history.pop().unwrap(); for t in self.tabs.values_mut() { - if t.position == self.active_tab_index.unwrap() { + if t.index == self.active_tab_index.unwrap() { t.set_force_render() } if t.position > active_tab.position { From bdef573fb47a5b100aeab70099df42e3f0cc1ab3 Mon Sep 17 00:00:00 2001 From: Paulo Coelho <9609090+prscoelho@users.noreply.github.com> Date: Mon, 6 Sep 2021 10:23:15 +0100 Subject: [PATCH 03/42] fix(tabs): use tab index for tab name (#686) --- zellij-server/src/tab.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zellij-server/src/tab.rs b/zellij-server/src/tab.rs index 16e196a1..be2d0ea7 100644 --- a/zellij-server/src/tab.rs +++ b/zellij-server/src/tab.rs @@ -268,7 +268,7 @@ impl Tab { let panes = BTreeMap::new(); let name = if name.is_empty() { - format!("Tab #{}", position + 1) + format!("Tab #{}", index + 1) } else { name }; From 1c9dc35121dbec00c424e4d3e367ec600aa909c0 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Mon, 6 Sep 2021 11:24:12 +0200 Subject: [PATCH 04/42] docs(changelog): update tab fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11a74967..7908d67e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] * Fix bug when opening new tab the new pane's viewport would sometimes be calculated incorrectly (https://github.com/zellij-org/zellij/pull/683) * Fix bug when in some cases closing a tab would not clear the previous pane's contents (https://github.com/zellij-org/zellij/pull/684) +* Fix bug where tabs would sometimes be created with the wrong index in their name (https://github.com/zellij-org/zellij/pull/686) ## [0.16.0] - 2021-08-31 * Plugins don't crash zellij anymore on receiving mouse events (https://github.com/zellij-org/zellij/pull/620) From 86fdd2400e467c5788e6598ca8acc2359d42e1c2 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Mon, 6 Sep 2021 20:24:47 +0200 Subject: [PATCH 05/42] fix(borders): properly handle wide chars in pane titles (#698) * work * fix(frame): properly handle widechars in frame titles * style(fmt): make rustfmt happy * fix(truncate): do not reverse second part of string (oops) * docs(changelog): document change --- CHANGELOG.md | 1 + test-template.yaml | 20 ++++++ zellij-server/src/ui/pane_boundaries_frame.rs | 69 +++++++++++-------- 3 files changed, 62 insertions(+), 28 deletions(-) create mode 100644 test-template.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 7908d67e..8cf07418 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Fix bug when opening new tab the new pane's viewport would sometimes be calculated incorrectly (https://github.com/zellij-org/zellij/pull/683) * Fix bug when in some cases closing a tab would not clear the previous pane's contents (https://github.com/zellij-org/zellij/pull/684) * Fix bug where tabs would sometimes be created with the wrong index in their name (https://github.com/zellij-org/zellij/pull/686) +* Fix bug where wide chars would mess up pane titles (https://github.com/zellij-org/zellij/pull/698) ## [0.16.0] - 2021-08-31 * Plugins don't crash zellij anymore on receiving mouse events (https://github.com/zellij-org/zellij/pull/620) diff --git a/test-template.yaml b/test-template.yaml new file mode 100644 index 00000000..b9c496d4 --- /dev/null +++ b/test-template.yaml @@ -0,0 +1,20 @@ +--- +template: + direction: Horizontal + parts: + - direction: Vertical + borderless: true + split_size: + Fixed: 1 + run: + plugin: tab-bar + - direction: Vertical + borderless: true + - direction: Vertical + borderless: true + - direction: Vertical + borderless: true + split_size: + Fixed: 2 + run: + plugin: status-bar diff --git a/zellij-server/src/ui/pane_boundaries_frame.rs b/zellij-server/src/ui/pane_boundaries_frame.rs index d3cd6d5a..49344149 100644 --- a/zellij-server/src/ui/pane_boundaries_frame.rs +++ b/zellij-server/src/ui/pane_boundaries_frame.rs @@ -4,6 +4,8 @@ use ansi_term::Style; use zellij_utils::pane_size::Viewport; use zellij_utils::zellij_tile::prelude::PaletteColor; +use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; + fn color_string(character: &str, color: Option) -> String { match color { Some(color) => match color { @@ -33,11 +35,11 @@ impl PaneFrame { let full_indication = format!(" {}/{} ", self.scroll_position.0, self.scroll_position.1); let short_indication = format!(" {} ", self.scroll_position.0); - if prefix.chars().count() + full_indication.chars().count() <= max_length { + if prefix.width() + full_indication.width() <= max_length { Some(format!("{}{}", prefix, full_indication)) - } else if full_indication.chars().count() <= max_length { + } else if full_indication.width() <= max_length { Some(full_indication) - } else if short_indication.chars().count() <= max_length { + } else if short_indication.width() <= max_length { Some(short_indication) } else { None @@ -52,28 +54,41 @@ impl PaneFrame { let full_text = format!(" {} ", &self.title); if max_length <= 6 { None - } else if full_text.chars().count() <= max_length { + } else if full_text.width() <= max_length { Some(full_text) } else { - let length_of_each_half = (max_length - middle_truncated_sign.chars().count()) / 2; - let first_part: String = full_text.chars().take(length_of_each_half).collect(); - let second_part: String = full_text - .chars() - .skip(full_text.chars().count() - length_of_each_half) - .collect(); - let title_left_side = if first_part.chars().count() - + middle_truncated_sign.chars().count() - + second_part.chars().count() - < max_length - { - // this means we lost 1 character when dividing the total length into halves - format!( - "{}{}{}", - first_part, middle_truncated_sign_long, second_part - ) - } else { - format!("{}{}{}", first_part, middle_truncated_sign, second_part) - }; + let length_of_each_half = (max_length - middle_truncated_sign.width()) / 2; + + let mut first_part: String = String::with_capacity(length_of_each_half); + for char in full_text.chars() { + if first_part.width() + char.width().unwrap_or(0) > length_of_each_half { + break; + } else { + first_part.push(char); + } + } + + let mut second_part: String = String::with_capacity(length_of_each_half); + for char in full_text.chars().rev() { + if second_part.width() + char.width().unwrap_or(0) > length_of_each_half { + break; + } else { + second_part.insert(0, char); + } + } + + let title_left_side = + if first_part.width() + middle_truncated_sign.width() + second_part.width() + < max_length + { + // this means we lost 1 character when dividing the total length into halves + format!( + "{}{}{}", + first_part, middle_truncated_sign_long, second_part + ) + } else { + format!("{}{}{}", first_part, middle_truncated_sign, second_part) + }; Some(title_left_side) } } @@ -83,15 +98,13 @@ impl PaneFrame { let right_boundary = boundary_type::TOP_RIGHT; let left_side = self.render_title_left_side(total_title_length); let right_side = left_side.as_ref().and_then(|left_side| { - let space_left = total_title_length.saturating_sub(left_side.chars().count() + 1); // 1 for a middle separator + let space_left = total_title_length.saturating_sub(left_side.width() + 1); // 1 for a middle separator self.render_title_right_side(space_left) }); let title_text = match (left_side, right_side) { (Some(left_side), Some(right_side)) => { let mut middle = String::new(); - for _ in - (left_side.chars().count() + right_side.chars().count())..total_title_length - { + for _ in (left_side.width() + right_side.width())..total_title_length { middle.push_str(boundary_type::HORIZONTAL); } format!( @@ -101,7 +114,7 @@ impl PaneFrame { } (Some(left_side), None) => { let mut middle_padding = String::new(); - for _ in left_side.chars().count()..total_title_length { + for _ in left_side.width()..total_title_length { middle_padding.push_str(boundary_type::HORIZONTAL); } format!( From 203a42c616d94710c8c32ab10d713eb3c8dac687 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Mon, 6 Sep 2021 20:26:30 +0200 Subject: [PATCH 06/42] fix(borders): properly handle in-viewport borderless panes (#697) * fix(borders): properly handle in-viewport borderless panes * style(fmt): make clippy happy * fix(borders): properly handle panes outside the viewport on startup * style(fmt): not this time, clippy * docs(changelog): document change --- CHANGELOG.md | 1 + zellij-server/src/panes/terminal_pane.rs | 4 ++- zellij-server/src/tab.rs | 42 +++++++++++++++++------- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cf07418..efd4a1aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Fix bug when in some cases closing a tab would not clear the previous pane's contents (https://github.com/zellij-org/zellij/pull/684) * Fix bug where tabs would sometimes be created with the wrong index in their name (https://github.com/zellij-org/zellij/pull/686) * Fix bug where wide chars would mess up pane titles (https://github.com/zellij-org/zellij/pull/698) +* Fix various borderless-frame in viewport bugs (https://github.com/zellij-org/zellij/pull/697) ## [0.16.0] - 2021-08-31 * Plugins don't crash zellij anymore on receiving mouse events (https://github.com/zellij-org/zellij/pull/620) diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index 092ec15e..ae867237 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -253,7 +253,9 @@ impl Pane for TerminalPane { color: self.frame_color, }; if &frame != last_frame { - vte_output.push_str(&frame.render()); + if !self.borderless { + vte_output.push_str(&frame.render()); + } self.frame = Some(frame); } } diff --git a/zellij-server/src/tab.rs b/zellij-server/src/tab.rs index be2d0ea7..b90d7827 100644 --- a/zellij-server/src/tab.rs +++ b/zellij-server/src/tab.rs @@ -684,22 +684,35 @@ impl Tab { } pub fn set_pane_frames(&mut self, draw_pane_frames: bool) { self.draw_pane_frames = draw_pane_frames; + self.should_clear_display_before_rendering = true; + let viewport = self.viewport; for (pane_id, pane) in self.panes.iter_mut() { - pane.set_frame(draw_pane_frames); - if draw_pane_frames { + if !pane.borderless() { + pane.set_frame(draw_pane_frames); + } + + #[allow(clippy::if_same_then_else)] + if draw_pane_frames & !pane.borderless() { + // there's definitely a frame around this pane, offset its contents pane.set_content_offset(Offset::frame(1)); + } else if draw_pane_frames && pane.borderless() { + // there's no frame around this pane, and the tab isn't handling the boundaries + // between panes (they each draw their own frames as they please) + // this one doesn't - do not offset its content + pane.set_content_offset(Offset::default()); + } else if !is_inside_viewport(&viewport, pane) { + // this pane is outside the viewport and has no border - it should not have an offset + pane.set_content_offset(Offset::default()); } else { + // no draw_pane_frames and this pane should have a separation to other panes + // according to its position in the viewport (eg. no separation if its at the + // viewport bottom) - offset its content accordingly let position_and_size = pane.current_geom(); let (pane_columns_offset, pane_rows_offset) = pane_content_offset(&position_and_size, &self.viewport); pane.set_content_offset(Offset::shift(pane_rows_offset, pane_columns_offset)); } - // FIXME: this should also override the above logic - if pane.borderless() { - pane.set_content_offset(Offset::default()); - } - // FIXME: This, and all other `set_terminal_size_using_fd` calls, would be best in // `TerminalPane::reflow_lines` if let PaneId::Terminal(pid) = pane_id { @@ -2345,10 +2358,9 @@ impl Tab { .unwrap(); } fn is_inside_viewport(&self, pane_id: &PaneId) -> bool { - let pane_position_and_size = self.panes.get(pane_id).unwrap().current_geom(); - pane_position_and_size.y >= self.viewport.y - && pane_position_and_size.y + pane_position_and_size.rows.as_usize() - <= self.viewport.y + self.viewport.rows + // this is mostly separated to an outside function in order to allow us to pass a clone to + // it sometimes when we need to get around the borrow checker + is_inside_viewport(&self.viewport, self.panes.get(pane_id).unwrap()) } fn offset_viewport(&mut self, position_and_size: &Viewport) { if position_and_size.x == self.viewport.x @@ -2378,6 +2390,14 @@ impl Tab { } } +#[allow(clippy::borrowed_box)] +fn is_inside_viewport(viewport: &Viewport, pane: &Box) -> bool { + let pane_position_and_size = pane.current_geom(); + pane_position_and_size.y >= viewport.y + && pane_position_and_size.y + pane_position_and_size.rows.as_usize() + <= viewport.y + viewport.rows +} + #[cfg(test)] #[path = "./unit/tab_tests.rs"] mod tab_tests; From 20b874d7603fbd9381f8a3e0337be8b914943dbe Mon Sep 17 00:00:00 2001 From: sudo_synul Date: Mon, 6 Sep 2021 21:25:15 +0200 Subject: [PATCH 07/42] fix(example) Update NewTab action in example/default.yaml to new syntax (#693) --- example/default.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/default.yaml b/example/default.yaml index 9b6c8904..1a72a634 100644 --- a/example/default.yaml +++ b/example/default.yaml @@ -129,7 +129,7 @@ keybinds: key: [ Char: 'j',] - action: [GoToPreviousTab,] key: [ Char: 'k',] - - action: [NewTab,] + - action: [NewTab: ,] key: [ Char: 'n',] - action: [CloseTab,] key: [ Char: 'x',] From 0f0122fb596e336d36dad04e390273dd3f3f430d Mon Sep 17 00:00:00 2001 From: a-kenji Date: Mon, 6 Sep 2021 21:26:26 +0200 Subject: [PATCH 08/42] chore(docs): fix/update example config file --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index efd4a1aa..e86bf138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Fix bug where tabs would sometimes be created with the wrong index in their name (https://github.com/zellij-org/zellij/pull/686) * Fix bug where wide chars would mess up pane titles (https://github.com/zellij-org/zellij/pull/698) * Fix various borderless-frame in viewport bugs (https://github.com/zellij-org/zellij/pull/697) +* Fix example configuration file (https://github.com/zellij-org/zellij/pull/693) ## [0.16.0] - 2021-08-31 * Plugins don't crash zellij anymore on receiving mouse events (https://github.com/zellij-org/zellij/pull/620) From 6d0c5a56f54eb3f47991cc2743587103cffc123c Mon Sep 17 00:00:00 2001 From: Paulo Coelho <9609090+prscoelho@users.noreply.github.com> Date: Thu, 9 Sep 2021 09:24:03 +0100 Subject: [PATCH 09/42] style(clippy): various fixes (#704) * test: fix clippy unused_io_amount * chore(clippy): various clippy fixes needless_borrow, let_and_return, vec_init_then_push, unit_arg, useless_format, field_reassign_with_default, redundant_clone --- src/tests/e2e/cases.rs | 86 +++++++++---------- src/tests/e2e/remote_runner.rs | 6 +- zellij-client/src/unit/input_handler_tests.rs | 18 ++-- zellij-server/src/logging_pipe.rs | 12 +-- zellij-server/src/panes/unit/grid_tests.rs | 5 +- zellij-server/src/unit/screen_tests.rs | 6 +- zellij-utils/src/input/config.rs | 18 ++-- zellij-utils/src/input/unit/keybinds_test.rs | 6 +- 8 files changed, 79 insertions(+), 78 deletions(-) diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs index dda7d96a..307986c1 100644 --- a/src/tests/e2e/cases.rs +++ b/src/tests/e2e/cases.rs @@ -157,7 +157,7 @@ pub fn cannot_split_terminals_vertically_when_active_terminal_is_too_small() { instruction: |mut remote_terminal: RemoteTerminal| -> bool { // this is just normal input that should be sent into the one terminal so that we can make // sure we silently failed to split in the previous step - remote_terminal.send_key(&"Hi!".as_bytes()); + remote_terminal.send_key("Hi!".as_bytes()); true }, }) @@ -205,26 +205,26 @@ pub fn scrolling_inside_a_pane() { let mut step_is_complete = false; if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { // cursor is in the newly opened second pane - remote_terminal.send_key(&format!("{:0<56}", "line1 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line2 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line3 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line4 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line5 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line6 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line7 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line8 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line9 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line10 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line11 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line12 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line13 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line14 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line15 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line16 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line17 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line18 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line19 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<57}", "line20 ").as_bytes()); + remote_terminal.send_key(format!("{:0<56}", "line1 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line2 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line3 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line4 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line5 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line6 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line7 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line8 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line9 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line10 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line11 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line12 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line13 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line14 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line15 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line16 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line17 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line18 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line19 ").as_bytes()); + remote_terminal.send_key(format!("{:0<57}", "line20 ").as_bytes()); step_is_complete = true; } step_is_complete @@ -574,7 +574,7 @@ pub fn lock_mode() { if remote_terminal.snapshot_contains("INTERFACE LOCKED") { remote_terminal.send_key(&TAB_MODE); remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE); - remote_terminal.send_key(&"abc".as_bytes()); + remote_terminal.send_key("abc".as_bytes()); step_is_complete = true; } step_is_complete @@ -675,7 +675,7 @@ pub fn detach_and_attach_session() { let mut step_is_complete = false; if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { // new pane has been opened and focused - remote_terminal.send_key(&"I am some text".as_bytes()); + remote_terminal.send_key("I am some text".as_bytes()); step_is_complete = true; } step_is_complete @@ -825,26 +825,26 @@ pub fn scrolling_inside_a_pane_with_mouse() { let mut step_is_complete = false; if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { // cursor is in the newly opened second pane - remote_terminal.send_key(&format!("{:0<56}", "line1 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line2 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line3 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line4 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line5 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line6 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line7 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line8 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line9 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line10 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line11 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line12 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line13 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line14 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line15 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line16 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line17 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line18 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<58}", "line19 ").as_bytes()); - remote_terminal.send_key(&format!("{:0<57}", "line20 ").as_bytes()); + remote_terminal.send_key(format!("{:0<56}", "line1 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line2 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line3 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line4 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line5 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line6 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line7 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line8 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line9 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line10 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line11 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line12 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line13 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line14 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line15 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line16 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line17 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line18 ").as_bytes()); + remote_terminal.send_key(format!("{:0<58}", "line19 ").as_bytes()); + remote_terminal.send_key(format!("{:0<57}", "line20 ").as_bytes()); step_is_complete = true; } step_is_complete diff --git a/src/tests/e2e/remote_runner.rs b/src/tests/e2e/remote_runner.rs index de9c35ed..7c4587f3 100644 --- a/src/tests/e2e/remote_runner.rs +++ b/src/tests/e2e/remote_runner.rs @@ -34,9 +34,7 @@ fn setup_remote_environment(channel: &mut ssh2::Channel, win_size: Size) { .request_pty("xterm", None, Some((columns, rows, 0, 0))) .unwrap(); channel.shell().unwrap(); - channel - .write_all(format!("export PS1=\"$ \"\n").as_bytes()) - .unwrap(); + channel.write_all("export PS1=\"$ \"\n".as_bytes()).unwrap(); channel.flush().unwrap(); } @@ -154,7 +152,7 @@ impl<'a> RemoteTerminal<'a> { format!("x: {}, y: {}", self.cursor_x, self.cursor_y) } pub fn send_key(&mut self, key: &[u8]) { - self.channel.write(key).unwrap(); + self.channel.write_all(key).unwrap(); self.channel.flush().unwrap(); } pub fn change_size(&mut self, cols: u32, rows: u32) { diff --git a/zellij-client/src/unit/input_handler_tests.rs b/zellij-client/src/unit/input_handler_tests.rs index a9b86a75..17bb1b0f 100644 --- a/zellij-client/src/unit/input_handler_tests.rs +++ b/zellij-client/src/unit/input_handler_tests.rs @@ -110,8 +110,7 @@ impl ClientOsApi for FakeClientOsApi { if stdin_events.is_empty() { panic!("ran out of stdin events!"); } - let next_event = stdin_events.remove(0); - next_event + stdin_events.remove(0) } fn box_clone(&self) -> Box { unimplemented!() @@ -174,14 +173,14 @@ pub fn quit_breaks_input_loop() { let send_client_instructions = SenderWithContext::new(send_client_instructions); let default_mode = InputMode::Normal; - drop(input_loop( + input_loop( client_os_api, config, options, command_is_executing, send_client_instructions, default_mode, - )); + ); let expected_actions_sent_to_server = vec![Action::Quit]; let received_actions = extract_actions_sent_to_server(events_sent_to_server); assert_eq!( @@ -192,8 +191,7 @@ pub fn quit_breaks_input_loop() { #[test] pub fn move_focus_left_in_pane_mode() { - let mut stdin_events = vec![]; - stdin_events.push(commands::MOVE_FOCUS_LEFT_IN_NORMAL_MODE.to_vec()); + let stdin_events = vec![commands::MOVE_FOCUS_LEFT_IN_NORMAL_MODE.to_vec()]; let events_sent_to_server = Arc::new(Mutex::new(vec![])); let command_is_executing = CommandIsExecuting::new(); let client_os_api = Box::new(FakeClientOsApi::new( @@ -210,14 +208,14 @@ pub fn move_focus_left_in_pane_mode() { let send_client_instructions = SenderWithContext::new(send_client_instructions); let default_mode = InputMode::Normal; - drop(input_loop( + input_loop( client_os_api, config, options, command_is_executing, send_client_instructions, default_mode, - )); + ); let expected_actions_sent_to_server = vec![Action::MoveFocusOrTab(Direction::Left), Action::Quit]; let received_actions = extract_actions_sent_to_server(events_sent_to_server); @@ -250,14 +248,14 @@ pub fn bracketed_paste() { let send_client_instructions = SenderWithContext::new(send_client_instructions); let default_mode = InputMode::Normal; - drop(input_loop( + input_loop( client_os_api, config, options, command_is_executing, send_client_instructions, default_mode, - )); + ); let expected_actions_sent_to_server = vec![ Action::Write(commands::BRACKETED_PASTE_START.to_vec()), Action::Write(commands::MOVE_FOCUS_LEFT_IN_NORMAL_MODE.to_vec()), // keys were directly written to server and not interpreted diff --git a/zellij-server/src/logging_pipe.rs b/zellij-server/src/logging_pipe.rs index f1fae6b8..1edb2954 100644 --- a/zellij-server/src/logging_pipe.rs +++ b/zellij-server/src/logging_pipe.rs @@ -149,7 +149,7 @@ mod logging_pipe_test { let test_buffer = "Testing write".as_bytes(); - pipe.write(test_buffer).expect("Err write"); + pipe.write_all(test_buffer).expect("Err write"); pipe.flush().expect("Err flush"); assert_eq!(pipe.buffer.len(), test_buffer.len()); @@ -161,7 +161,7 @@ mod logging_pipe_test { let test_buffer = "Testing write \n".as_bytes(); - pipe.write(test_buffer).expect("Err write"); + pipe.write_all(test_buffer).expect("Err write"); pipe.flush().expect("Err flush"); assert_eq!(pipe.buffer.len(), 0); @@ -174,7 +174,7 @@ mod logging_pipe_test { let test_buffer = "Testing write \n".as_bytes(); let test_buffer2 = "And the rest".as_bytes(); - pipe.write( + pipe.write_all( [ test_buffer, test_buffer, @@ -197,7 +197,7 @@ mod logging_pipe_test { let test_buffer = "Testing write \n".as_bytes(); - pipe.write( + pipe.write_all( [ test_buffer, test_buffer, @@ -223,7 +223,7 @@ mod logging_pipe_test { // make sure it's not valid utf-8 string if we drop last symbol assert!(std::str::from_utf8(&test_buffer[..test_buffer.len() - 1]).is_err()); - pipe.write(&test_buffer[..test_buffer.len() - 1]) + pipe.write_all(&test_buffer[..test_buffer.len() - 1]) .expect("Err write"); pipe.flush().expect("Err flush"); @@ -237,7 +237,7 @@ mod logging_pipe_test { let mut pipe = LoggingPipe::new("TestPipe", 0); let test_buffer = "Testing write \n".as_bytes(); - pipe.write( + pipe.write_all( [test_buffer, test_buffer, b"\n", b"\n", b"\n"] .concat() .as_slice(), diff --git a/zellij-server/src/panes/unit/grid_tests.rs b/zellij-server/src/panes/unit/grid_tests.rs index b10565fe..34236f7e 100644 --- a/zellij-server/src/panes/unit/grid_tests.rs +++ b/zellij-server/src/panes/unit/grid_tests.rs @@ -8,9 +8,8 @@ fn read_fixture(fixture_name: &str) -> Vec { path_to_file.push("tests"); path_to_file.push("fixtures"); path_to_file.push(fixture_name); - let content = std::fs::read(path_to_file) - .unwrap_or_else(|_| panic!("could not read fixture {:?}", &fixture_name)); - content + std::fs::read(path_to_file) + .unwrap_or_else(|_| panic!("could not read fixture {:?}", &fixture_name)) } #[test] diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 2d080886..95c1b98d 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -79,8 +79,10 @@ fn create_new_screen(size: Size) -> Screen { let mut bus: Bus = Bus::empty(); let fake_os_input = FakeInputOutput {}; bus.os_input = Some(Box::new(fake_os_input)); - let mut client_attributes = ClientAttributes::default(); - client_attributes.size = size; + let client_attributes = ClientAttributes { + size, + ..Default::default() + }; let max_panes = None; let mode_info = ModeInfo::default(); let session_state = Arc::new(RwLock::new(SessionState::Attached)); diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs index 930fe07a..8bee3d0a 100644 --- a/zellij-utils/src/input/config.rs +++ b/zellij-utils/src/input/config.rs @@ -275,8 +275,10 @@ mod config_test { #[test] fn try_from_cli_args_with_config() { let arbitrary_config = PathBuf::from("nonexistent.yaml"); - let mut opts = CliArgs::default(); - opts.config = Some(arbitrary_config); + let opts = CliArgs { + config: Some(arbitrary_config), + ..Default::default() + }; println!("OPTS= {:?}", opts); let result = Config::try_from(&opts); assert!(result.is_err()); @@ -285,11 +287,13 @@ mod config_test { #[test] fn try_from_cli_args_with_option_clean() { use crate::setup::Setup; - let mut opts = CliArgs::default(); - opts.command = Some(Command::Setup(Setup { - clean: true, - ..Setup::default() - })); + let opts = CliArgs { + command: Some(Command::Setup(Setup { + clean: true, + ..Setup::default() + })), + ..Default::default() + }; let result = Config::try_from(&opts); assert!(result.is_ok()); } diff --git a/zellij-utils/src/input/unit/keybinds_test.rs b/zellij-utils/src/input/unit/keybinds_test.rs index e8a776d7..6800bdf5 100644 --- a/zellij-utils/src/input/unit/keybinds_test.rs +++ b/zellij-utils/src/input/unit/keybinds_test.rs @@ -92,7 +92,7 @@ fn merge_keybinds_overwrites_same_keys() { let mut keybinds_self = Keybinds::new(); keybinds_self .0 - .insert(InputMode::Normal, mode_keybinds_self.clone()); + .insert(InputMode::Normal, mode_keybinds_self); let mut keybinds_other = Keybinds::new(); keybinds_other .0 @@ -152,7 +152,7 @@ fn no_unbind_unbinds_none() { fn last_keybind_is_taken() { let actions_1 = vec![Action::NoOp, Action::NewTab(None)]; let keyaction_1 = KeyActionFromYaml { - action: actions_1.clone(), + action: actions_1, key: vec![Key::F(1), Key::Backspace, Key::Char('t')], }; let actions_2 = vec![Action::GoToTab(1)]; @@ -184,7 +184,7 @@ fn last_keybind_overwrites() { let mut expected = ModeKeybinds::new(); expected.0.insert(Key::F(1), actions_2.clone()); - expected.0.insert(Key::Backspace, actions_1.clone()); + expected.0.insert(Key::Backspace, actions_1); expected.0.insert(Key::Char('t'), actions_2); assert_eq!(expected, ModeKeybinds::from(vec![keyaction_1, keyaction_2])); From 19b3f8366f7a46ac9360bd17b670c240d0aabe87 Mon Sep 17 00:00:00 2001 From: Tw Date: Thu, 9 Sep 2021 18:45:03 +0800 Subject: [PATCH 10/42] feat(plugin): add exec_cmd helper for executing command in host Signed-off-by: Tw Signed-off-by: Tw --- assets/plugins/status-bar.wasm | Bin 435070 -> 435055 bytes assets/plugins/strider.wasm | Bin 535162 -> 535152 bytes assets/plugins/tab-bar.wasm | Bin 424785 -> 424733 bytes zellij-server/src/tab.rs | 9 +++- zellij-server/src/wasm_vm.rs | 34 +++++++++++++-- zellij-tile/src/shim.rs | 5 +++ zellij-utils/assets/layouts/default.yaml | 6 ++- .../assets/layouts/disable-status-bar.yaml | 3 +- zellij-utils/assets/layouts/strider.yaml | 9 ++-- zellij-utils/src/input/layout.rs | 10 ++++- ...ee-panes-with-tab-and-default-plugins.yaml | 6 ++- zellij-utils/src/input/unit/layout_test.rs | 40 ++++++++++++++---- 12 files changed, 99 insertions(+), 23 deletions(-) diff --git a/assets/plugins/status-bar.wasm b/assets/plugins/status-bar.wasm index d1ad39e9020119af80d4d791e42fd00a925edf31..285669e5976ad31d2ff6e113e6a49cea21f80d3a 100644 GIT binary patch delta 14976 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 bJ)y_H=XgAiGFLBq#tVOMec3ZU-#+rc_2y3g diff --git a/assets/plugins/strider.wasm b/assets/plugins/strider.wasm index b57557a7ad3d86104e504c7ddb390b8675010596..ab3d3cd9f4a7123d2486abad400de0b14fab7ddc 100644 GIT binary patch delta 25426 zcmbWf30#%M_doued04#{yei77TyO(d+;L}6bI&bPQ*+?WE@am4_4-@fnfIA9+nFRrcP>4>2#jl6P5fvJ*hxRLO>}o_*UfjxEB#`h)_)1P6)A*dAFN+5^ z-KWzrqe1jgQT>8(Lqy;t+})m&cT4Uy$f0Yn4n-cWgfQ)U5# zBEH9ROpH*V9kaBqap61FQlo>lbK(XsxN0bh3WZ$)}Aat(OE@jWNdF z*;lMTV5Gd?wRm)!PCEK?cXIQfkgOl~YBEV4E8f_7J~`;L(Xh*Q$~E?MF{s>_&~-HB z7#q8O6YlSq@RBSqUf(TVp#McZRlr}WTj_yjkjZ%u+$;XHi zjFR-|spsx5)jaJJ^mNS~1SukyC&4aNw@ZZ<5K^KtkQmT{!-4IH%ZcP$5Z^&3Z}#)thRJ{$J$YNaF_ zOZvCAqDjV^{f8DCLpthIUA%YLUO@%Lt440osiL^knC60>Ge(Vz3EOf6T@Lajx#L{& zCF8bnCA7sz8NZB*j7{UW<8%Ck1)#iv&zFi5uL~o3t~h04PYTM093ZMC*LQNF`PYwf zqW^b&S0~zT(s?Q>_D+u1snSTB+LoR*7EbLaaz8iZ)Hd{N@rP3biSmq(r`?Fp={Lq2 zqo*g^vB#&kK?7c&o`lbDr}wa_-EOqf9}~5eM$L^W^lWj$j17Wz8oOro!{@KF`qIwg z&a=Zv9Y1?3P=#fr6t~G4NTTA>ZO6T%DD5U-ku63TJ7b5 z`OMgRQ`252m}m)&Ks-|qx&tBWaI?HxCR_5*z;*ek5wZWe8gq>sbU)oKt z(nHm(XwlTzl96D1J#Uh6XwF?QEW_q@iTdUT6k^FB#I2+3kOUotoiHAo+pN`jP7JFz z1pAr5V=^Sn9?dG_;M_Q|bCvPq+#bazZWcse8b96wX(+Ej^I5SBq_=YnrQ(CbJ*FoNOd>izh9QCaN%&-5wKL1{$vm z@-!!Q^XhEp@*hOJsQ9JZzZW8FA8c}rvH8v!R8}0kQVH0H=kFR!Ay0jZcDI*h#;Lnw z=yfCLo^iC-n0$|<_luvtrw4_WKl6^(Q=~vQEALGVXCyUPvQo;BMMg;GihpPDWv-QflT81ZYTWnG!y#MW zK(pJ)N~6icUxwto2@od>j0+F<7d6iqJsugDSo9P33-bnoDD@$}6HHj+AnqYygAVV;8o@!8U-eZy$DrdjVbr}m=nn0(wWH_9q{nF%3?1!R~-dkQ$$ zH?O1o-ZGDiX`#7lVvI&FHB&Phi9loJnlM{Jsp#~&HQqWA9?)2XZ>@PRZp*`^T0(#< zULwG&$%0cx{-aGpwpt8ZjJF=`lDW+SCM}3{CRhe6g8+*xx|wX#Lzw=m zMK_aex`*jIExMU()4fdJWzo%Ko30H5eYZsqqy$~=GHzX)LA#Bw)?T1pM#Z{YMZu}! zVe8WsUWdpO3yKN9`_W2DPnY*PD8$gqVeA@=tR>Ex9Kxgod z4WPmT254UH^>m=PBsY&>E2cdCG7N|Q%ykrA{;Or%u(J)o(l9S?e;p}|NyWXN4WuqL z%*K0OZ2M%X1A<^rO~6}Y7gCDsl!pVAoGc=Z8`}gK+uH^g8_(S+sH*t?^2-9^av*1( z=9+AkA&D)3X_+EGC^L?}*p^C*FTPkIVryW`%-wh}YRQ69nxKJKm+Orm3s=M5FMBzT zH>m4g&J$IKjI~?6#^^2Aidr*T{MMEugzfL6Td$?3ijQrL7If0Mu&ry*Nj9>jMyKu5 zKzL;P=XCOgc|{~nZ!;F`XfMufGdAoxA7vtKDsI92vR$ z89lt}Jxi$AyC*^R=iYqtO43^;7pThE`PR`PCNG~&f|-{yng^A7KxWnl?}9}(w>Y8H z7#ptQ;M-rMI<Q`LebwAG<4Ox~XsQDdh5CHM2j zJ^MS+JI2=iJ!ZV~xs{oPNXvNUqY+!tF!Myn?XeK1hqJ_OK#455D_OVG-&u{c_nv~H zmY-^BoOy4O6;m~LldECQVU#kHOOaWG0a$Thq{utW$3Nrzf!>is!5#Eto2K5PE2gUj*R{!rVxax|-)S%-$g7QB4OP5o-V!$=Fy0^Tz7`ULsa zGAY0!m1-6(ry8_iidU3E4bqtzJCAzlI)ma49}TD01*aW0$Ze+sFlJ){(s)7FK`t|% zKH5b;`*W#EI7%_%($B_EN4wF(MvITfLuSi9US@vA8h?I#o$>9*amLb$&*`h;VV}4~ ztAaE3pz!%rLtCnU0bvxH1%g@lxnGPsKN}Tv@qR2T6~+gj^+sU)QcVTFbJOH=fyp~`W}7`BlerQlv|wj&FgIE`+Rpj6&R0w*A<^-->t^yvhO2| znbpk@yQBIy6A#7b>)$8$$UkR`Ef9V316K7?Cq-Zan1Iba&%{!r=KF3vYU-z(z)7!{ zqRXuF^(x3RGEXFjS2;Pk&MITqiFo7N@1s4#F;mee)7lbS+dogVgaIvX`NOsPL`P-h zD=1I4sIzcEjy|x81bc(7Zri-9nf@|9I)O4`Q1$+wp2L{%9Q@CziDs-_-L$c%U+C$i zpzpZShH7#mH4o3)g8^nE>V}u%yedefzVw(H_YgG;g}H)TD@v|cw>(5mRYXf_Y*e3a zs%Kv)RkQA*Xgc;nv!99k6!psp0h>6d<{$>I^Vx|_c3*(??4W6KPv)pFEciiif2p%Q z!kI4dzXr+FUMT+J?A@H-nEY#smyNP4zf`JuL&nCkiKB!>x#wpvTSPT8lsXtGF>a&k z*Y;5bD5$3>33FrHrAxi1C=EmS>z~Cf&vmAVZ?Vv3bSCVWMW#rD<<@Wvn16mHR`06w zODUxEClnivBh>Z(+t;-Ob(lqky5xcHkq{^IPGOXJ7qt};*-Dn%80CDVVdWlO+oRD2 zo|Jr^%K0C7T4SGX)b^###YbwxM3gf&Tsb$v+dzut^3o5+xr+%Q1t*aq7Bjv1?**Zm z)m&vydF%(f%BOiWd&$!_{>xB%-CnmWM|N}FY*=}4F1nMd)Ik@mpl^NY zZrUlrFF`9z!MbsFUZVmUQp-$RQ;u;G_a=)qr=u*gG1Qn9f~Cu2wnRps9-cddDp^-cdWiq>DE-lW|6XD37)q^{GiP>1(&R}88xWQ)P8ug%; zw$ESlZK>vksmPFa3~N^&Xr9^N`^x~oJ+~wfRw-^vPn@|)Rxm@T#ZaYQNTh!MY{Gg$YNJ|G3(8lE zTT*}h(D_nTvw+6Xc9ph}5~)zNXhpB<6_-n&Y(p{XTq_!)SKHwF*7d+{2ItI_8I-MN zwxLw$%0S^a6F=Rq0z*?p5hK)v>G6OO!Y1YUP)y zxmTqhQiWHgSE$ohr5{r%$yY1CS}g~?iM6S&*Wc4w>h)yW)_5nj5Y|yCoKGI`Sg4il z;LEC1CGDu=-{p~~es4zu|4uJcquNtXI;2*$hmAU>f;-SSpeY?_a9RzYhxmi9VS=q| z6_Fhs?aUV5@<1s0NMYTzn^ROeJ=r*UWX# zB05E#b8^6;3$Fil!bMb}da^SmtMnxlk)mtn3Bc*jNcgJEbtX-2wP@kGMgr&O09gYE z(sre%&`R2>?n$8+XunG6LOuS`bEcMd!O*;=c66b|RG^x7r6`EHXIH9n6`}Bsnub$J zkxJ-B6JpA?vbe77nc3#G-6&a<{;FQ@#>VVdb+#L&hnD{OaDXcz2-*8}r-80243|}2 zm3pu{b-aos4mmaWB{5l*ccln5um{ZyD&zqyR4?|Rga~WE>JP}JE$WLN)Wg;z+e61A zh1T~Z8J3&Pt%p8((L}X(J`E7L*@_lWlzOix%@egxt4_V>H4eY;MX4Odr&{5ZR4~`( zsi#uu;g|yKTcCA4g_n0r5D%vMA&C9bCe^<;td&Q!K zgXnRa=Nb@8)wn*?B=az*g)@IR?lr73j`?Z01+i&A`Dq~8v|6iNG;&1u!PA5~MC za`&fHdQ=VRPZKgPy$YwKE*s%?vx!UoWTD1Dja~8!6G7VYKpoxVl5YaJ!(H+fiy97Y zpj-h4zm_cbGhVqbttoeOCGXdBoeU|EwzvvD8waw777J|pY#Y z%urdOE?h%T4k<&jdB|isAIf&tC-7OUpgw^>*x(QXydjd?lI{CwD2^!N^h>_*;jm|M zhh9RV>_!UlHpt-9H!V(puJWJorzcyGNd*F-OW`+qp5kMoe(A@$7CjaeOOs?RrP2}?2>Ec@@&LVI0stjf+sduBG^?CIeH^A#X-2WNYY>M>a!vQKq=%NsiazgRdiG&LJR=m6B74 zcrHG!o>J)BaV>VZjlW^G&Q_N7xRQ|hs>fLBIzJDMw2UstB=ynI2Cvpfi$wbf9jk;@ zPS)|3Aw6?Eguu`nm`->1(mB+|hmqqR+Kpn(UF`ubzn9FyvgLV!(B3@t;aD2puFlCI zJ3yvZcayM(!;~}uFL-fiW05ay9N`VedNq6k#bf3!nm~P;7Vg^1wiFvqJBAJCZr|<+ zQk#dFGYb>ct!BWXhppj7npiGVM{pgcHePO-L_2Hw5PwB(_Ia zav{{M%vqw|nn8==?bXcOA$cKT*hK7LanfxF6C{?JaSu})=#f(|l zG?b`KvnUzVL$l~@y=41dW8+;BYU6C`)wFs$%f()6HCGcpNK~tzXH#F1|CMSthX&CG z-_ki$MYP^GW*$u^ef?`z{&(kNH(&ka0!pH4mArsjT;VcO6Bp2+pj^Jb3sJYUq;_ri zOu#QGa9YR**|XxWrv)peNd2+^+eiPVf6hXhOs}Ytg*2Q`nZb()jw;nKgVNPKizpKA zD)rFCQ9x1H*SB{T)smg==7w2(vGOU9_)H~F{85GYS3H{@ER9=?fMwX{bV zQn4>L%UKp|O{!Q{k+RmOKDvn_dpNVvUqH@o9xT4vzzwFEe?2JH&i?3(x~UwX0=EG{EaK$mcd-H0^kv=+#^6@^>Zb4zJN zKvHE1Du9m|vp|z4ZGb@xl(h`_n5qNh_~ze6!Fp_#!=bZdsXjQMa#m3G-=}n$uf-j- zhpv*K&fH0{bU?XQk|9p+QTZ$3O0C(WN)V!h4gW*2L64Q99}QLMN@|8{#TcgR;e@K5^MLGwFW)8x2~M$%_Q2QX558x zwsVSI4vzVp>Ak&b*=TB_-n)xlvYmea?s)e#xQFhiki2(LRA+feZMc`RL#nypwpb{= znKZIR1(4~`;kDTWd}z0M*7zREq-TTbgpY?ya?QK;@B~Q>W?w!?>qh_bHp=AZ7;$`m zVvTuhb}t>4mv;^i{D5ROmNnqRQ64tl%gW)|hQ%lt3g`DoVlB^B%~w-oSRUsIMjj~h z1|?Tx+h+PGo2W-uQz+IAwHkK5KpkI=17fynnnR=vJ=|dFW-d_T8l$wQD zrlzOhWxSl(`HBarmFhQtd4F@PE_|3~IFBr<)scs3njiVPJwoFtZYztK?@%;}r8>+T z>}YLPR@Qd4aSc7)sM;O~XK}9bje3-B6HVTDB80CC@CI=V+&ry3f(t!JA?ncUd1?%Yta_WMEo$KNu z-k_d)oCZ6+Dfj(^HvV;38@*fg!?v#?N7NHp|C<#s7Dob;9bI-ik_u4YDzA3fj{Vx zTa6b!QpqKgHDTFx; zFLUx?*N*`>wT%+Rx%Yhyw^MgP*}lO$C;-o%e8XR*_JN`Pmk{b6AFYV3=Dh5+W*M7+ zmlyIz$_iijZYp=tY2VlH(vXml8g?tj$u+(XhY0`jRHqNI)aR+CA5w?RJXkF+x{H3I zM=2S&Yy=6e9gKLMunw}880sbeOAeHVTZ}79!<7=3X1em|5H7>DKwY*sQjXAc5md7Z z)-%-j=xAh6_CZ7&n9;S_Y*P`9Qfoh=IPu#ma}_kIj)nxSWwu5po55_l89mMA1enq1 z>lm_`Aw=aKr3rfZLCYNBRM^Rv@-bDCpA`QIHVub-Uw=Yx1UXi$Ol>?y^YpwhkBYws zD?!{>G`3?UpYotjTSZ1Pdm#C=#Ks991s17+MM}aERjv7oT94S0DdHI1K0{gD-aE~qQ?+*SiZTmLn6Ckj{l zswg?4A?Bi|F8^-y-lN1fw1aZhTaSoj_0>1juO)n?Xoi=P^&%KNKHj4w4>ynLTt`FI z?^_xYVeeH*u7{yH$)i+z(D%f*)Sf!lz#{T-v_U$J3)4(D=e@GlI-`OFQ=MaRB!tQ= z_49X>l*y^Eun>Vr);*xxWh69ULWnB?HWYchp_FR9g7L{4F^>)5s z$uWzTXVY}aF^g7U)1YL2T2ZP^43RtS7|sNU;Sh(u?KT;4>X#*mVWeFdNt)IZ_;ugN z?;-KVmtn&B|`5 zx2+glP$gCj?x#1b7+g=gtr*Nj1KDty%zYN8Rdz%K00*pqKjp2{IFFU8hCkDvUF`1y z6e5p#4FwZx?k5f~!Fi<+fNe<-yL)iesRsN)W9F=f1TjK&k3~)QK0C{8XH25NLMydV zSl4aO4Zr&jLqkpH4eYh;yy2l~37^a2L~5mZ9^&0BFUL5v*8D=vZ>Tnh%8RRPUVE{{ z04pc+dX48bo?cj|ZFdgNGn*mAbIi!6uWS}?8@Uxh{pB2*ScrrYwcreOY-_LaaP+X} zrw|`z-daZcv8RxwA0k$+-aJFO;?hrQVhtSCzkRCQXDMph{vVLmz_Z$1HsFrSkmXDX zWeS!Dlkz!JLYM-tsY&^UDS=Gk?IPzo!IXwf;fDZBIsbz%>nyD&HL1H;>GS+bz3^b! zH|!j~Ttnx4pPZ+QBL3`QvmAcp+zLwcTODixXsBOm>Gt^JOtqedvlHUaI>_#oT;Kh_ zQFEf>D*qxb>Z;YAi#Tfkd{|Xoq{#Vam|1v|5_ESzm$#w3Ov|KT|B79+o#3fUxWGAd zCr6{Y1iNu|j>dHfe;!uTf2YWpY<_~3R+nz5QlzV?zr$jl z_?>1)RC61wN9nEI-Pluo;jo%^iI!82`sfmEh2gyS4_Ioi%Kd}J#Ts02peGrf>}>6s z_?to@Uh;TVz@K2SYi5RaS2Enf3^`mvuvtRuD;bXcNi8Y+$;%Yg+7>;Rlj!m~vI%SA z3>+sUsBr-zPEEN?@v*inncV!!VvWl<`=33mc3*~9=*(et85NNCi73$$gZD!uX^ z0_VoHhg%Bva~yvnL+n&Pk%&-TN$jS_)hQAOAqk%lH3$lIGk7XMR07=^$boNUkXS|Z znD0oim`mLr7m2Cqv}$Y^c~jH9Ihzv%JL35jGS<-hO0nz42fnN$__l*D-#qbZeyB*I zc(pE6w2J&Eo)7*lF|fhE}!-DLIZ+cISCbwHn(Ow+150+c42x0+K{7kzi(Y@ zu`r@a`Mnq^uwFkaz{;Qhw~oW}RjYY>4WB0&D%iM?6^ zNqj*>?YKa>^%6c3c{k}p~a-!*be9QrKe21;|u`!X#U1vtyn?;rNv`6WFZ!J3yi&mSWeBcSFsTH9$&>fe;j3Il4LcBb;;ul zNwOlwxa58dv0g;GC=85+;*b`J@1J~S=IaX7%q2gx5O<;}?iMY?0~6trTPzd? zgp1G%$2ec6><);V(4!`lt$-S#7Z&3F;x=@xh1vmO8+7qYvqG){rubV6aYJz@`l*Fj zgK#JMo`qOWxDze15O)w)qK{c9lY5Ul(JTwHXmBU`(-&qH+-}^7eq|wUH#VamT8PIG z8`3fhg#ux(x7|XlY}lCQTZpR{=!At>6R>Uk@pH3=)(HvN6k(^xR|`1M@JfuA;A}r| zlgUoW0#%hF{^`}xxGv<na^f5epE^=_=~I%7nWUkD`5fUB#R5KYuY;KY7|bjr!X6 z5TU|BYGg0bI$FZ!e zY^5z=goJq7n2<`=N`s7FLG6d0T6pwFBdBtLx zimkV#Te-4hTx#LQ6mxUW1QrB};`*^G(&Qn=W|0AAH!@@@$SyT*yNC*rr)e}qQmWog z6@B`cZNeD=atp~w5F*VRkR(D93Cn=hOpUjarCea3yw|JWu?!`#DwM0Py+waq6x`BV ztnOA<%>N^IA|I%&{O$UPDWPU13G6jo(?>)FnH7;*H&%3xs^CgZF&^L;e`%bjN0~a? zM`Vba=hgJS;!Y}8pY|1NaQd3l58qy|dEQssPec$WhY!HDwjn#$^`H!UAFNK+&%=*J}+tCz&z6JZJIn1CL-+yj)7?aFkN1wht7sE>q;D zDpacbMu`}8W}s*?!B#@B^`IV6yR$mp>#557VfE}4Kg(cDeZ=GZ< zG?ZJUvn#g725@pg%e-Qwgfe*b(Xl*H5`?UuSF*om*hg$1xT!PF(QC^Q-u18ErJ zJQXcPYPeHL08(o5)eMR0o3GxJ;-+A8hFr>5ebdE|G_K!igg4N-?U(1QMtL=s83Xne zYH0gkDh)C#Eln56nLMMo9cZ%`?H?V>^O%XIV!OdMYA?eZvwnDvVK3j z`1JrjHUws14rHl&28&*}j(Kgcc-&)7>JX<^Tu`kaBBIpILqt$4Yba#HGt-UAnDumxD2uOoo-L4F2E=Q6xdQQ&p^Rl##!OAr|E>|a zF0-TrO=KeLHxn_lO4atEqNKH10=9^pfrkmU$*V6<)RtkGP_5abGPQn~xG?9cLS!eM zWw&^S!`gV@y~hB-TgcupYeY>DV$wkgF>Tw`#(a(Xak!Z6wc1MDR=ZVjZ+JDkRp`Lt z5n_+sa{VtY7f#D@fyxalQ)5PoOCBC>QwP1lqcM3Z)zndcpXv$y>L!$cLVJTS2@$}K z#wF_f%-xbU-|2Y+FcR?Hn6{Z%-s<=$apWJQ9IZ}_#&2xEls$5-h=GZF>00qsT;14t z`56U9kJlq_fLYq}W5oOQZ0fSHA_^n+@L18^WonAqy94U|u_7bVuam=ZOoaAB1!3kY z)QEASWjzhjS%b_68!S~warHP+#*G?1UPK1j3X?HjWW&~;87~IL+RHNQoT)-N9U7)fQ)zj=nTl-APCU&EG*NvG_b&tTfZ8-cjO%ZXbz(4|m+gMUz=>eW z*BpIE1+Zq3wPs<*BD_`xm2#cvX{`zI>cQ)*)ueuR9-9IEUp-TJRM&}On%m!CLiJBi z6#f2Tz#>)H^|_|uDI&qL571X@Cjr^sHAS>=n##gIpkACJ z9eRoe4*OODODQ|Urnn-JV2-Weqz}4!~ToE1Tw;M7#FPhPL7%Dm zK`#g6YlEIwkcbnkRUJ-aC3XzQZ#x!@sW9tSafgcu-eAkdes`<*Qk*MOrMHR8jk5Ql zW`16A4)^M^1713mFBiixbz*K8J)mr3Zx@5?hcppk@^utrKGs7M)vnvcXm*4It`IrY zU9DXq7TUj3qb{uwZJl`X9d+^R>%~{yA>Ku{^$&^|->f^uNW!zvS5}Jd!t)vX4Bh4r zR5XBmzEfxK5_h0Ui|!UJW9`HwJ|!Js#t=^uc8EAHm-{x~EkX&JxZ@sC;9fBTOieRIJYL=Q$Q19<0rdx?wW@iRNJl$nW})+H_Wke$zI>l} z9k0RW+z;;`uIjJL7B^EzRhBJ$P6zLMK*Wu(ex8Tjd(9HjrWO4D7_YngXoc*wFT{n? z-#G9Z`Dz^MhX+KVy5>$1>)Z06SW1-Wi_Q@*=rPHVWJ4Jvr8*vYMHtUg>TqJy?t z&aKmH#f^kMj9f2TJG2$+#T-H}zj#bM;LzqhF7gRo3fds>Yd&k#+$Tg{q*aFLp5)E4 zF6}}ipA_Y8ap_avkDK7(WGDZa0x>(?|EAK7oBUGNUVau5==I>8trpu$ztefzho@6` z@VuLE3=Y`$2fXM(&wecs?Hl{2jfR27>&&gERLYB@TZ{eda%bs3(J_7Zo4SftLpD59F! zHylXsvC;n9#W%rnpxDconi{@RtI;Ao*iwO z<7^uaf!Ju=6NZOZrJOQ-&LsLb_q3CNJrHX zARIDT=$P;K!(wM(Qr<)jN>3Y8`m;Uka_B}3UPD4v%dkw$~AT>TCBOS z$e!E)a!a}92`dviU-#`(`R4m(VwLayPefiw;*-!=B$pyEFQ5Iq{7xu@Gpe-XqF{14 zI%+;?;@7l(K)BDY`V%SEt0Me@9s@Sg{DQUANx|FJK3a~)vDW7B$ZQNPuG{;4EylB; zz83SaB%c3TLCXEzJ$>00HQYFZ?x9nl>^HYVl}1z(FtID*PM%~_iWJm*%@{62hpOxnZ{4O z{9o_{a0P3ga8HhKxy~!m1jk*tBVA<;XDzvH-mSOK>%8#ho91<1en;m{YT5Xp&Z=W? zy;hAPrV8{3uqb zoT;xXy*SU7UIpqk)K3+nyh~RNi3!`wcwOcjjD8y7KkEK13emXs2ZLYy&5G2 zTuGjETC}9=)Lo~=Bk6G~JlaSwKZ~592!9{%(Z(SBP}j7`dbm5%6YAkK;0P0|VLyv& ze2@Q(Yqapg>mY`}w7kQY1K{WMh1ZB(f}Zy6`#jrnOiVo9z(sOETn7$iXL&No62{qtt4W91k zF7u|gvGRU{IG2&CW`S)=DabCs{V5);8Nxf3SOO{l4hMb}A%;WSg^*j~q&xWzB0rDS zS%e`7e?u6=eClktJ~VX6ERQAt2PoN4ZxY&}tw(DNn4+dP)K}9xzV93A3k1T^jrEU3 zXl)0p5#cJislK_<-KY~UR?IQr@NVBXP4#yL-K}1X(%0cT>pi0N_|coYcueVE3l1Ko z{{1!W7SN&*e+VJh_Y6WeLZ|irls-UhjMksWBVZY$$IwnSEk^Gce5o1(R}iMw$LKX( zws!OITS;v%{!Shh}8!+a>!x!Xlzv!r%xE?kVk?XUoZDOU^BN} z+cIF5vctauxPEP`fV&|*U%eQo4{Vp;-J^8^d>MZ{_72{91%5?SrOowt)v>uA>2a90 z+f3?)=6alTs4mpds9sT#!0GkyAYhh0Hg1(eSWr7)j_@>daBJWeR*R0D5)B$_JMCg_15*tC zcB)LT{uX_!`X}gpCT+yeRk;p?_VJk7@(%jh6g--Zk6@y*meED<{AV4kjwI-v zXRSvu&B1;@{#aTLUXD2T$iXWSXFdnta|NCUT%SL~Nmnxx_05#3f?Mii5KL;R_kyWi z*HWK~FVdfCsb|6+zqpkdoN5Kx<*C@#dV2&zTSIs9`gyd8C~6d0B0SySG8XOu9<2fB zPWm5+vo1O5!bw+ew$|sMwD>l9yP$Je@?AUBm^OOChzf8_Lk^bR_4peHj7t3K;HX8- zVvFwZ^9*GhdF7IJs*T>V%e@0V+5{waLP;!52X_HxO>l58V4nK-D%n=QEwi!on5}m# zRp=G?x3s_7;a`B+emnd>00#qqg^;E4u^qmLko)&KLT>X8gwQ>08$wq7EeN4LG1_y8 zFylIe4H4dtkUP3ujZV_H<4pfYk{&t!ZZwMxPzB0h1N$Ff%u#n0_@tJETSX1j9$SnPl5Cf0eqNOWlEm<_z zyL{y>^ZIz}b{Mf33f^H9V8<~D;p1pti+cD;VAgRborO_9{Uu=7EGIok+X6zeYSdhB zs!pF4%@UW+yJNEzTBH$fJA}Y2F06I6I$5U@=(e^>IQyto-TfP;dP|nB+tI z=<$tGuk&chU}MV{g}+pFM<4x;=-{cARJx(^b|Bvg>_r%?g8S-2DOio^t9Kjz%MF(C z{v07&JD!c`jem9)g5Cv~XD%x)3NvLk8Q~QCO+`8zxbf`?U&0%AZDGpFTvs$RfB-%22fS%IGF*MIkvDBl`_rd^u z0d9P!D0iBk$YDyF-U(uzk)}7pp>0K)9!`P7Ah#6tXu94_l}LRiz7^FxU2hwmJ8buB!hKeFBa0JvIcpM_Q^9 zhGGF)s-_IZ25spqkJ-{!z{umdRJ}Y@@0{S&@W>S{c+ze`iTVwNG(3mgqmb(?Y#hd0 zExS9gx9T)aZ{S`rZ%*f%Zf0XtJ5jA2rYClEWHk`7V!hok+uG$00p=;_C}bkccn3>4 zYKG|@5=&4=64FoMkA>sl0>pW8)y`G}hU@8#vNJr|XyEUl&^&U{>apSa&_-M5cuWiE z>|dgBv54+RtAr7HJTyv<(0e626P4@X-G_s@d>(*lbFDeJOl=&Y52OP1%?Q1-bZW%y zrm5`?PPK8wJdbuQa({~+vQ={MQKWNEBGkB%INdnpeIT)Ij!=6?!VI)l+fRx}@6qMhJmvZiHx-7H-oU+hH?=7;`NeAunX{2)X*U2zkBjWYfFb zVIPF(uGSYJTcm*qIe$81Hci*q2_x);{kK^wMma)m*yjkjf^QIV!%iS%dvqQltI4QE z3l=UPJnPoQh-g`1MF~w9gaoFZJC6S+cMLp-;9u}%tXk28NXom z5H3A^(Q@s8&3jNKkJV!vRNE}47O88;>I3K__2^h^@3Yn3vHB5g<{lfT_YBE~Yr?fI zaf3QCPM_NCIt&)(gw_Lp+yO8C92?FZv6*u+9}RP;-*En z-L`1y0`L4q^Onr@&YYR^*PQ+p6^q+~@b|C+PnT4y=T! zn5g#Lpm*zIM#0$|qGQuGZTYlYr!Ac}f7bkh5x(Y=s<zn%<#Z z4pLdp{Pz(49>t2q1~?UeY#kjlzvgz8J56t)?wzVH6mcum$*KAj(tNv6Mo@^{^c$qO zEzws!8!JO(1-|Lxiek-Vt>S+iX02|&T*qI4ul@4C4@Bs)Q$ib$_U}`Ft_X~${M9Y) P2*h6yz7icgt@!@~=<_XE delta 25316 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 DHSZ+L diff --git a/assets/plugins/tab-bar.wasm b/assets/plugins/tab-bar.wasm index 896512fe24e574197e1ceb20da2c15767f95e109..39705bcb12e48b93a98c249b56f3c860807a1a74 100644 GIT binary patch delta 29082 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, PathBuf, usize), // tx_pid, path_of_plugin , tab_index - Update(Option, Event), // Focused plugin / broadcast, event data + Load(Sender, PathBuf, usize, bool), // tx_pid, path_of_plugin , tab_index, allow_exec_host_cmd + Update(Option, Event), // Focused plugin / broadcast, event data Render(Sender, u32, usize, usize), // String buffer, plugin id, rows, cols Unload(u32), Exit, @@ -54,6 +54,8 @@ pub(crate) struct PluginEnv { pub senders: ThreadSenders, pub wasi_env: WasiEnv, pub subscriptions: Arc>>, + // FIXME: Once permission system is ready, this could be removed + pub _allow_exec_host_cmd: bool, } // Thread main -------------------------------------------------------------------------------------------------------- @@ -65,7 +67,7 @@ pub(crate) fn wasm_thread_main(bus: Bus, store: Store, data_d let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel"); err_ctx.add_call(ContextType::Plugin((&event).into())); match event { - PluginInstruction::Load(pid_tx, path, tab_index) => { + PluginInstruction::Load(pid_tx, path, tab_index, _allow_exec_host_cmd) => { let plugin_dir = data_dir.join("plugins/"); let wasm_bytes = fs::read(&path) .or_else(|_| fs::read(&path.with_extension("wasm"))) @@ -99,12 +101,17 @@ pub(crate) fn wasm_thread_main(bus: Bus, store: Store, data_d let wasi = wasi_env.import_object(&module).unwrap(); + if _allow_exec_host_cmd { + info!("Plugin({:?}) is able to run any host command, this may lead to some security issues!", path); + } + let plugin_env = PluginEnv { plugin_id, tab_index, senders: bus.senders.clone(), wasi_env, subscriptions: Arc::new(Mutex::new(HashSet::new())), + _allow_exec_host_cmd, }; let zellij = zellij_exports(&store, &plugin_env); @@ -174,6 +181,7 @@ pub(crate) fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObj host_get_plugin_ids, host_open_file, host_set_timeout, + host_exec_cmd, } } @@ -248,6 +256,24 @@ fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) { }); } +fn host_exec_cmd(plugin_env: &PluginEnv) { + let mut cmdline: Vec = wasi_read_object(&plugin_env.wasi_env); + let command = cmdline.remove(0); + + // Bail out if we're forbidden to run command + if !plugin_env._allow_exec_host_cmd { + warn!("This plugin isn't allow to run command in host side, skip running this command: '{cmd} {args}'.", + cmd = command, args = cmdline.join(" ")); + return; + } + + // Here, we don't wait the command to finish + process::Command::new(command) + .args(cmdline) + .spawn() + .unwrap(); +} + // Helper Functions --------------------------------------------------------------------------------------------------- pub fn wasi_read_string(wasi_env: &WasiEnv) -> String { diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs index b514b55a..bdd6f4fb 100644 --- a/zellij-tile/src/shim.rs +++ b/zellij-tile/src/shim.rs @@ -37,6 +37,10 @@ pub fn open_file(path: &Path) { pub fn set_timeout(secs: f64) { unsafe { host_set_timeout(secs) }; } +pub fn exec_cmd(cmd: &[&str]) { + object_to_stdout(&cmd); + unsafe { host_exec_cmd() }; +} // Internal Functions @@ -60,4 +64,5 @@ extern "C" { fn host_get_plugin_ids(); fn host_open_file(); fn host_set_timeout(secs: f64); + fn host_exec_cmd(); } diff --git a/zellij-utils/assets/layouts/default.yaml b/zellij-utils/assets/layouts/default.yaml index e0a28da1..549dea24 100644 --- a/zellij-utils/assets/layouts/default.yaml +++ b/zellij-utils/assets/layouts/default.yaml @@ -7,7 +7,8 @@ template: split_size: Fixed: 1 run: - plugin: tab-bar + plugin: + path: tab-bar - direction: Vertical body: true - direction: Vertical @@ -15,6 +16,7 @@ template: split_size: Fixed: 2 run: - plugin: status-bar + plugin: + path: status-bar tabs: - direction: Vertical diff --git a/zellij-utils/assets/layouts/disable-status-bar.yaml b/zellij-utils/assets/layouts/disable-status-bar.yaml index a58ef4cf..e97bb8f1 100644 --- a/zellij-utils/assets/layouts/disable-status-bar.yaml +++ b/zellij-utils/assets/layouts/disable-status-bar.yaml @@ -7,6 +7,7 @@ template: split_size: Fixed: 1 run: - plugin: tab-bar + plugin: + path: tab-bar - direction: Vertical body: true diff --git a/zellij-utils/assets/layouts/strider.yaml b/zellij-utils/assets/layouts/strider.yaml index 96e3c290..ccb2a574 100644 --- a/zellij-utils/assets/layouts/strider.yaml +++ b/zellij-utils/assets/layouts/strider.yaml @@ -7,7 +7,8 @@ template: split_size: Fixed: 1 run: - plugin: tab-bar + plugin: + path: tab-bar - direction: Vertical body: true - direction: Vertical @@ -15,7 +16,8 @@ template: split_size: Fixed: 2 run: - plugin: status-bar + plugin: + path: status-bar tabs: - direction: Vertical parts: @@ -23,5 +25,6 @@ tabs: split_size: Percent: 20 run: - plugin: strider + plugin: + path: strider - direction: Horizontal diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index be71130c..5d4b3492 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -53,11 +53,19 @@ pub enum SplitSize { #[serde(crate = "self::serde")] pub enum Run { #[serde(rename = "plugin")] - Plugin(Option), + Plugin(Option), #[serde(rename = "command")] Command(RunCommand), } +#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(crate = "self::serde")] +pub struct RunPlugin { + pub path: PathBuf, + #[serde(default)] + pub _allow_exec_host_cmd: bool, +} + // The layout struct ultimately used to build the layouts. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(crate = "self::serde")] diff --git a/zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-default-plugins.yaml b/zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-default-plugins.yaml index 88046395..ae54a0c9 100644 --- a/zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-default-plugins.yaml +++ b/zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-default-plugins.yaml @@ -6,14 +6,16 @@ template: split_size: Fixed: 1 run: - plugin: tab-bar + plugin: + path: tab-bar - direction: Horizontal body: true - direction: Vertical split_size: Fixed: 2 run: - plugin: status-bar + plugin: + path: status-bar tabs: - direction: Vertical diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs index abce56a4..1b696b0a 100644 --- a/zellij-utils/src/input/unit/layout_test.rs +++ b/zellij-utils/src/input/unit/layout_test.rs @@ -45,7 +45,10 @@ fn default_layout_merged_correctly() { borderless: true, parts: vec![], split_size: Some(SplitSize::Fixed(1)), - run: Some(Run::Plugin(Some("tab-bar".into()))), + run: Some(Run::Plugin(Some(RunPlugin { + path: "tab-bar".into(), + ..Default::default() + }))), }, Layout { direction: Direction::Vertical, @@ -59,7 +62,10 @@ fn default_layout_merged_correctly() { borderless: true, parts: vec![], split_size: Some(SplitSize::Fixed(2)), - run: Some(Run::Plugin(Some("status-bar".into()))), + run: Some(Run::Plugin(Some(RunPlugin { + path: "status-bar".into(), + ..Default::default() + }))), }, ], split_size: None, @@ -83,7 +89,10 @@ fn default_layout_new_tab_correct() { borderless: true, parts: vec![], split_size: Some(SplitSize::Fixed(1)), - run: Some(Run::Plugin(Some("tab-bar".into()))), + run: Some(Run::Plugin(Some(RunPlugin { + path: "tab-bar".into(), + ..Default::default() + }))), }, Layout { direction: Direction::Horizontal, @@ -97,7 +106,10 @@ fn default_layout_new_tab_correct() { borderless: true, parts: vec![], split_size: Some(SplitSize::Fixed(2)), - run: Some(Run::Plugin(Some("status-bar".into()))), + run: Some(Run::Plugin(Some(RunPlugin { + path: "status-bar".into(), + ..Default::default() + }))), }, ], split_size: None, @@ -253,7 +265,10 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() { borderless: false, parts: vec![], split_size: Some(SplitSize::Fixed(1)), - run: Some(Run::Plugin(Some("tab-bar".into()))), + run: Some(Run::Plugin(Some(RunPlugin { + path: "tab-bar".into(), + ..Default::default() + }))), }, Layout { direction: Direction::Vertical, @@ -297,7 +312,10 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() { borderless: false, parts: vec![], split_size: Some(SplitSize::Fixed(2)), - run: Some(Run::Plugin(Some("status-bar".into()))), + run: Some(Run::Plugin(Some(RunPlugin { + path: "status-bar".into(), + ..Default::default() + }))), }, ], split_size: None, @@ -321,7 +339,10 @@ fn three_panes_with_tab_and_default_plugins_new_tab_is_correct() { borderless: false, parts: vec![], split_size: Some(SplitSize::Fixed(1)), - run: Some(Run::Plugin(Some("tab-bar".into()))), + run: Some(Run::Plugin(Some(RunPlugin { + path: "tab-bar".into(), + ..Default::default() + }))), }, Layout { direction: Direction::Horizontal, @@ -335,7 +356,10 @@ fn three_panes_with_tab_and_default_plugins_new_tab_is_correct() { borderless: false, parts: vec![], split_size: Some(SplitSize::Fixed(2)), - run: Some(Run::Plugin(Some("status-bar".into()))), + run: Some(Run::Plugin(Some(RunPlugin { + path: "status-bar".into(), + ..Default::default() + }))), }, ], split_size: None, From f0da6872df49815de723b11fe9cf151be0de4e61 Mon Sep 17 00:00:00 2001 From: Brooks Rady Date: Thu, 9 Sep 2021 11:51:21 +0100 Subject: [PATCH 11/42] docs(changelog): add cmd_exec feature --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e86bf138..3da60017 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Fix bug where wide chars would mess up pane titles (https://github.com/zellij-org/zellij/pull/698) * Fix various borderless-frame in viewport bugs (https://github.com/zellij-org/zellij/pull/697) * Fix example configuration file (https://github.com/zellij-org/zellij/pull/693) +* Allow plugins to run system commands (https://github.com/zellij-org/zellij/pull/666) + * This has also added a temporary new permission flag that needs to be specified in the layout. This is a breaking change: + ```yaml + ... + plugin: strider + ... + ``` + has become: + ```yaml + plugin: + path: strider + ``` + A plugin can be given command executing permission with: + ```yaml + plugin: + path: strider + _allow_exec_host_cmd: true + ``` ## [0.16.0] - 2021-08-31 * Plugins don't crash zellij anymore on receiving mouse events (https://github.com/zellij-org/zellij/pull/620) From f2850d2931dddbc3d3ac4f4ac3c656df63bc1ebb Mon Sep 17 00:00:00 2001 From: Paulo Coelho <9609090+prscoelho@users.noreply.github.com> Date: Thu, 9 Sep 2021 15:38:10 +0100 Subject: [PATCH 12/42] fix(tab-bar): prevent active tab from being hidden (#703) --- default-plugins/tab-bar/src/line.rs | 172 +++++++++++++--------------- default-plugins/tab-bar/src/main.rs | 2 +- default-plugins/tab-bar/src/tab.rs | 4 +- 3 files changed, 82 insertions(+), 96 deletions(-) diff --git a/default-plugins/tab-bar/src/line.rs b/default-plugins/tab-bar/src/line.rs index 4cdc02c4..be5623ff 100644 --- a/default-plugins/tab-bar/src/line.rs +++ b/default-plugins/tab-bar/src/line.rs @@ -8,39 +8,82 @@ fn get_current_title_len(current_title: &[LinePart]) -> usize { current_title.iter().map(|p| p.len).sum() } +// move elements from before_active and after_active into tabs_to_render while they fit in cols +// adds collapsed_tabs to the left and right if there's left over tabs that don't fit fn populate_tabs_in_tab_line( tabs_before_active: &mut Vec, tabs_after_active: &mut Vec, tabs_to_render: &mut Vec, cols: usize, + palette: Palette, + capabilities: PluginCapabilities, ) { - let mut take_next_tab_from_tabs_after = true; + let mut middle_size = get_current_title_len(tabs_to_render); + + let mut total_left = 0; + let mut total_right = 0; loop { - if tabs_before_active.is_empty() && tabs_after_active.is_empty() { + let left_count = tabs_before_active.len(); + let right_count = tabs_after_active.len(); + let collapsed_left = left_more_message(left_count, palette, tab_separator(capabilities)); + let collapsed_right = right_more_message(right_count, palette, tab_separator(capabilities)); + + let total_size = collapsed_left.len + middle_size + collapsed_right.len; + + if total_size > cols { + // break and dont add collapsed tabs to tabs_to_render, they will not fit break; } - let current_title_len = get_current_title_len(tabs_to_render); - if current_title_len >= cols { - break; - } - let should_take_next_tab = take_next_tab_from_tabs_after; - let can_take_next_tab = !tabs_after_active.is_empty() - && tabs_after_active.get(0).unwrap().len + current_title_len <= cols; - let can_take_previous_tab = !tabs_before_active.is_empty() - && tabs_before_active.last().unwrap().len + current_title_len <= cols; - if should_take_next_tab && can_take_next_tab { - let next_tab = tabs_after_active.remove(0); - tabs_to_render.push(next_tab); - take_next_tab_from_tabs_after = false; - } else if can_take_previous_tab { - let previous_tab = tabs_before_active.pop().unwrap(); - tabs_to_render.insert(0, previous_tab); - take_next_tab_from_tabs_after = true; - } else if can_take_next_tab { - let next_tab = tabs_after_active.remove(0); - tabs_to_render.push(next_tab); - take_next_tab_from_tabs_after = false; + + let left = if let Some(tab) = tabs_before_active.last() { + tab.len } else { + usize::MAX + }; + + let right = if let Some(tab) = tabs_after_active.first() { + tab.len + } else { + usize::MAX + }; + + // total size is shortened if the next tab to be added is the last one, as that will remove the collapsed tab + let size_by_adding_left = + left.saturating_add(total_size) + .saturating_sub(if left_count == 1 { + collapsed_left.len + } else { + 0 + }); + let size_by_adding_right = + right + .saturating_add(total_size) + .saturating_sub(if right_count == 1 { + collapsed_right.len + } else { + 0 + }); + + let left_fits = size_by_adding_left <= cols; + let right_fits = size_by_adding_right <= cols; + // active tab is kept in the middle by adding to the side that + // has less width, or if the tab on the other side doesn' fit + if (total_left <= total_right || !right_fits) && left_fits { + // add left tab + let tab = tabs_before_active.pop().unwrap(); + middle_size += tab.len; + total_left += tab.len; + tabs_to_render.insert(0, tab); + } else if right_fits { + // add right tab + let tab = tabs_after_active.remove(0); + middle_size += tab.len; + total_right += tab.len; + tabs_to_render.push(tab); + } else { + // there's either no space to add more tabs or no more tabs to add, so we're done + tabs_to_render.insert(0, collapsed_left); + tabs_to_render.push(collapsed_right); break; } } @@ -56,7 +99,8 @@ fn left_more_message(tab_count_to_the_left: usize, palette: Palette, separator: " ← +many ".to_string() }; // 238 - let more_text_len = more_text.chars().count() + 2; // 2 for the arrows + // chars length plus separator length on both sides + let more_text_len = more_text.chars().count() + 2 * separator.chars().count(); let left_separator = style!(palette.cyan, palette.orange).paint(separator); let more_styled_text = style!(palette.black, palette.orange) .bold() @@ -85,7 +129,8 @@ fn right_more_message( } else { " +many → ".to_string() }; - let more_text_len = more_text.chars().count() + 1; // 2 for the arrow + // chars length plus separator length on both sides + let more_text_len = more_text.chars().count() + 2 * separator.chars().count(); let left_separator = style!(palette.cyan, palette.orange).paint(separator); let more_styled_text = style!(palette.black, palette.orange) .bold() @@ -101,48 +146,6 @@ fn right_more_message( } } -fn add_previous_tabs_msg( - tabs_before_active: &mut Vec, - tabs_to_render: &mut Vec, - title_bar: &mut Vec, - cols: usize, - palette: Palette, - separator: &str, -) { - while get_current_title_len(tabs_to_render) - + left_more_message(tabs_before_active.len(), palette, separator).len - >= cols - && !tabs_to_render.is_empty() - { - tabs_before_active.push(tabs_to_render.remove(0)); - } - - let left_more_message = left_more_message(tabs_before_active.len(), palette, separator); - if left_more_message.len <= cols { - title_bar.push(left_more_message); - } -} - -fn add_next_tabs_msg( - tabs_after_active: &mut Vec, - title_bar: &mut Vec, - cols: usize, - palette: Palette, - separator: &str, -) { - while get_current_title_len(title_bar) - + right_more_message(tabs_after_active.len(), palette, separator).len - >= cols - && !title_bar.is_empty() - { - tabs_after_active.insert(0, title_bar.pop().unwrap()); - } - let right_more_message = right_more_message(tabs_after_active.len(), palette, separator); - if right_more_message.len < cols { - title_bar.push(right_more_message); - } -} - fn tab_line_prefix(session_name: Option<&str>, palette: Palette, cols: usize) -> Vec { let prefix_text = " Zellij ".to_string(); @@ -184,7 +187,6 @@ pub fn tab_line( palette: Palette, capabilities: PluginCapabilities, ) -> Vec { - let mut tabs_to_render = Vec::new(); let mut tabs_after_active = all_tabs.split_off(active_tab_index); let mut tabs_before_active = all_tabs; let active_tab = if !tabs_after_active.is_empty() { @@ -194,38 +196,22 @@ pub fn tab_line( }; let mut prefix = tab_line_prefix(session_name, palette, cols); let prefix_len = get_current_title_len(&prefix); - if prefix_len + active_tab.len <= cols { - tabs_to_render.push(active_tab); + + // if active tab alone won't fit in cols, don't draw any tabs + if prefix_len + active_tab.len > cols { + return prefix; } + let mut tabs_to_render = vec![active_tab]; + populate_tabs_in_tab_line( &mut tabs_before_active, &mut tabs_after_active, &mut tabs_to_render, cols.saturating_sub(prefix_len), + palette, + capabilities, ); - - let mut tab_line: Vec = vec![]; - if !tabs_before_active.is_empty() { - add_previous_tabs_msg( - &mut tabs_before_active, - &mut tabs_to_render, - &mut tab_line, - cols.saturating_sub(prefix_len), - palette, - tab_separator(capabilities), - ); - } - tab_line.append(&mut tabs_to_render); - if !tabs_after_active.is_empty() { - add_next_tabs_msg( - &mut tabs_after_active, - &mut tab_line, - cols.saturating_sub(prefix_len), - palette, - tab_separator(capabilities), - ); - } - prefix.append(&mut tab_line); + prefix.append(&mut tabs_to_render); prefix } diff --git a/default-plugins/tab-bar/src/main.rs b/default-plugins/tab-bar/src/main.rs index d4111cfa..85078126 100644 --- a/default-plugins/tab-bar/src/main.rs +++ b/default-plugins/tab-bar/src/main.rs @@ -65,7 +65,7 @@ impl ZellijPlugin for State { self.mode_info.session_name.as_deref(), all_tabs, active_tab_index, - cols, + cols.saturating_sub(1), self.mode_info.palette, self.mode_info.capabilities, ); diff --git a/default-plugins/tab-bar/src/tab.rs b/default-plugins/tab-bar/src/tab.rs index 9edfba13..dfa5ab77 100644 --- a/default-plugins/tab-bar/src/tab.rs +++ b/default-plugins/tab-bar/src/tab.rs @@ -5,7 +5,7 @@ use zellij_tile_utils::style; pub fn active_tab(text: String, palette: Palette, separator: &str) -> LinePart { let left_separator = style!(palette.cyan, palette.green).paint(separator); - let tab_text_len = text.chars().count() + 4; // 2 for left and right separators, 2 for the text padding + let tab_text_len = text.chars().count() + 2 + separator.chars().count() * 2; // 2 for left and right separators, 2 for the text padding let tab_styled_text = style!(palette.black, palette.green) .bold() .paint(format!(" {} ", text)); @@ -22,7 +22,7 @@ pub fn active_tab(text: String, palette: Palette, separator: &str) -> LinePart { pub fn non_active_tab(text: String, palette: Palette, separator: &str) -> LinePart { let left_separator = style!(palette.cyan, palette.fg).paint(separator); - let tab_text_len = text.chars().count() + 4; // 2 for left and right separators, 2 for the padding + let tab_text_len = text.chars().count() + 2 + separator.chars().count() * 2; // 2 for left and right separators, 2 for the text padding let tab_styled_text = style!(palette.black, palette.fg) .bold() .paint(format!(" {} ", text)); From 26a970a7d8a0f3703124ab3bb57cff4d296a2e33 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Thu, 9 Sep 2021 16:40:14 +0200 Subject: [PATCH 13/42] docs(changelog): tab bar fixes --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3da60017..44a7c65d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Fix bug where wide chars would mess up pane titles (https://github.com/zellij-org/zellij/pull/698) * Fix various borderless-frame in viewport bugs (https://github.com/zellij-org/zellij/pull/697) * Fix example configuration file (https://github.com/zellij-org/zellij/pull/693) +* Fix various tab bar responsiveness issues (https://github.com/zellij-org/zellij/pull/703) * Allow plugins to run system commands (https://github.com/zellij-org/zellij/pull/666) * This has also added a temporary new permission flag that needs to be specified in the layout. This is a breaking change: ```yaml @@ -30,6 +31,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) _allow_exec_host_cmd: true ``` + ## [0.16.0] - 2021-08-31 * Plugins don't crash zellij anymore on receiving mouse events (https://github.com/zellij-org/zellij/pull/620) * A universal logging system has been implemented (https://github.com/zellij-org/zellij/pull/592) From 4f94f95c90a0b5c2cb3992533cec40cc55f05983 Mon Sep 17 00:00:00 2001 From: spacemaison Date: Fri, 10 Sep 2021 08:35:06 -0700 Subject: [PATCH 14/42] feat(cwd-pane): Keeping the cwd when opening new panes (#691) * feat(cwd-pane): Add a new trait to get the cwd of a given pid Co-authored-by: Quentin Rasmont * feat(cwd-pane): Allow for setting the cwd when spawning a new terminal Co-authored-by: Quentin Rasmont * feat(cwd-pane): Add an active_pane field to the Pty struct Co-authored-by: Quentin Rasmont * feat(cwd-pane): Update Pty with Tab's active pane id Co-authored-by: Quentin Rasmont * feat(cwd-pane): Refactor spawn_terminal to use cwd by default Co-authored-by: Quentin Rasmont * feat(cwd-pane): Fix tests and lints Co-authored-by: Quentin Rasmont * feat(cwd-pane): Fix formatting * feat(cwd-pane): Refactor child pid fetching to handle errors better Instead of panicking when transfering the process id of the forked child command we just return an empty process id. * feat(cwd-pane): Add non Linux/MacOS targets for the get_cwd method. This will allow Zellij to compile on non Linux/MacOS targets without having an inherited cwd. * feat(cwd-pane): Refactor spawn_terminal method to use ChildId struct. The spawn_terminal methods been refactored to use the ChildId struct in order to clarify what the Pid's returned by it are. The documentation for the ChildId struct was improved as well. * feat(cwd-pane): Fix tests/lints Co-authored-by: Jesse Tuchsen Co-authored-by: Quentin Rasmont --- Cargo.lock | 57 +++++--- zellij-server/Cargo.toml | 4 + zellij-server/src/os_input_output.rs | 177 ++++++++++++++++++------- zellij-server/src/pty.rs | 63 ++++++--- zellij-server/src/tab.rs | 115 ++++++++-------- zellij-server/src/unit/screen_tests.rs | 8 +- zellij-server/src/unit/tab_tests.rs | 8 +- zellij-utils/src/errors.rs | 1 + zellij-utils/src/input/command.rs | 5 + 9 files changed, 295 insertions(+), 143 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6c2cdcd3..7bd709ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,11 +4,11 @@ version = 3 [[package]] name = "addr2line" -version = "0.16.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd" +checksum = "e7a2e47a1fbe209ee101dd6d61285226744c6c8d3c21c8dc878ba6cb9f467f3a" dependencies = [ - "gimli 0.25.0", + "gimli 0.24.0", ] [[package]] @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" dependencies = [ "memchr", ] @@ -227,16 +227,16 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.61" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01" +checksum = "4717cfcbfaa661a0fd48f8453951837ae7e8f81e481fbb136e3202d72805a744" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", - "object 0.26.0", + "object 0.24.0", "rustc-demangle", ] @@ -621,6 +621,26 @@ dependencies = [ "syn", ] +[[package]] +name = "darwin-libproc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc629b7cf42586fee31dae31f9ab73fa5ff5f0170016aa61be5fcbc12a90c516" +dependencies = [ + "darwin-libproc-sys", + "libc", + "memchr", +] + +[[package]] +name = "darwin-libproc-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef0aa083b94c54aa4cfd9bbfd37856714c139d1dc511af80270558c7ba3b4816" +dependencies = [ + "libc", +] + [[package]] name = "derivative" version = "2.2.0" @@ -897,9 +917,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.25.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" +checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" [[package]] name = "gloo-timers" @@ -1188,9 +1208,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.4.0" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "memmap2" @@ -1325,12 +1345,9 @@ dependencies = [ [[package]] name = "object" -version = "0.26.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55827317fb4c08822499848a14237d2874d6f139828893017237e7ab93eb386" -dependencies = [ - "memchr", -] +checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170" [[package]] name = "once_cell" @@ -1675,9 +1692,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.4" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759" dependencies = [ "aho-corasick", "memchr", @@ -2652,9 +2669,11 @@ dependencies = [ "ansi_term 0.12.1", "async-trait", "base64", + "byteorder", "cassowary", "chrono", "daemonize", + "darwin-libproc", "insta", "log", "serde_json", diff --git a/zellij-server/Cargo.toml b/zellij-server/Cargo.toml index fe56b742..1c8c986b 100644 --- a/zellij-server/Cargo.toml +++ b/zellij-server/Cargo.toml @@ -12,6 +12,7 @@ license = "MIT" ansi_term = "0.12.1" async-trait = "0.1.50" base64 = "0.13.0" +byteorder = "1.4.3" daemonize = "0.4.1" serde_json = "1.0" unicode-width = "0.1.8" @@ -23,6 +24,9 @@ log = "0.4.14" typetag = "0.1.7" chrono = "0.4.19" +[target.'cfg(target_os = "macos")'.dependencies] +darwin-libproc = "0.2.0" + [dev-dependencies] insta = "1.6.0" diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs index 05980805..e24b24b4 100644 --- a/zellij-server/src/os_input_output.rs +++ b/zellij-server/src/os_input_output.rs @@ -1,4 +1,8 @@ +#[cfg(target_os = "macos")] +use darwin_libproc; + use std::env; +use std::fs; use std::os::unix::io::RawFd; use std::os::unix::process::CommandExt; use std::path::PathBuf; @@ -10,7 +14,7 @@ use zellij_utils::{async_std, interprocess, libc, nix, signal_hook, zellij_tile} use async_std::fs::File as AsyncFile; use async_std::os::unix::io::FromRawFd; use interprocess::local_socket::LocalSocketStream; -use nix::pty::{forkpty, Winsize}; +use nix::pty::{forkpty, ForkptyResult, Winsize}; use nix::sys::signal::{kill, Signal}; use nix::sys::termios; use nix::sys::wait::waitpid; @@ -29,6 +33,7 @@ use zellij_utils::{ use async_std::io::ReadExt; pub use async_trait::async_trait; +use byteorder::{BigEndian, ByteOrder}; pub use nix::unistd::Pid; @@ -92,44 +97,94 @@ fn handle_command_exit(mut child: Child) { } } +fn handle_fork_pty( + fork_pty_res: ForkptyResult, + cmd: RunCommand, + parent_fd: RawFd, + child_fd: RawFd, +) -> (RawFd, ChildId) { + let pid_primary = fork_pty_res.master; + let (pid_secondary, pid_shell) = match fork_pty_res.fork_result { + ForkResult::Parent { child } => { + let pid_shell = read_from_pipe(parent_fd, child_fd); + (child, pid_shell) + } + ForkResult::Child => { + let child = unsafe { + let command = &mut Command::new(cmd.command); + if let Some(current_dir) = cmd.cwd { + command.current_dir(current_dir); + } + command + .args(&cmd.args) + .pre_exec(|| -> std::io::Result<()> { + // this is the "unsafe" part, for more details please see: + // https://doc.rust-lang.org/std/os/unix/process/trait.CommandExt.html#notes-and-safety + unistd::setpgid(Pid::from_raw(0), Pid::from_raw(0)) + .expect("failed to create a new process group"); + Ok(()) + }) + .spawn() + .expect("failed to spawn") + }; + unistd::tcsetpgrp(0, Pid::from_raw(child.id() as i32)) + .expect("faled to set child's forceground process group"); + write_to_pipe(child.id(), parent_fd, child_fd); + handle_command_exit(child); + ::std::process::exit(0); + } + }; + + ( + pid_primary, + ChildId { + primary: pid_secondary, + shell: pid_shell.map(|pid| Pid::from_raw(pid as i32)), + }, + ) +} + /// Spawns a new terminal from the parent terminal with [`termios`](termios::Termios) /// `orig_termios`. /// -fn handle_terminal(cmd: RunCommand, orig_termios: termios::Termios) -> (RawFd, Pid) { - let (pid_primary, pid_secondary): (RawFd, Pid) = { - match forkpty(None, Some(&orig_termios)) { - Ok(fork_pty_res) => { - let pid_primary = fork_pty_res.master; - let pid_secondary = match fork_pty_res.fork_result { - ForkResult::Parent { child } => child, - ForkResult::Child => { - let child = unsafe { - Command::new(cmd.command) - .args(&cmd.args) - .pre_exec(|| -> std::io::Result<()> { - // this is the "unsafe" part, for more details please see: - // https://doc.rust-lang.org/std/os/unix/process/trait.CommandExt.html#notes-and-safety - unistd::setpgid(Pid::from_raw(0), Pid::from_raw(0)) - .expect("failed to create a new process group"); - Ok(()) - }) - .spawn() - .expect("failed to spawn") - }; - unistd::tcsetpgrp(0, Pid::from_raw(child.id() as i32)) - .expect("faled to set child's forceground process group"); - handle_command_exit(child); - ::std::process::exit(0); - } - }; - (pid_primary, pid_secondary) - } - Err(e) => { - panic!("failed to fork {:?}", e); - } +fn handle_terminal(cmd: RunCommand, orig_termios: termios::Termios) -> (RawFd, ChildId) { + // Create a pipe to allow the child the communicate the shell's pid to it's + // parent. + let (parent_fd, child_fd) = unistd::pipe().expect("failed to create pipe"); + match forkpty(None, Some(&orig_termios)) { + Ok(fork_pty_res) => handle_fork_pty(fork_pty_res, cmd, parent_fd, child_fd), + Err(e) => { + panic!("failed to fork {:?}", e); } - }; - (pid_primary, pid_secondary) + } +} + +/// Write to a pipe given both file descriptors +fn write_to_pipe(data: u32, parent_fd: RawFd, child_fd: RawFd) { + let mut buff = [0; 4]; + BigEndian::write_u32(&mut buff, data); + if unistd::close(parent_fd).is_err() { + return; + } + if unistd::write(child_fd, &buff).is_err() { + return; + } + unistd::close(child_fd).unwrap_or_default(); +} + +/// Read from a pipe given both file descriptors +fn read_from_pipe(parent_fd: RawFd, child_fd: RawFd) -> Option { + let mut buffer = [0; 4]; + if unistd::close(child_fd).is_err() { + return None; + } + if unistd::read(parent_fd, &mut buffer).is_err() { + return None; + } + if unistd::close(parent_fd).is_err() { + return None; + } + Some(u32::from_be_bytes(buffer)) } /// If a [`TerminalAction::OpenFile(file)`] is given, the text editor specified by environment variable `EDITOR` @@ -145,11 +200,11 @@ fn handle_terminal(cmd: RunCommand, orig_termios: termios::Termios) -> (RawFd, P /// This function will panic if both the `EDITOR` and `VISUAL` environment variables are not /// set. pub fn spawn_terminal( - terminal_action: Option, + terminal_action: TerminalAction, orig_termios: termios::Termios, -) -> (RawFd, Pid) { +) -> (RawFd, ChildId) { let cmd = match terminal_action { - Some(TerminalAction::OpenFile(file_to_open)) => { + TerminalAction::OpenFile(file_to_open) => { if env::var("EDITOR").is_err() && env::var("VISUAL").is_err() { panic!("Can't edit files if an editor is not defined. To fix: define the EDITOR or VISUAL environment variables with the path to your editor (eg. /usr/bin/vim)"); } @@ -160,15 +215,13 @@ pub fn spawn_terminal( .into_os_string() .into_string() .expect("Not valid Utf8 Encoding")]; - RunCommand { command, args } - } - Some(TerminalAction::RunCommand(command)) => command, - None => { - let command = - PathBuf::from(env::var("SHELL").expect("Could not find the SHELL variable")); - let args = vec![]; - RunCommand { command, args } + RunCommand { + command, + args, + cwd: None, + } } + TerminalAction::RunCommand(command) => command, }; handle_terminal(cmd, orig_termios) @@ -214,8 +267,10 @@ impl AsyncReader for RawFdAsyncReader { pub trait ServerOsApi: Send + Sync { /// Sets the size of the terminal associated to file descriptor `fd`. fn set_terminal_size_using_fd(&self, fd: RawFd, cols: u16, rows: u16); - /// Spawn a new terminal, with a terminal action. - fn spawn_terminal(&self, terminal_action: Option) -> (RawFd, Pid); + /// Spawn a new terminal, with a terminal action. The returned tuple contains the master file + /// descriptor of the forked psuedo terminal and a [ChildId] struct containing process id's for + /// the forked child process. + fn spawn_terminal(&self, terminal_action: TerminalAction) -> (RawFd, ChildId); /// Read bytes from the standard output of the virtual terminal referred to by `fd`. fn read_from_tty_stdout(&self, fd: RawFd, buf: &mut [u8]) -> Result; /// Creates an `AsyncReader` that can be used to read from `fd` in an async context @@ -247,6 +302,8 @@ pub trait ServerOsApi: Send + Sync { /// Update the receiver socket for the client fn update_receiver(&mut self, stream: LocalSocketStream); fn load_palette(&self) -> Palette; + /// Returns the current working directory for a given pid + fn get_cwd(&self, pid: Pid) -> Option; } impl ServerOsApi for ServerOsInputOutput { @@ -255,7 +312,7 @@ impl ServerOsApi for ServerOsInputOutput { set_terminal_size_using_fd(fd, cols, rows); } } - fn spawn_terminal(&self, terminal_action: Option) -> (RawFd, Pid) { + fn spawn_terminal(&self, terminal_action: TerminalAction) -> (RawFd, ChildId) { let orig_termios = self.orig_termios.lock().unwrap(); spawn_terminal(terminal_action, orig_termios.clone()) } @@ -336,6 +393,18 @@ impl ServerOsApi for ServerOsInputOutput { fn load_palette(&self) -> Palette { default_palette() } + #[cfg(target_os = "macos")] + fn get_cwd(&self, pid: Pid) -> Option { + darwin_libproc::pid_cwd(pid.as_raw()).ok() + } + #[cfg(target_os = "linux")] + fn get_cwd(&self, pid: Pid) -> Option { + fs::read_link(format!("/proc/{}/cwd", pid)).ok() + } + #[cfg(all(not(target_os = "linux"), not(target_os = "macos")))] + fn get_cwd(&self, _pid: Pid) -> Option { + None + } } impl Clone for Box { @@ -353,3 +422,13 @@ pub fn get_server_os_input() -> Result { send_instructions_to_client: Arc::new(Mutex::new(None)), }) } + +/// Process id's for forked terminals +#[derive(Debug)] +pub struct ChildId { + /// Primary process id of a forked terminal + pub primary: Pid, + /// Process id of the command running inside the forked terminal, usually a shell. The primary + /// field is it's parent process id. + pub shell: Option, +} diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index e28729b7..c6857258 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -1,5 +1,5 @@ use crate::{ - os_input_output::{AsyncReader, Pid, ServerOsApi}, + os_input_output::{AsyncReader, ChildId, ServerOsApi}, panes::PaneId, screen::ScreenInstruction, thread_bus::{Bus, ThreadSenders}, @@ -12,14 +12,16 @@ use async_std::{ }; use std::{ collections::HashMap, + env, os::unix::io::RawFd, + path::PathBuf, time::{Duration, Instant}, }; use zellij_utils::{ async_std, errors::{get_current_ctx, ContextType, PtyContext}, input::{ - command::TerminalAction, + command::{RunCommand, TerminalAction}, layout::{Layout, LayoutFromYaml, Run, TabLayout}, }, logging::debug_to_file, @@ -33,6 +35,7 @@ pub(crate) enum PtyInstruction { SpawnTerminal(Option), SpawnTerminalVertically(Option), SpawnTerminalHorizontally(Option), + UpdateActivePane(Option), NewTab(Option, Option), ClosePane(PaneId), CloseTab(Vec), @@ -45,6 +48,7 @@ impl From<&PtyInstruction> for PtyContext { PtyInstruction::SpawnTerminal(_) => PtyContext::SpawnTerminal, PtyInstruction::SpawnTerminalVertically(_) => PtyContext::SpawnTerminalVertically, PtyInstruction::SpawnTerminalHorizontally(_) => PtyContext::SpawnTerminalHorizontally, + PtyInstruction::UpdateActivePane(_) => PtyContext::UpdateActivePane, PtyInstruction::ClosePane(_) => PtyContext::ClosePane, PtyInstruction::CloseTab(_) => PtyContext::CloseTab, PtyInstruction::NewTab(..) => PtyContext::NewTab, @@ -54,8 +58,9 @@ impl From<&PtyInstruction> for PtyContext { } pub(crate) struct Pty { + pub active_pane: Option, pub bus: Bus, - pub id_to_child_pid: HashMap, + pub id_to_child_pid: HashMap, debug_to_file: bool, task_handles: HashMap>, } @@ -86,6 +91,9 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: LayoutFromYaml) { .send_to_screen(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid))) .unwrap(); } + PtyInstruction::UpdateActivePane(pane_id) => { + pty.set_active_pane(pane_id); + } PtyInstruction::NewTab(terminal_action, tab_layout) => { let merged_layout = layout.template.clone().insert_tab_layout(tab_layout); pty.spawn_terminals_for_layout(merged_layout.into(), terminal_action.clone()); @@ -208,14 +216,30 @@ fn stream_terminal_bytes( impl Pty { pub fn new(bus: Bus, debug_to_file: bool) -> Self { Pty { + active_pane: None, bus, id_to_child_pid: HashMap::new(), debug_to_file, task_handles: HashMap::new(), } } + pub fn get_default_terminal(&self) -> TerminalAction { + TerminalAction::RunCommand(RunCommand { + args: vec![], + command: PathBuf::from(env::var("SHELL").expect("Could not find the SHELL variable")), + cwd: self + .active_pane + .and_then(|pane| match pane { + PaneId::Plugin(..) => None, + PaneId::Terminal(id) => self.id_to_child_pid.get(&id).and_then(|id| id.shell), + }) + .and_then(|id| self.bus.os_input.as_ref().map(|input| input.get_cwd(id))) + .flatten(), + }) + } pub fn spawn_terminal(&mut self, terminal_action: Option) -> RawFd { - let (pid_primary, pid_secondary): (RawFd, Pid) = self + let terminal_action = terminal_action.unwrap_or_else(|| self.get_default_terminal()); + let (pid_primary, child_id): (RawFd, ChildId) = self .bus .os_input .as_mut() @@ -228,7 +252,7 @@ impl Pty { self.debug_to_file, ); self.task_handles.insert(pid_primary, task_handle); - self.id_to_child_pid.insert(pid_primary, pid_secondary); + self.id_to_child_pid.insert(pid_primary, child_id); pid_primary } pub fn spawn_terminals_for_layout( @@ -236,29 +260,26 @@ impl Pty { layout: Layout, default_shell: Option, ) { + let default_shell = default_shell.unwrap_or_else(|| self.get_default_terminal()); let extracted_run_instructions = layout.extract_run_instructions(); let mut new_pane_pids = vec![]; for run_instruction in extracted_run_instructions { match run_instruction { Some(Run::Command(command)) => { let cmd = TerminalAction::RunCommand(command); - let (pid_primary, pid_secondary): (RawFd, Pid) = self - .bus - .os_input - .as_mut() - .unwrap() - .spawn_terminal(Some(cmd)); - self.id_to_child_pid.insert(pid_primary, pid_secondary); + let (pid_primary, child_id): (RawFd, ChildId) = + self.bus.os_input.as_mut().unwrap().spawn_terminal(cmd); + self.id_to_child_pid.insert(pid_primary, child_id); new_pane_pids.push(pid_primary); } None => { - let (pid_primary, pid_secondary): (RawFd, Pid) = self + let (pid_primary, child_id): (RawFd, ChildId) = self .bus .os_input .as_mut() .unwrap() .spawn_terminal(default_shell.clone()); - self.id_to_child_pid.insert(pid_primary, pid_secondary); + self.id_to_child_pid.insert(pid_primary, child_id); new_pane_pids.push(pid_primary); } // Investigate moving plugin loading to here. @@ -285,10 +306,15 @@ impl Pty { pub fn close_pane(&mut self, id: PaneId) { match id { PaneId::Terminal(id) => { - let child_pid = self.id_to_child_pid.remove(&id).unwrap(); + let pids = self.id_to_child_pid.remove(&id).unwrap(); let handle = self.task_handles.remove(&id).unwrap(); task::block_on(async { - self.bus.os_input.as_mut().unwrap().kill(child_pid).unwrap(); + self.bus + .os_input + .as_mut() + .unwrap() + .kill(pids.primary) + .unwrap(); let timeout = Duration::from_millis(100); match async_timeout(timeout, handle.cancel()).await { Ok(_) => {} @@ -297,7 +323,7 @@ impl Pty { .os_input .as_mut() .unwrap() - .force_kill(child_pid) + .force_kill(pids.primary) .unwrap(); } }; @@ -315,6 +341,9 @@ impl Pty { self.close_pane(id); }); } + pub fn set_active_pane(&mut self, pane_id: Option) { + self.active_pane = pane_id; + } } impl Drop for Pty { diff --git a/zellij-server/src/tab.rs b/zellij-server/src/tab.rs index ba8a8c88..dafb2863 100644 --- a/zellij-server/src/tab.rs +++ b/zellij-server/src/tab.rs @@ -295,6 +295,13 @@ impl Tab { } } + fn set_active_terminal(&mut self, pane_id: Option) { + self.active_terminal = pane_id; + self.senders + .send_to_pty(PtyInstruction::UpdateActivePane(self.active_terminal)) + .unwrap(); + } + pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec, tab_index: usize) { // TODO: this should be an attribute on Screen instead of full_screen_ws let free_space = PaneGeom::default(); @@ -393,7 +400,7 @@ impl Tab { self.set_pane_frames(self.draw_pane_frames); // This is the end of the nasty viewport hack... // FIXME: Active / new / current terminal, should be pane - self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next(); + self.set_active_terminal(self.panes.iter().map(|(id, _)| id.to_owned()).next()); self.render(); } pub fn new_pane(&mut self, pid: PaneId) { @@ -466,7 +473,7 @@ impl Tab { } } } - self.active_terminal = Some(pid); + self.set_active_terminal(Some(pid)); self.render(); } pub fn horizontal_split(&mut self, pid: PaneId) { @@ -495,7 +502,7 @@ impl Tab { ); active_pane.set_geom(top_winsize); self.panes.insert(pid, Box::new(new_terminal)); - self.active_terminal = Some(pid); + self.set_active_terminal(Some(pid)); self.relayout_tab(Direction::Vertical); self.render(); } @@ -524,7 +531,7 @@ impl Tab { active_pane.set_geom(left_winsize); self.panes.insert(pid, Box::new(new_terminal)); } - self.active_terminal = Some(pid); + self.set_active_terminal(Some(pid)); self.relayout_tab(Direction::Horizontal); self.render(); } @@ -1757,16 +1764,16 @@ impl Tab { } let active_terminal_id = self.get_active_pane_id().unwrap(); let terminal_ids: Vec = self.get_selectable_panes().map(|(&pid, _)| pid).collect(); // TODO: better, no allocations - let first_terminal = terminal_ids.get(0).unwrap(); let active_terminal_id_position = terminal_ids .iter() .position(|id| id == &active_terminal_id) .unwrap(); - if let Some(next_terminal) = terminal_ids.get(active_terminal_id_position + 1) { - self.active_terminal = Some(*next_terminal); - } else { - self.active_terminal = Some(*first_terminal); - } + let active_terminal = terminal_ids + .get(active_terminal_id_position + 1) + .or_else(|| terminal_ids.get(0)) + .copied(); + + self.set_active_terminal(active_terminal); self.render(); } pub fn focus_next_pane(&mut self) { @@ -1785,16 +1792,17 @@ impl Tab { a_pane.y().cmp(&b_pane.y()) } }); - let first_pane = panes.get(0).unwrap(); let active_pane_position = panes .iter() .position(|(id, _)| *id == &active_pane_id) // TODO: better .unwrap(); - if let Some(next_pane) = panes.get(active_pane_position + 1) { - self.active_terminal = Some(*next_pane.0); - } else { - self.active_terminal = Some(*first_pane.0); - } + + let active_terminal = panes + .get(active_pane_position + 1) + .or_else(|| panes.get(0)) + .map(|p| *p.0); + + self.set_active_terminal(active_terminal); self.render(); } pub fn focus_previous_pane(&mut self) { @@ -1818,11 +1826,13 @@ impl Tab { .iter() .position(|(id, _)| *id == &active_pane_id) // TODO: better .unwrap(); - if active_pane_position == 0 { - self.active_terminal = Some(*last_pane.0); + + let active_terminal = if active_pane_position == 0 { + Some(*last_pane.0) } else { - self.active_terminal = Some(*panes.get(active_pane_position - 1).unwrap().0); - } + Some(*panes.get(active_pane_position - 1).unwrap().0) + }; + self.set_active_terminal(active_terminal); self.render(); } // returns a boolean that indicates whether the focus moved @@ -1834,7 +1844,7 @@ impl Tab { return false; } let active_terminal = self.get_active_pane(); - if let Some(active) = active_terminal { + let updated_active_terminal = if let Some(active) = active_terminal { let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() @@ -1853,17 +1863,16 @@ impl Tab { let next_active_pane = self.panes.get_mut(&p).unwrap(); next_active_pane.set_should_render(true); - self.active_terminal = Some(p); + self.set_active_terminal(Some(p)); self.render(); return true; } - None => { - self.active_terminal = Some(active.pid()); - } + None => Some(active.pid()), } } else { - self.active_terminal = Some(active_terminal.unwrap().pid()); - } + Some(active_terminal.unwrap().pid()) + }; + self.set_active_terminal(updated_active_terminal); false } pub fn move_focus_down(&mut self) { @@ -1874,7 +1883,7 @@ impl Tab { return; } let active_terminal = self.get_active_pane(); - if let Some(active) = active_terminal { + let updated_active_terminal = if let Some(active) = active_terminal { let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() @@ -1893,15 +1902,14 @@ impl Tab { let next_active_pane = self.panes.get_mut(&p).unwrap(); next_active_pane.set_should_render(true); - self.active_terminal = Some(p); - } - None => { - self.active_terminal = Some(active.pid()); + Some(p) } + None => Some(active.pid()), } } else { - self.active_terminal = Some(active_terminal.unwrap().pid()); - } + Some(active_terminal.unwrap().pid()) + }; + self.set_active_terminal(updated_active_terminal); self.render(); } pub fn move_focus_up(&mut self) { @@ -1912,7 +1920,7 @@ impl Tab { return; } let active_terminal = self.get_active_pane(); - if let Some(active) = active_terminal { + let updated_active_terminal = if let Some(active) = active_terminal { let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() @@ -1931,15 +1939,14 @@ impl Tab { let next_active_pane = self.panes.get_mut(&p).unwrap(); next_active_pane.set_should_render(true); - self.active_terminal = Some(p); - } - None => { - self.active_terminal = Some(active.pid()); + Some(p) } + None => Some(active.pid()), } } else { - self.active_terminal = Some(active_terminal.unwrap().pid()); - } + Some(active_terminal.unwrap().pid()) + }; + self.set_active_terminal(updated_active_terminal); self.render(); } // returns a boolean that indicates whether the focus moved @@ -1951,7 +1958,7 @@ impl Tab { return false; } let active_terminal = self.get_active_pane(); - if let Some(active) = active_terminal { + let updated_active_terminal = if let Some(active) = active_terminal { let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() @@ -1970,17 +1977,16 @@ impl Tab { let next_active_pane = self.panes.get_mut(&p).unwrap(); next_active_pane.set_should_render(true); - self.active_terminal = Some(p); + self.set_active_terminal(Some(p)); self.render(); return true; } - None => { - self.active_terminal = Some(active.pid()); - } + None => Some(active.pid()), } } else { - self.active_terminal = Some(active_terminal.unwrap().pid()); - } + Some(active_terminal.unwrap().pid()) + }; + self.set_active_terminal(updated_active_terminal); false } fn horizontal_borders(&self, terminals: &[PaneId]) -> HashSet { @@ -1999,6 +2005,7 @@ impl Tab { borders }) } + fn panes_to_the_left_between_aligning_borders(&self, id: PaneId) -> Option> { if let Some(terminal) = self.panes.get(&id) { let upper_close_border = terminal.y(); @@ -2125,7 +2132,7 @@ impl Tab { if let Some(pane) = self.panes.get_mut(&id) { pane.set_selectable(selectable); if self.get_active_pane_id() == Some(id) && !selectable { - self.active_terminal = self.next_active_pane(&self.get_pane_ids()) + self.set_active_terminal(self.next_active_pane(&self.get_pane_ids())); } } self.render(); @@ -2146,7 +2153,7 @@ impl Tab { self.panes.remove(&id); if self.active_terminal == Some(id) { let next_active_pane = self.next_active_pane(&panes); - self.active_terminal = next_active_pane; + self.set_active_terminal(next_active_pane); } self.relayout_tab(Direction::Horizontal); return; @@ -2158,7 +2165,7 @@ impl Tab { self.panes.remove(&id); if self.active_terminal == Some(id) { let next_active_pane = self.next_active_pane(&panes); - self.active_terminal = next_active_pane; + self.set_active_terminal(next_active_pane); } self.relayout_tab(Direction::Horizontal); return; @@ -2170,7 +2177,7 @@ impl Tab { self.panes.remove(&id); if self.active_terminal == Some(id) { let next_active_pane = self.next_active_pane(&panes); - self.active_terminal = next_active_pane; + self.set_active_terminal(next_active_pane); } self.relayout_tab(Direction::Vertical); return; @@ -2182,7 +2189,7 @@ impl Tab { self.panes.remove(&id); if self.active_terminal == Some(id) { let next_active_pane = self.next_active_pane(&panes); - self.active_terminal = next_active_pane; + self.set_active_terminal(next_active_pane); } self.relayout_tab(Direction::Vertical); return; @@ -2304,7 +2311,7 @@ impl Tab { } fn focus_pane_at(&mut self, point: &Position) { if let Some(clicked_pane) = self.get_pane_id_at(point) { - self.active_terminal = Some(clicked_pane); + self.set_active_terminal(Some(clicked_pane)); self.render(); } } diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 95c1b98d..aeea671b 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -1,10 +1,11 @@ use super::{Screen, ScreenInstruction}; use crate::zellij_tile::data::{ModeInfo, Palette}; use crate::{ - os_input_output::{AsyncReader, Pid, ServerOsApi}, + os_input_output::{AsyncReader, ChildId, Pid, ServerOsApi}, thread_bus::Bus, SessionState, }; +use std::path::PathBuf; use std::sync::{Arc, RwLock}; use zellij_utils::input::command::TerminalAction; use zellij_utils::input::layout::LayoutTemplate; @@ -28,7 +29,7 @@ impl ServerOsApi for FakeInputOutput { fn set_terminal_size_using_fd(&self, _fd: RawFd, _cols: u16, _rows: u16) { // noop } - fn spawn_terminal(&self, _file_to_open: Option) -> (RawFd, Pid) { + fn spawn_terminal(&self, _file_to_open: TerminalAction) -> (RawFd, ChildId) { unimplemented!() } fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result { @@ -73,6 +74,9 @@ impl ServerOsApi for FakeInputOutput { fn load_palette(&self) -> Palette { unimplemented!() } + fn get_cwd(&self, _pid: Pid) -> Option { + unimplemented!() + } } fn create_new_screen(size: Size) -> Screen { diff --git a/zellij-server/src/unit/tab_tests.rs b/zellij-server/src/unit/tab_tests.rs index ea96df62..de5c1067 100644 --- a/zellij-server/src/unit/tab_tests.rs +++ b/zellij-server/src/unit/tab_tests.rs @@ -1,11 +1,12 @@ use super::Tab; use crate::zellij_tile::data::{ModeInfo, Palette}; use crate::{ - os_input_output::{AsyncReader, Pid, ServerOsApi}, + os_input_output::{AsyncReader, ChildId, Pid, ServerOsApi}, panes::PaneId, thread_bus::ThreadSenders, SessionState, }; +use std::path::PathBuf; use std::sync::{Arc, RwLock}; use zellij_utils::input::layout::LayoutTemplate; use zellij_utils::pane_size::Size; @@ -27,7 +28,7 @@ impl ServerOsApi for FakeInputOutput { fn set_terminal_size_using_fd(&self, _fd: RawFd, _cols: u16, _rows: u16) { // noop } - fn spawn_terminal(&self, _file_to_open: Option) -> (RawFd, Pid) { + fn spawn_terminal(&self, _file_to_open: TerminalAction) -> (RawFd, ChildId) { unimplemented!() } fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result { @@ -72,6 +73,9 @@ impl ServerOsApi for FakeInputOutput { fn load_palette(&self) -> Palette { unimplemented!() } + fn get_cwd(&self, _pid: Pid) -> Option { + unimplemented!() + } } fn create_new_tab(size: Size) -> Tab { diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index 65a54c56..87c3bb4d 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -237,6 +237,7 @@ pub enum PtyContext { SpawnTerminal, SpawnTerminalVertically, SpawnTerminalHorizontally, + UpdateActivePane, NewTab, ClosePane, CloseTab, diff --git a/zellij-utils/src/input/command.rs b/zellij-utils/src/input/command.rs index 2054f208..debd5bc5 100644 --- a/zellij-utils/src/input/command.rs +++ b/zellij-utils/src/input/command.rs @@ -15,6 +15,8 @@ pub struct RunCommand { pub command: PathBuf, #[serde(default)] pub args: Vec, + #[serde(default)] + pub cwd: Option, } /// Intermediate representation @@ -25,6 +27,8 @@ pub struct RunCommandAction { #[serde(default)] pub args: Vec, #[serde(default)] + pub cwd: Option, + #[serde(default)] pub direction: Option, } @@ -33,6 +37,7 @@ impl From for RunCommand { RunCommand { command: action.command, args: action.args, + cwd: action.cwd, } } } From 8888476885d80a5727ac9c23f2e4c288b75aadb8 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Fri, 10 Sep 2021 17:36:38 +0200 Subject: [PATCH 15/42] docs(changelog): document cwd fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44a7c65d..e5c2392f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] +* New panes/tabs now open in CWD of focused pane (https://github.com/zellij-org/zellij/pull/691) * Fix bug when opening new tab the new pane's viewport would sometimes be calculated incorrectly (https://github.com/zellij-org/zellij/pull/683) * Fix bug when in some cases closing a tab would not clear the previous pane's contents (https://github.com/zellij-org/zellij/pull/684) * Fix bug where tabs would sometimes be created with the wrong index in their name (https://github.com/zellij-org/zellij/pull/686) From aae9c9c807d5990fb3cdc6fcb3fd0bb8c3c3c7f2 Mon Sep 17 00:00:00 2001 From: Paulo Coelho <9609090+prscoelho@users.noreply.github.com> Date: Sun, 12 Sep 2021 19:29:07 +0100 Subject: [PATCH 16/42] Calculate width with unicode-width in tab-bar and utils (#709) * fix(tab-bar): calculate string width using unicode-width * fix(utils): calculate ansi_len using unicode-width --- Cargo.lock | 2 ++ default-plugins/tab-bar/Cargo.toml | 1 + default-plugins/tab-bar/src/line.rs | 7 ++++--- default-plugins/tab-bar/src/tab.rs | 5 +++-- zellij-utils/Cargo.toml | 1 + zellij-utils/src/shared.rs | 6 ++---- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7bd709ac..8f3d4761 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1998,6 +1998,7 @@ version = "0.1.0" dependencies = [ "ansi_term 0.12.1", "colored", + "unicode-width", "zellij-tile", "zellij-tile-utils", ] @@ -2726,6 +2727,7 @@ dependencies = [ "strum", "tempfile", "termion", + "unicode-width", "vte 0.10.1", "zellij-tile", ] diff --git a/default-plugins/tab-bar/Cargo.toml b/default-plugins/tab-bar/Cargo.toml index dd91bdb5..7fd6593d 100644 --- a/default-plugins/tab-bar/Cargo.toml +++ b/default-plugins/tab-bar/Cargo.toml @@ -8,5 +8,6 @@ license = "MIT" [dependencies] colored = "2" ansi_term = "0.12" +unicode-width = "0.1.8" zellij-tile = { path = "../../zellij-tile" } zellij-tile-utils = { path = "../../zellij-tile-utils" } \ No newline at end of file diff --git a/default-plugins/tab-bar/src/line.rs b/default-plugins/tab-bar/src/line.rs index be5623ff..af78b0fc 100644 --- a/default-plugins/tab-bar/src/line.rs +++ b/default-plugins/tab-bar/src/line.rs @@ -1,4 +1,5 @@ use ansi_term::ANSIStrings; +use unicode_width::UnicodeWidthStr; use crate::{LinePart, ARROW_SEPARATOR}; use zellij_tile::prelude::*; @@ -100,7 +101,7 @@ fn left_more_message(tab_count_to_the_left: usize, palette: Palette, separator: }; // 238 // chars length plus separator length on both sides - let more_text_len = more_text.chars().count() + 2 * separator.chars().count(); + let more_text_len = more_text.width() + 2 * separator.width(); let left_separator = style!(palette.cyan, palette.orange).paint(separator); let more_styled_text = style!(palette.black, palette.orange) .bold() @@ -130,7 +131,7 @@ fn right_more_message( " +many → ".to_string() }; // chars length plus separator length on both sides - let more_text_len = more_text.chars().count() + 2 * separator.chars().count(); + let more_text_len = more_text.width() + 2 * separator.width(); let left_separator = style!(palette.cyan, palette.orange).paint(separator); let more_styled_text = style!(palette.black, palette.orange) .bold() @@ -159,7 +160,7 @@ fn tab_line_prefix(session_name: Option<&str>, palette: Palette, cols: usize) -> }]; if let Some(name) = session_name { let name_part = format!("({}) ", name); - let name_part_len = name_part.chars().count(); + let name_part_len = name_part.width(); let name_part_styled_text = style!(palette.white, palette.cyan).bold().paint(name_part); if cols.saturating_sub(prefix_text_len) >= name_part_len { parts.push(LinePart { diff --git a/default-plugins/tab-bar/src/tab.rs b/default-plugins/tab-bar/src/tab.rs index dfa5ab77..fdb49855 100644 --- a/default-plugins/tab-bar/src/tab.rs +++ b/default-plugins/tab-bar/src/tab.rs @@ -1,11 +1,12 @@ use crate::{line::tab_separator, LinePart}; use ansi_term::ANSIStrings; +use unicode_width::UnicodeWidthStr; use zellij_tile::prelude::*; use zellij_tile_utils::style; pub fn active_tab(text: String, palette: Palette, separator: &str) -> LinePart { let left_separator = style!(palette.cyan, palette.green).paint(separator); - let tab_text_len = text.chars().count() + 2 + separator.chars().count() * 2; // 2 for left and right separators, 2 for the text padding + let tab_text_len = text.width() + 2 + separator.width() * 2; // 2 for left and right separators, 2 for the text padding let tab_styled_text = style!(palette.black, palette.green) .bold() .paint(format!(" {} ", text)); @@ -22,7 +23,7 @@ pub fn active_tab(text: String, palette: Palette, separator: &str) -> LinePart { pub fn non_active_tab(text: String, palette: Palette, separator: &str) -> LinePart { let left_separator = style!(palette.cyan, palette.fg).paint(separator); - let tab_text_len = text.chars().count() + 2 + separator.chars().count() * 2; // 2 for left and right separators, 2 for the text padding + let tab_text_len = text.width() + 2 + separator.width() * 2; // 2 for left and right separators, 2 for the text padding let tab_styled_text = style!(palette.black, palette.fg) .bold() .paint(format!(" {} ", text)); diff --git a/zellij-utils/Cargo.toml b/zellij-utils/Cargo.toml index 6e4b72cc..838b0c57 100644 --- a/zellij-utils/Cargo.toml +++ b/zellij-utils/Cargo.toml @@ -30,6 +30,7 @@ vte = "0.10.1" zellij-tile = { path = "../zellij-tile/", version = "0.17.0" } log = "0.4.14" log4rs = "1.0.0" +unicode-width = "0.1.8" [dependencies.async-std] version = "1.3.0" diff --git a/zellij-utils/src/shared.rs b/zellij-utils/src/shared.rs index 4b3ba974..06d1378d 100644 --- a/zellij-utils/src/shared.rs +++ b/zellij-utils/src/shared.rs @@ -7,6 +7,7 @@ use std::os::unix::fs::PermissionsExt; use std::path::Path; use std::{fs, io}; use strip_ansi_escapes::strip; +use unicode_width::UnicodeWidthStr; use zellij_tile::data::{Palette, PaletteColor, PaletteSource, ThemeHue}; const UNIX_PERMISSIONS: u32 = 0o700; @@ -18,10 +19,7 @@ pub fn set_permissions(path: &Path) -> io::Result<()> { } pub fn ansi_len(s: &str) -> usize { - from_utf8(&strip(s.as_bytes()).unwrap()) - .unwrap() - .chars() - .count() + from_utf8(&strip(s.as_bytes()).unwrap()).unwrap().width() } pub fn adjust_to_size(s: &str, rows: usize, columns: usize) -> String { From 829ff953e1a4eb749b85e85d70b5d9618669f891 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Sun, 12 Sep 2021 20:30:55 +0200 Subject: [PATCH 17/42] docs(changelog): unicode_width in `tab-bar` titles --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5c2392f..e8f5f976 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) path: strider _allow_exec_host_cmd: true ``` +* Use the unicode width in tab-bar plugin, for tab names (https://github.com/zellij-org/zellij/pull/709) ## [0.16.0] - 2021-08-31 From 789005d66a8def0c4ae2af1da85a4719958ebb19 Mon Sep 17 00:00:00 2001 From: Thomas Linford Date: Sun, 12 Sep 2021 23:12:25 +0200 Subject: [PATCH 18/42] fix automated build errors on setup command (#711) - handle opts before client os input setup --- src/main.rs | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/main.rs b/src/main.rs index f0d00f77..abf40aa8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,6 +36,14 @@ pub fn main() { }; start_server(Box::new(os_input), path); } else { + let (config, layout, config_options) = match Setup::from_options(&opts) { + Ok(results) => results, + Err(e) => { + eprintln!("{}", e); + process::exit(1); + } + }; + let os_input = match get_client_os_input() { Ok(os_input) => os_input, Err(e) => { @@ -54,14 +62,6 @@ pub fn main() { session_name = Some(get_active_session()); } - let (config, _, config_options) = match Setup::from_options(&opts) { - Ok(results) => results, - Err(e) => { - eprintln!("{}", e); - process::exit(1); - } - }; - start_client( Box::new(os_input), opts, @@ -70,14 +70,6 @@ pub fn main() { None, ); } else { - let (config, layout, _) = match Setup::from_options(&opts) { - Ok(results) => results, - Err(e) => { - eprintln!("{}", e); - process::exit(1); - } - }; - let session_name = opts .session .clone() From 7c959ee3a2ccf81fd8977eae5d3cf1fcda9c6dda Mon Sep 17 00:00:00 2001 From: a-kenji Date: Sun, 12 Sep 2021 23:14:29 +0200 Subject: [PATCH 19/42] docs(changelog): fix automated builds that use `setup` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8f5f976..938568e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) _allow_exec_host_cmd: true ``` * Use the unicode width in tab-bar plugin, for tab names (https://github.com/zellij-org/zellij/pull/709) +* Fix automated builds that make use of the `setup` subcommand (https://github.com/zellij-org/zellij/pull/711) ## [0.16.0] - 2021-08-31 From da2a9b5c18eeff329d635004644664dadf3634bb Mon Sep 17 00:00:00 2001 From: Tw Date: Mon, 13 Sep 2021 17:56:33 +0800 Subject: [PATCH 20/42] feat(screen): support specifying tab's name in layout (#715) Signed-off-by: Tw --- zellij-server/src/pty.rs | 20 ++++++++++++++++++++ zellij-utils/src/input/layout.rs | 3 +++ 2 files changed, 23 insertions(+) diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index c6857258..e37e6062 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -95,8 +95,28 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: LayoutFromYaml) { pty.set_active_pane(pane_id); } PtyInstruction::NewTab(terminal_action, tab_layout) => { + let tab_name = tab_layout.as_ref().and_then(|layout| { + if layout.name.is_empty() { + None + } else { + Some(layout.name.clone()) + } + }); + let merged_layout = layout.template.clone().insert_tab_layout(tab_layout); pty.spawn_terminals_for_layout(merged_layout.into(), terminal_action.clone()); + + if let Some(tab_name) = tab_name { + // clear current name at first + pty.bus + .senders + .send_to_screen(ScreenInstruction::UpdateTabName(vec![0])) + .unwrap(); + pty.bus + .senders + .send_to_screen(ScreenInstruction::UpdateTabName(tab_name.into_bytes())) + .unwrap(); + } } PtyInstruction::ClosePane(id) => { pty.close_pane(id); diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index 5d4b3492..7d102451 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -224,6 +224,8 @@ pub struct TabLayout { pub parts: Vec, pub split_size: Option, pub run: Option, + #[serde(default)] + pub name: String, } impl Layout { @@ -427,6 +429,7 @@ impl Default for TabLayout { parts: vec![], split_size: None, run: None, + name: String::new(), } } } From b42ce60348dbb92708cebae661111c409eb2161b Mon Sep 17 00:00:00 2001 From: a-kenji Date: Mon, 13 Sep 2021 11:58:11 +0200 Subject: [PATCH 21/42] docs(changelog): add naming tab's in the `layout` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 938568e9..ac0e008c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ``` * Use the unicode width in tab-bar plugin, for tab names (https://github.com/zellij-org/zellij/pull/709) * Fix automated builds that make use of the `setup` subcommand (https://github.com/zellij-org/zellij/pull/711) +* Add option to specify a tabs name in the tab `layout` file (https://github.com/zellij-org/zellij/pull/715) ## [0.16.0] - 2021-08-31 From 2771b247acf73e6166bcf2625a9446ea50e14d24 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Mon, 13 Sep 2021 12:24:19 +0200 Subject: [PATCH 22/42] Improve handling of empty valid `yaml` files (#716) Improves the way empty valid `yaml` files are handled. When deserializing a `config` or `layout` file, that is an empty valid `yaml` file, eg: ``` --- ``` We now assume the default configuration is desired. --- zellij-utils/src/input/config.rs | 23 ++++++++++++++--------- zellij-utils/src/input/layout.rs | 7 +++++-- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs index 8bee3d0a..0339d4dc 100644 --- a/zellij-utils/src/input/config.rs +++ b/zellij-utils/src/input/config.rs @@ -99,16 +99,21 @@ impl TryFrom<&CliArgs> for Config { impl Config { /// Uses defaults, but lets config override them. pub fn from_yaml(yaml_config: &str) -> ConfigResult { - let config_from_yaml: ConfigFromYaml = serde_yaml::from_str(yaml_config)?; - let keybinds = Keybinds::get_default_keybinds_with_config(config_from_yaml.keybinds); - let options = Options::from_yaml(config_from_yaml.options); - let themes = config_from_yaml.themes; + let config_from_yaml: Option = serde_yaml::from_str(yaml_config)?; - Ok(Config { - keybinds, - options, - themes, - }) + match config_from_yaml { + None => Ok(Config::default()), + Some(config) => { + let keybinds = Keybinds::get_default_keybinds_with_config(config.keybinds); + let options = Options::from_yaml(config.options); + let themes = config.themes; + Ok(Config { + keybinds, + options, + themes, + }) + } + } } /// Deserializes from given path. diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index 7d102451..98fdb0d1 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -103,9 +103,12 @@ impl LayoutFromYaml { let mut layout = String::new(); layout_file.read_to_string(&mut layout)?; - let layout: LayoutFromYaml = serde_yaml::from_str(&layout)?; + let layout: Option = serde_yaml::from_str(&layout)?; - Ok(layout) + match layout { + Some(layout) => Ok(layout), + None => Ok(LayoutFromYaml::default()), + } } // It wants to use Path here, but that doesn't compile. From cfdc2eabfa31ef7e907a325e235dcac8cf748c36 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Mon, 13 Sep 2021 12:25:31 +0200 Subject: [PATCH 23/42] docs(changelog): Improve handling of empty yaml files --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac0e008c..7e3a0072 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Use the unicode width in tab-bar plugin, for tab names (https://github.com/zellij-org/zellij/pull/709) * Fix automated builds that make use of the `setup` subcommand (https://github.com/zellij-org/zellij/pull/711) * Add option to specify a tabs name in the tab `layout` file (https://github.com/zellij-org/zellij/pull/715) +* Improve handling of empty valid `yaml` files (https://github.com/zellij-org/zellij/pull/716) ## [0.16.0] - 2021-08-31 From 9a5a315aee615ae85959ee3cd76e1b8893282fbf Mon Sep 17 00:00:00 2001 From: a-kenji Date: Mon, 13 Sep 2021 16:23:22 +0200 Subject: [PATCH 24/42] Add `options` subcommand to `attach` (#718) fixes #688 - the `options` subcommand of `attach` functions the same, as the `options` subcommand of creating the normal session, but not every option will have an effect on reattaching, for example the `default_mode` setting would make no sense to switch. In the future it would make sense to be able to hot swap some of the options on reattach, but we are not able to do that yet, for example the `default_shell` one. Eg: ``` zellij attach options --theme ``` --- src/main.rs | 10 +++++++++- zellij-client/src/lib.rs | 2 +- zellij-utils/src/cli.rs | 10 ++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index abf40aa8..29446450 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use std::process; use zellij_client::{os_input_output::get_client_os_input, start_client, ClientInfo}; use zellij_server::{os_input_output::get_server_os_input, start_server}; use zellij_utils::{ - cli::{CliArgs, Command, Sessions}, + cli::{CliArgs, Command, SessionCommand, Sessions}, consts::{ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR}, logging::*, setup::{get_default_data_dir, Setup}, @@ -54,6 +54,7 @@ pub fn main() { if let Some(Command::Sessions(Sessions::Attach { mut session_name, force, + options, })) = opts.command.clone() { if let Some(session) = session_name.as_ref() { @@ -62,10 +63,16 @@ pub fn main() { session_name = Some(get_active_session()); } + let config_options = match options { + Some(SessionCommand::Options(o)) => config_options.merge(o), + None => config_options, + }; + start_client( Box::new(os_input), opts, config, + config_options.clone(), ClientInfo::Attach(session_name.unwrap(), force, config_options), None, ); @@ -85,6 +92,7 @@ pub fn main() { Box::new(os_input), opts, config, + config_options, ClientInfo::New(session_name), layout, ); diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 7d67a01a..bd2c6376 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -86,6 +86,7 @@ pub fn start_client( mut os_input: Box, opts: CliArgs, config: Config, + config_options: Options, info: ClientInfo, layout: Option, ) { @@ -105,7 +106,6 @@ pub fn start_client( .unwrap(); std::env::set_var(&"ZELLIJ", "0"); - let config_options = Options::from_cli(&config.options, opts.command.clone()); let palette = config.themes.clone().map_or_else( || os_input.load_palette(), |t| { diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs index 2ac0e986..a8dc9c0a 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -62,6 +62,13 @@ pub enum Command { Sessions(Sessions), } +#[derive(Debug, StructOpt, Clone, Serialize, Deserialize)] +pub enum SessionCommand { + /// Change the behaviour of zellij + #[structopt(name = "options")] + Options(Options), +} + #[derive(Debug, StructOpt, Clone, Serialize, Deserialize)] pub enum Sessions { /// List active sessions @@ -78,5 +85,8 @@ pub enum Sessions { /// zellij client (if any) and attach to this. #[structopt(long, short)] force: bool, + /// Change the behaviour of zellij + #[structopt(subcommand, name = "options")] + options: Option, }, } From 645992483d9ae0cc239e76cce8e34fd6621f2f6d Mon Sep 17 00:00:00 2001 From: a-kenji Date: Mon, 13 Sep 2021 16:24:27 +0200 Subject: [PATCH 25/42] docs(changelog): Add options subcommand to attach --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e3a0072..cc384c6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Fix automated builds that make use of the `setup` subcommand (https://github.com/zellij-org/zellij/pull/711) * Add option to specify a tabs name in the tab `layout` file (https://github.com/zellij-org/zellij/pull/715) * Improve handling of empty valid `yaml` files (https://github.com/zellij-org/zellij/pull/716) +* Add options subcommand to attach (https://github.com/zellij-org/zellij/pull/718) ## [0.16.0] - 2021-08-31 From c09e65383ea5bbadc149cf8804d07807434a6632 Mon Sep 17 00:00:00 2001 From: Paulo Coelho <9609090+prscoelho@users.noreply.github.com> Date: Wed, 15 Sep 2021 09:48:16 +0100 Subject: [PATCH 26/42] fix(frames): don't pad empty pane title (#724) --- zellij-server/src/ui/pane_boundaries_frame.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zellij-server/src/ui/pane_boundaries_frame.rs b/zellij-server/src/ui/pane_boundaries_frame.rs index 49344149..a283a6b3 100644 --- a/zellij-server/src/ui/pane_boundaries_frame.rs +++ b/zellij-server/src/ui/pane_boundaries_frame.rs @@ -52,7 +52,7 @@ impl PaneFrame { let middle_truncated_sign = "[..]"; let middle_truncated_sign_long = "[...]"; let full_text = format!(" {} ", &self.title); - if max_length <= 6 { + if max_length <= 6 || self.title.is_empty() { None } else if full_text.width() <= max_length { Some(full_text) From eaf72db29b54b0b6e51ca0c671ad4db5fa5666f1 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Wed, 15 Sep 2021 10:49:36 +0200 Subject: [PATCH 27/42] docs(changelog): update pane title fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc384c6b..1eec2618 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Add option to specify a tabs name in the tab `layout` file (https://github.com/zellij-org/zellij/pull/715) * Improve handling of empty valid `yaml` files (https://github.com/zellij-org/zellij/pull/716) * Add options subcommand to attach (https://github.com/zellij-org/zellij/pull/718) +* Fix: do not pad empty pane frame title (https://github.com/zellij-org/zellij/pull/724) ## [0.16.0] - 2021-08-31 From b1f17a624c90a21719c5f1a2b846cce0be82a401 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Wed, 15 Sep 2021 14:03:55 +0200 Subject: [PATCH 28/42] fix(keys): bring back ctrl-n to get from scroll mode to resize mode --- zellij-utils/assets/config/default.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zellij-utils/assets/config/default.yaml b/zellij-utils/assets/config/default.yaml index ef9711b4..60b4069e 100644 --- a/zellij-utils/assets/config/default.yaml +++ b/zellij-utils/assets/config/default.yaml @@ -182,6 +182,8 @@ keybinds: key: [Ctrl: 'p',] - action: [SwitchToMode: Session,] key: [Ctrl: 'o',] + - action: [SwitchToMode: Resize,] + key: [Ctrl: 'n',] - action: [ScrollToBottom, SwitchToMode: Normal,] key: [Ctrl: 'c',] - action: [Quit,] From 720a3ecbafbb6a0a66025ba560c63ccf3c9a40f1 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Wed, 15 Sep 2021 15:44:36 +0200 Subject: [PATCH 29/42] Fix prompt line overflowing when resizing panes (#725) * fix(wrap): do not wrap empty lines and properly place cursor when resizing * fix(wrap): truncate last blank line wraps * fix(wrap): truncate lines right after unwrapping them * refactor(grid): remove unused method * docs(changelog): document change --- CHANGELOG.md | 1 + zellij-server/src/panes/grid.rs | 21 ++++++++++++++++++- ...grid__grid_tests__clear_scroll_region.snap | 2 +- ...anes__grid__grid_tests__emacs_longbuf.snap | 4 ++-- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eec2618..17b4b9ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Improve handling of empty valid `yaml` files (https://github.com/zellij-org/zellij/pull/716) * Add options subcommand to attach (https://github.com/zellij-org/zellij/pull/718) * Fix: do not pad empty pane frame title (https://github.com/zellij-org/zellij/pull/724) +* Fix: Do not overflow empty lines when resizing panes (https://github.com/zellij-org/zellij/pull/725) ## [0.16.0] - 2021-08-31 diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs index 5e3479ee..2d2354c6 100644 --- a/zellij-server/src/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -527,8 +527,8 @@ impl Grid { for (i, line) in self.viewport.iter().enumerate() { if line.is_canonical { canonical_lines_traversed += 1; + y_coordinates = i; if canonical_lines_traversed == canonical_line_index + 1 { - y_coordinates = i; break; } } @@ -628,6 +628,23 @@ impl Grid { } } } + + // trim lines after the last empty space that has no following character, because + // terminals don't trim empty lines + for line in viewport_canonical_lines.iter_mut() { + let mut trim_at = None; + for (index, character) in line.columns.iter().enumerate() { + if character.character != EMPTY_TERMINAL_CHARACTER.character { + trim_at = None; + } else if trim_at.is_none() { + trim_at = Some(index); + } + } + if let Some(trim_at) = trim_at { + line.columns.truncate(trim_at); + } + } + let mut new_viewport_rows = vec![]; for mut canonical_line in viewport_canonical_lines { let mut canonical_line_parts: Vec = vec![]; @@ -658,9 +675,11 @@ impl Grid { } new_viewport_rows.append(&mut canonical_line_parts); } + self.viewport = new_viewport_rows; let mut new_cursor_y = self.canonical_line_y_coordinates(cursor_canonical_line_index); + let new_cursor_x = (cursor_index_in_canonical_line / new_columns) + (cursor_index_in_canonical_line % new_columns); let current_viewport_row_count = self.viewport.len(); diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__clear_scroll_region.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__clear_scroll_region.snap index b53826df..e5c302a0 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__clear_scroll_region.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__clear_scroll_region.snap @@ -3,7 +3,7 @@ source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- -00 (C): Welcome to fish, the friendly interactive shell +00 (C): Welcome to fish, the friendly interactive shell 01 (C): ⋊> ~/c/mosaic on main ⨯ vim some-file 15:07:22 02 (C): ⋊> ~/c/mosaic on main ⨯ 15:07:29 diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__emacs_longbuf.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__emacs_longbuf.snap index 5c08c323..71cc9bc8 100644 --- a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__emacs_longbuf.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__emacs_longbuf.snap @@ -3,8 +3,8 @@ source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- -00 (C): ➜ mosaic git:(mosaic#130) emacs -01 (C): ➜ mosaic git:(mosaic#130) emacs -nw +00 (C): ➜ mosaic git:(mosaic#130) emacs +01 (C): ➜ mosaic git:(mosaic#130) emacs -nw 02 (C): ➜ mosaic git:(mosaic#130) exit 03 (C): From 4219266523a7f9de83d36c1cc50c65f65c87f2ea Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Wed, 15 Sep 2021 16:13:26 +0200 Subject: [PATCH 30/42] chore(version): bump changelog version --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17b4b9ae..ff59c9b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] + +## [0.17.0] - 2021-09-15 * New panes/tabs now open in CWD of focused pane (https://github.com/zellij-org/zellij/pull/691) * Fix bug when opening new tab the new pane's viewport would sometimes be calculated incorrectly (https://github.com/zellij-org/zellij/pull/683) * Fix bug when in some cases closing a tab would not clear the previous pane's contents (https://github.com/zellij-org/zellij/pull/684) From 1868816791caca222f8a17ab96e3d514dc2ae79f Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Wed, 15 Sep 2021 16:13:42 +0200 Subject: [PATCH 31/42] chore(release): v0.17.0 --- assets/plugins/status-bar.wasm | Bin 435055 -> 435072 bytes assets/plugins/strider.wasm | Bin 535152 -> 535173 bytes assets/plugins/tab-bar.wasm | Bin 424733 -> 429960 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/plugins/status-bar.wasm b/assets/plugins/status-bar.wasm index 285669e5976ad31d2ff6e113e6a49cea21f80d3a..56edbb361849ad38167877c2641009d8967d750f 100644 GIT binary patch delta 12943 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 delta 12761 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 diff --git a/assets/plugins/strider.wasm b/assets/plugins/strider.wasm index ab3d3cd9f4a7123d2486abad400de0b14fab7ddc..3baf91270b3fe284c797c8fc23a360449d63dc06 100644 GIT binary patch delta 23634 zcmb7s3tUyj_Wz#QZ~)H%4=O%*o`WxZi%)zr_Zc?F{ zQE`)kI@ZO)(!{bfqZ=95s7NtMv8b>t|KBxx@59l&_w)OAFK6bvX3cBOnl&?PW?Nt3 zdEj$TQKKGKzwJHEnW2gLVit6gwcZDq#I?j=amb8~2}%{^#$Um`okZ|B25=2{kYkZz z6*fN;EL|lP7db+8R#yC0SS({@##>=Si(5D&8LKVs;40!s_&nkcN}tlAfwNzVM@8+| zS%cB6-B^C%dE?BAuEss>E`a&Y=mpI(jy%0ra}Uv+9_|Shwf_KU2cUbxL^Y)>0tiEj z0+ezYTX0jJFi#Tzqr>AKs%e6GgIqyof%Sm}D$q`gGLtg52Go_g)mYVWmBVa|G4=K# zJh$BFeXvjQLM3H;`d>SLyKG5#FY z(TpY)Pa3^WM+^3j+sjew{S#l-S!Hq0DRG?bHYQDr4&QeeO>nrAT(M43X52EZljG=KIaJNnb?K6bS0 zoL|_7#h$qdI;%HQ=P%(!Um9Y5KjYZ^Mo8|nYZl=5+66Jj5ucAFN^CJ?5Fm>xo9YUe_k{Mjp}uMDreh_o0rULS9Anj(%rA~a7S9E z%gfU=m)nU^DK?HT8Q5|!rG_ANbhyhELiBPYX=xn$#2B-*L-OZH^#oB~mX;Z<@-`!$ zF9YeZiXKMvDr4)?xVhDK>gezg7k8#*CVEK?YHV^ib2*563kV`i)g27%2@2=1L<*_W+*sx+H%PanI#VFRQ;3$}a1DQ&WszS1h7p?5Wj4xNk zUBgvpju59qaR#9){|c`I)ipMsM@V`^z+Ul#7`%&oBP;#^E#{ z-8Us?oIij#bJ&p zzjQ>o*sU5ugRD_`9<8|SrXHwKztNX&m>{R`j%Ird$34^7 zeq-)Ef_+^4^gaDqSoPNTv;n-gs5UC^O^hHUG!GYLEM1fsp&9G{H-j(Z4z{B>IdfbH z+hN?faYSgrLuglbQDN-exV&w?*+;>~V$~v$#s*@&Dkx6P?!x|Ss1OWtG1~giyqS(pAy!9hB?*TPS~! zC_$Ut@tU(f+KAlQTJ~PUgN-{jhg%X#fe0Vl>}e8Vxke(~v-v>GtDDL+Z;&XYzyVG7 zXldF&9^}zruAh6Pb!d^9{;IL(k>rfmO`vErc|0K^Xbt37V$xNzMGqzVnMq?>4`iMWYgFcHR(%G_bZ zJ~0N>FMeVj&p5TQ4CB+v)drgjaoPf{3H)dQY!bLQYk^h-&RL)(fuAkVg1|2ph$Qfv z1+dI;aoz$E1TI)0+&LQPqJ>Rnaq*_7+OUk50~=Oof#y`ws}@kLEV4j2rMzwd)yNVH zs7AhN0oBOe7Eq0R+XAYQr4|TLeSXJ6SO8%eEC4m;;ynvs+s4H{3t;Kv;sXmnfjO+t z(%jkXP;qH)9%Dtuyr*AcMaBBonJl9EXVbW0=NN?9zBKQHCQ>+&iU&Ot%=%894M8#Kbnjbvbt7(I7g z1H!|*zGRKhFD+s`YnQS7wQhX#E@R7Uy;+{I|Fu_HN%6YZ8N^k*zIZuz=I$p|3xY5U zjM_KuWXFufZ+h|jz?;|N_q#VcTBG4#|GJ#Cr}S5>uO)kqj#j+1?++nf%BT(j0YGE~f{(x=;!IAM zON$N0~?Gg_F|x~+$s)*5rHIIV0e z$HH^`y@lxGi1)j;+i|Q+OUHppT)cQuTjLFO@>Cp9f?!4Ndp{NDpZDMI5u5*&zXu7T zs0q9lEE){gKFPoN#Q1Q3T72>0GHq-)r!5Ef=o7<(F!I~^#PDFWecxfD>j(XeLHnIi zCL6=-qFVJeUQL|%)VTM99&D?zaex2ttrb*^=0rK7&^Wk%BnCeGgBbjF`5?7bqe{Ib z_lw3oAN0hwz3_wn*M0b);Kn#K=8} zjk_`B!#2k0gNre7rhK@J)c(zn*5S9?q0UX^Xl6N!4vl3;ieEb9VnZ9h!$@=Ya@tkX z$^`jk)_@dblFBrbmP51zV&?<3Zwty5I zrEIof6p&+Noaho!Z&yI1dgIL#2}xyvBtL*O$79Z-cgpB87~MZlbU^PCPfpQi*{UP< zpb8P+Wa%8XUXZGUcpO)@aURl8e;Hq%KpD|6kRSZ;tTh+!JUvUrx;9N8YZ`{tB{_bk zN}J2Mi7c*p?z)foC<-fv&cGooVZpNf=toRPB-tt`k2%-!8 zUxQGxe=9zA?rvl^=KP%OA;T=HFO+GX(5W$G=9o889R3l!CQ&X7W8ID3(JrI<=WcCF zP|g5e8t%fjOBeb8UKWmUaAF$4*_hQ&C^9-H}>y1K;Wxv4bQe6GZY8HCr z2Nc^5EAZ^!_V+o2I#f|%PI2UWB*cos)6flCH%bu^9YnQ-QO>8D$TcQ6_K(~_6O#^9 zxxay@BR1hi;}GU6KHM12+uGwol~WTu%>-X3vam${?hP%hMTQv6_P9TGgk{uGl>NI0hMO!wd@hfzF`Hjc^FHMX;Kx3 zttxSrs>u0aY?ZAYVWb_wN;}9`xbdRz>oB&Kv1&Qp$!=%0@*^i(&wlWwxmXF0*i3^= zdlDC&on=sS);_~hlsf2(>P$8%P7kr$!cbz;2NEu6Ld`cwjT2elTHMx9Hc~oilQq<} zHny~_(#Fh7hkI-$%c!}&U(0+uo3mDoU6B8Bv)nMhWPR5}vb)3C4*6v~+k(+roq!=c zB+n+W(bH|sLmwhV19VAsNv$sS>eI>=J!uDZHj8wPq-~FtHFhd-i+s?-b}ie2PI+J@ z(uEZx_W^bEdK7ZW_yyXfrp+D|f617WGu7W%|1fX$vJEU(#wD`;?1-F^$P#;hN%M=g z5U@^pnD*|Qy$lN#wPgU#+wiUtLzu}>|J(mGU;SC3oYbDhvm&{&JsYN1{Zc0Dm$NDC zpiI4iC9*OZ-+{da&G@S$i*l5;h#%FhIitna)wp`c=JkgyH&!zYxsHY+w@5DUT zJ~PR~X`dO3#uZ6-XV&NM^xbmi73pO%_lop#S#?GF5$Q>~Qu&o~$rb6CUDB&FjuarDnn_1YeEm3kxxCEqJOZvOPz1-%(9xg!EhxVka}-+ z2P>8L^k&bqluP-V zK>L|!(L`(IQZ6UZ9?1u-9no64l;;VwokVjHtp%-hT!8jE(OMEMlCC=xZNEv20MYGI z2OTh0%4vgH%ZwA07D4yZPet2HG*xjh z-S#P31<_(DZxCIaC|bP~Lzsl}emR5nYz0u`Ak3MkwuUNDgp$mJ9{O=(W2_~SKICvj#p!>+=FZ2+-Dtu&DfDAXwqnhMW)@=F{FB+#j?G?2hRD6|Pdo=`ze$@6_WmJNab>qTG0 zc$l@=nw=<=wsN7KX6bY)XDhu>Svo2-$d4zmXvdZpiQ|c5W7s}9Z8B@^n=p|%xC!E+ z12<1*JzDNCEodUviFBb&KyM3V(PS3a#!3k1f?^^A?msLy1+r!`OH3{>bGf&{E>qmY z=?+~nTKQ01B5VZOr%@4d;8iSPzG5IMI^;?3fNag3;v||W+A3*_%mT!7@I&_#BL5JO z+f2!)l%oHdQs#T@Dr|dOoF}$kW|jinQNUdGpUV0yD?}qr+eQ+qQ3)*=9<&8j(e|!DM$c!*z#xB}&-(Q>Hv-Dh3Y$aS z#j^SnBXP^XW!na8QOPI9L!A z75G8fDs(3CR?##aAiP7`#2CTWiK6p+)j3$?%NG`~+TJy=U!sL*M4(0m6OMP`n?ncc zxuL?k3*#aeLfuE46>`sYY(>1asHts}2NI?mOHOxBF7&%hj#`KvaE08okUhfcWZOkR zWpcqHHWI|`i&z&B4=rNv@QPj1+n-0tZP&AbQ5SXvEV!DpC7mrU$RDp~LwNOZ*>y2; z7y4E&X4Q-ryrJ^>mM&#uZ4@I9F2iQ3lH3Cs{93@m=~hE5rg-s zLdLDe7BN>2Sj`4INysp`n5(Hzt^Ww)>pJto&;2hkyp1cGXry`E+L zeO6cb;%{T`vMVIWv$wMtRwLuJiuBlCe^$0to&_7J?uUfTJj!>>LqIA zmU~%N=mjdcGgeDa2AddP2c$ebv_LCB$9aoqhwq^b_Kc%R{6RSN?|9!DAcw#U11TP4 zk4=7%+>G>sBbF{#%u$cY8mPnk((d7zlSn3unGHT1@?kPPq#o{9u_A>)<@|0+Ea-VM zZX;_GUP5_7kO%5qD8)u>+_uSS525>yY-C~ZTgr_v_NDUpMp%nH8I{c@n{&IBye*rB z>t)4zC2J4%r?Xiq#`UXg79Z!YCr>p*_$xsp%Vg_^*kH2~@{8X55bN(aLLGC+>TDJz z%N}A8-TWmWN2S>t<+2#SX)UiMcDnyTYP~#fmKW$}P2a?>wVzyEkcT(1Yy8O9_hB}j z#qJ~V(#47y;Lm!wJIk;%3HyuT|K;@!_0L@NoE7FYmqqZVd zz8yL2B(v*+gPrQ4p)HipKEX!Wy{Yy6@C197^Q;5r%JAHi?2I4mc#2J)mv?}SPFtQT zs;p&G9}`YHq-sE{zn77b;K3O?NiBj&h@~h7!H|%WE-I*RBgyhq%Lx)&jXL>=WZh|t z@Gr@pfkSOxE=y)5^5I<8e^676*z-_*&_!(5C}!{Y>Ji7zX>_+GnoV@-Sth-gdk_X=PIBdY9t*zt=xdHPkB$TxrJYrc#1<1EiN>NOU`Se#SRFnEz#j zyvN7Z$6TPiTg9U7Y1SVorvdA{z4*rH&boh2Xp z6t(Algz4PO`1EKS$IdL8*($n&qMV}q@hoMLjq0PJY^O{(%3>X_P^zvNvnU!wQP_4$ zgq<>uvbntWBXd^ZWYW{u`!iO_{G^1>v1--$zW$uO?XV|Pm2X=WTf_O5N?)t5+3k8O zYpKV*D~2nRjte=5eEHw9ZmdVAREHVpqPs!2%J4QLLa5UV?=e_BVPFxtDBq0bP|Qg*;#UQT9O84D=LfL^WK1l2 zql1{Z02$Mcw#Xs}shB9sz%CkWlvHXZIaEw+sr0AprVTRdlew;jq)v=M|j-7%FK%L_MZN_kduP|dc zy&pAWIJqA&W0=UzMDsO}754jdlnVk5S`VLdc^!7Ml`{HA_E$UW1#=nlkl~=Ir;Kv7 zOg_U}^XyY{{24Z7Og@GOqHfx4DlaK{8Li`MB3I|(Nic?Vz9qAW13@?%Mvx{uj=S>s z8K`oD{NoIZySf2%0n;Zc$uz+NsCp?@D?d2Pa(V7)xv-wSk@5H! zrm)}~hzN#@G$e1_Tq>S8p(c))+k zwddKb35}nC$$Wy_4b)s>G9evBU-vnFXL0Omiz^8}s-bk98s-TX7ug!Yaq@(H{fi0- zb0;P012iTy*D#nsO2_aLhuBD^a<`e?6XDM)9y%=V`Gd8Iv6^ZzxSJSq4$ED{U^U)i zXwk%wdsu!0hNaI^6+w0tt(q9NQ$mQH5Y?3M(j_@sHzoX&5`yg+&{Nck-4&|d=4^bUl)zL=P z@#M=qif=spfOr>Y6aSKc_;UgAV*&9fR}=qK7ypM{*I5^zVa0vp!Z{CP1-^-``K%yT>)RI1 zk2C(z5xF6jZ=tYF9KW8zyW;q3j(YW_fUi$HUlqdk%jY`sSjQonA}!QH=&MCELPZNG z+FY%PPy1SS;&%kYKEn4n$WsVwk9GPA&c}U7ffokO#~9kvbxg^nqH&_U{J24fQtPY% zI|U}3HG;$@M7&Jk@wgPfu_f~6?!34BsXL#S@$(6l(#FeOo*x#85kKJ9VPVtWn6Sf5$ z;S>*=s3p*Fr}()>6-sCrywFVu^Znx{Gz3jEQ6xAAJH@jmiU%45_pgblp(*Incm9SB zbczqY%TSP7Gr%d{HWLZ;cZyd`M7{6l6w*Z0i@r{gWg==?AE!9;tty2ilMEkr6YA|0 z@0%zJX}z4{brVrTdtyQPc5jA;VZk>8t{zTYJ*#4d zM3k236i=B5Uy8V}720Sbst`u?$FEgBN=v|+Z6XqPJjUKcB&RrdNliq($7eW?5XxYJ z)Fj3!em$;=Aw`UKisL3Cy=dnY2b$8_Vqi>!&r|ToS)Bim%GU>|wNrd=BI-mGE)Yya z1JlYW-ZN1+5NMHkhcR9GT2p*^UPF)kLXW zlFWy~2hen8e+RCBj6Qt18AQnoeRx#Uvn%XrunYHj`tpmor1;SXH)uRgy+2AZkPjYH zYi=lUa@5>~T+p|BD-(mnmm1!Vi30P$mZ*=$Vo(B*mO)3wLW*T(W@X~Q4@WE+zP$r^ z+Z#$jH+Qz1?HFbaqMjhymE{nTDj-lzn^qe%(7qMVs6Zhy-Dv2kSR^x_eNRhA*a&KR zB5myevYwS9g~yC+KuP$*!NXUIl$D9_SFfc4>ZpL-NV4|p)IVgRns6e=^Jv1Vq##i$ zpG)Cg@Y3U83LiXJRfb&XeQ3IubL>)*cxWQS-pQ)L3uP*)wo^d z!?4f2YY^WU-&D+%a!b!(-U<%~kfW`fJeZ3_Rkjx=aTJW&y&)VPcELPsT_RgUaz7%A z2J?PxDczJ&CB^8gNz&m8d0{Y5=b8EPh9UfRl>E&Qz8T)Q)k8U4hxtBz80w?scEfRp zP$}0A=bgdz^l-jDf(oJz^U0{DM#hieot&z0wTZ5gQ%3Niy{KMuP$@~p=q8IqM3=51 zN}^Os*m#suE8iZ$W1LFLYMg6j^$6Z0+0rC%6q#c~g8QEuH9VEKb_HsbT$sue;>m{~ zdQ<0{;i-YhHcNwsLR~Yr$!Ah|e78UzL8^#D2|hpx52CZCurxkVM`Lp2iBt?-q3j@d zN`zfl5K>T9y10v_tW{T#xs z)S)WyQM;(t zD@-0DtI0!?hJd_fBp(uU`E&^!xPv447#s=Rqquj@|KOt*)7-_0&XF!s@Q^`G3afWP z9yT$sUmkoRONYPUbm)AJ%pS!DvK(19ia#-4P1R78q*Th*D-SmXjN4S43Ngpk^*Y{N znHA8SR8vD`R4Q62w~pp>q8q`GJ~kQ_0&aa*Vz51XF`-;z_`Zb9e55DR5FiC3Kw|a; z<2JDZ88NTUj^VF(R3ToCXQ+u!!-eB#;M`=h@ig_*vHXQFRUYLi{(X6NuE-VU-jHHjboKMM2gWd1^&DNOn<8-4=I z64G@GRaGHhyNdsZ3eTCM3V(SDKNx0>6ztgDQ+ZqT@X4vXpHr!Z`og?YR!rsTFnuGY z@hDa!XHDbn19Svmy}A_^E=S<$lVX4He(|Hzb=B!g9KH_!b?ra0q^lQ9a0KH(+~aZc^!`)Ve7~Lj|!cIu9@mq;_{Lw) zfcgKaq&jh)Krb z?2r}1DbtFDKme=O^4pw5@Q_&s;mlh872mvHesl}J7@4;pHPek|*10nJ?l@0=wT_Qx z6|&>4ygxfF=ikala=Ko>l}EV_q7?N;1}&6(Z{?FAh34z|Pu4%&AQ#=nTg$lHc;qF7 z36~N&2PXP<+{RmSR_@z<2k*z-=g3LqQeTocgJk}c7w+P>;i=}jyLtNT^>O#ZXZ za(vfiVfgH}Y<>U^8S`JFkgo>Bk2J-7KRv(`LUB>r;!*y!qsVmV{Prm3lMOcH@Kb(p z^Amil-o|8ur^?-p_9eQo@+CgSkGS}jFMJK#`4UEc*{gT(>(l&Cgj~3rswAbLi_Tz= z8_&kHn1TB3_Dhvu(THb!N66({ZMl50HgXK#UpsiWNWV7Ha*oGF<#O-~ykFdL@-208 z)5pryxKzM%K#lGObL$W^9(jSc9cI6Z!0V@h{BEos=C|Ir zBX$J4I+eq{gWfw?E@`t2(=9DK?&OKo-|;(nYh2WP;agO|^K_nF>bv*~58++?4;ZKy z1*Mi!+Foq4s7lk5-TPIZaA`a1Ne-DG%f+wq=X+SSdtvndi%(o7S%vl8#q*)X6}$Ld zt+&7{4l%)X%d>%Ff z|1RMJT3QmvEouZNJgS1o6W-u$GfctWfOXaJc4G7({boo2cDodN4^fYgf(G#26a}e- z!ZV6gWRVIftW%@{i?oBn9~3FyBIQw7t4O&PX$yrXKlo_+(P(4Z1>+-AaN3ae!Nn|uz>{zKmOCQn|T^T%GTo344>BG-&<&-b(y z`TlsADDZO>`KfKiZht&Xl=(Tz{Zzb^@yElk3^(`jB5y57>8MQ{`Q5D5k=AeHi@wCp z+w&9YdZk)c`*?Il^~2bEgKZl~wRU`aqB}nk{}7Lr)9rC@PmI|vDlOc3J0@XUPuRBn z{CvcaQfqUHdNYQe#fU~fHPde2_WbSn`CdE+4s(vmzxdcce0S{T`*FGK8}T-;<2>u4 z?6HS0k_!sWLnZRfm2%BHJf=nd zA=nxEn6ZsV$I1d@BcFYT59FJylH=dyW7$66vUm9)4sT8VUMz|EzIXQW?}OpM-f@6W z&_ioKDx*869N(`Wa-D9#fB%R#U>!Vrh`-F}HOVKubKIdnP|;LW6y*N`JSYtnwF03^ zvEatpahR6`Cl&sV@;u@Y0(FN~MK~5>vT!zv5?)*hep|tB8oGf}V?>^n>J4*gP8gK> zEg-iSTb?vCp=V7WP>J^ss>E8~BcJoU(8QfkNhBXZpl4Qj{ALPkPwucHfBo`TtunFe3sOGgG-1kkN33+g6 z^?x&jXh2Hf7f7H7h$izqS_E+-P{OxAH9BBn|8oFT94~)9$>WFBQW`x@@PDHO;0)2+ z5$=cgPwKDA#7GbwcKgCC|alV&N@oo(MkbnPx@EXZa^ObCr zTz#6$$Qs?P1*4Fm!ETLFC|lR@fsx&VE+;RjtplgH^pVDlKKb zTbl^_GbkzyVblz_HU;6wx~8=W!2OW!4Zx|utrV71e&l1ot@*b6$Zz5i|9%Xj2u{uW zs@*`Oxf6QeE{B3%m~+aqcB&5KG55U+#QjtAG%7p zQ#+ZZeSbZ}c54pelMNC2*p69?+!_ZQ19A+)rT8O(^}Zb~1FZ$(DRNs-QTFdTH`iqfSXaF7%s9~Zo%U2Vnzt35gENH7g#&*e+c6!3JUCC}WKv#i- z26EUiO}ibmc8EWYkm`FLp$nnimH(DLd#+p4rFX0zCHJ(`AB@ed!H~9O3F+@H`7ven zkMhN14d+Lk(xw9qQ4VFft)8aPj8&Lcgz%Uistl>Ud-m++xy#eDr{^9GE~I#U z%C*x5n)71P94G}9a4XFQO`B@b?Qn4CrnI63rt&W%gSZYB(S!exXAAOB*av@J!g6zh z-U}aElqTr6M2<@_r%HEBCz_GtB-|C}nuM}PgXuel( zH@67?Qj7Con0v5W>B)QOPZZc~d>E6Nls^zB2H^V3_|Mr`e&p4AEy_o+abUj>e>A6T zybf{dijD6;ocL^f&t-Tia3FuWoi67m>Myc!Qg5$M3AaUY6piR%&ct=?_0|}+huiD( zS$BD|y`F(}VR;7?oa_K87RojqVG|4G$d1sH!l7<$7RsK)OkrLaW*U{);cl%N=yv*_ zh?Cyf=`HMZ>FcO3Mrkpf^sbI9tmMwO+Hc#m zxXW-jaG?3-z-0Dq{y$RPSeG^J972-INh|ycA@%TMgw*Q&2+7R9hmfYhI|!js(c0^X z5aaU*nK})7-Iu#pb25gl zJ^q1B=O23GB)2vfRXqbqwnBJm5o3q0UAkxqHY_&Vdthq`HXDBm+&ch&1l-TYzB~KD zRz=yogVCjqcA9*;zn&V038eOt;nrY-Hlv;%};4 zIat4~UBY~`pM6nlSCDgnJqQ!zAA|L=EJ2POqWASWK&9Dn?rOKPZZz4@1OMbJlX8fj z5Oxca$;jr(cZXn~)gapp)kh3+&o(pfLc^$`{bABc;=>S<>IRzlz(QBa$A;>2!a9JB zmK^-!0z>tO*<5+oFuiZP>8Oed>w`^02ZU<#fG}1b7^dIEmdk;|^(m3IQFvpXIsM9f za=5;n@zATEO4Sp?Ls1BgN-z9{%Wo#=u?aWbqBhM_kwWu={`26E+6$*$xi39ck6^(I zA&=g2Lz>=K772YJp36q2>7Co_HhdRv(C9@>p!V9$aJ!|DRx_@qJJ}Ggj~A zwQG3zvKBmLwV*=&h(fsQdf925el>T`miLX*6BBKDjDYl#QCi{k=Js_oFwG|LeK1a+ zkXV6QlYmd-k2-AQ9f;HPa4(j9#_MU3dFgI#GVpg$S{jXH`M`L6Y~;ShZe^A1twyr^ z&vCJ6EM~KPtXT;v8Ni{;Yz#t3Q%4}A0H71^|EwmDrSP92( zF|BkhLK>EH2&sbe2&rLzAtaj;4uzy)pR{864eLfNTDubQq!lJl54m)TzIf0CKx*S` zE4fWfYQFdR{d`ykmyU=yqO#L^ zv#8O$_8I;BvQGLPQJ0YTw&;1yvJP%Pthfg$PK|4UqUs5NC;(Lp632*>1`q^}0f=)| zGXJJLL0S_)LWkxatSEvd___Sd0#EuDXn-gs!epKEWu4ouuV}ZzVKzp;{?oL8@ z@m{49+jmpZpG_U(9D&*2A67(?I9DJz@Il5lbcRh)#L;q+>otnH6zh%h@EU)xT@uUy$wxXqP3_`vJ{mUU; z@aq~%zXOJ@XAPw#L-+deOWFGT;WtoFHvBpYTq7p4a(&(if#1Rry-ZF={XZkxnbD-u z2_rLAR$F>_%wf)oO79uJM`hKe-6q9x_LM$ha&+*X56}dMJINL66wm2vCzr83dXFiq zS&6=D%0B!~nYsj&SMmE?Y2vhC#-1weF})vi9Gbqi0E(XJJRE39ksb5g9BXLnVOxLKx*)gO%7%*2c`a#gdJqm?GLQ^i7F%!&E4Hx zDikz{`#BR>DkGq1s%X&Ni&wF-(z_OqW$k|a2q~KxEi((NC4$)zqcBx8exzSm62mG= zol8ft@MFX$F^mkh_|+zVn|{}_EC7c)hkTl(S6Luop? zZ|Rx!)py8Xc0UkePt6WYP^mT|hZ?;$2eg?%{*B1NAln0aln68^mW=&G&4-{k%mL+B z4k#Dznk)yja zJI0?YFbz8@(v+AmRZ*N_ipaSaGcru%m(I+LVys$Uy)HVY0yMI^qK=rop;LhwqVPV% zOG=+x_bunyM_}=y_0SA|z4+D}pgI8?C5MH0`t}hl@QF{*m(HR>|8Y|^dqsELF_|6K zXWb##@zU*g^kqR+JKj|K@gAZ|KY3?j2qB?pT$Hm6QKAQCt^eN%JXsrAVQJ6oF#)Vl zzisodz@q!nt}ZC;@a840wwiquY%~@v0;#M&maF{Il-!Q&zjF1v?%xtv{3@E=Nu1PM zZ23Ge_cef6QKVnoGKAOf(EC0xEV1MV>KA4ogi-E6d_NLl6@kb{1~aFq-ysWEvIza( z4@_=x4u!ejWe}kyjmB5?(7eb2c|RUT-(f4LT`rVW@-HF;BIcK26zwjeTwlF{@&||l zDkffW)<^4M&qc}}tGU0vF)!GXP%=9GaGusA!ea`F@b$d6WAFfy>%8)nTVTcr-?9zRA!+b`{*cC zzwpsap7rhKa*R*7t0jC3@tp-C34Ct>_y}D5Xn}A7KUts!feRLBPT*$?gc11F0$63Z z_{{<#1R5<6>>LU7mxWx!`r86d0(a$_jl){QMUDlqz;LnI0)Yg!=sO;p&9~<2ujfzK z$3Naas;U~=)kZWRAng)fT)O4)mMrTzs}7@KyDbn(CGD|*(aK^A1QTbk1&l_PSiorH zOBOI1x!(dtBM(@>Xyic)1Q>mO)k0YRxG1v#)R>Dy7J%0Vn_&U0d|bR~0Vpts^-0g4 z!H$)dkv??KQ)iDn$lkiEBs<}A!wT9ifEf57|nmf zE`@_op`UoR11m56^Vw=1Qx8*Sc)J0pCL2m=fM%L1w(8&S-VBSs`d_guPk-oNPx6{d z{h?w_pSb5b-e^QiU*GcqWBK}n#n-VXN>3L@adu9>xVM+%99h_Mz1zOIAUv?|Gj{Ho z^b*GF_UcPs?93bX>f2uI!E*E?FYblXXTHQBuhR9UOSp6E5mGNdNHb4A_wsG*gud`V z0)Fp3a3g-dInV*WPEQgV)W)0x61n;pZ%l1&n(I~?H_bH{*`=xFJ2;%nGjGm^S`B-v zQ&hpJawP*BCUN=lWo31Ozmun6gW@L&^t<0mfkk`kt*){8pL=`ITI_BDFZzo*-Bls^ zXYcFp97&D;_Xp+5=wMDh2iMf`!G0L}U3`47KiXdQf!^t8AAP_PXM~xIVR})m>gohV zocU0{^JrJ5>zj}C4c4ov7{!TlVDFC~8P4kU;G;44?RYdLyx!nmk^5==j-%b!n|krl zzBj%3nVFe{$X3&|MtXR)KJ_s)Uq<32CKOSF* zUDBj?7L(c^csCQjohv&um7|#Dq*acFA^2CNiw&;-1|!YgOUSFHr3vzDB$A&=DpyQe zZY^j5CM}zpv?BfI_gW;MCQb}EhB>rUlO$p2^gr~}%PAfF{w{u`^$i?=PhU=0aFDYUn! zJrBYJGztWx=!Wz9t)EVC{CPK4lxqE*PY0kdzmwhAr+V*`ov^K+cXA~t@14|m!-did zCucI=ctJ0`J)-o+QzPxT{>taqnrwjWMTtBk!iu>yS(T^>m+*YSasddkn2zty{i&%bX_E)cvi68jQ3|6E+@=Y)NR(!J=zpK9u*XP$pBDSIS4+D?J z?|5Ai3C$mU@&Z!ya+|}U{stgRPxG)`um84p-+EuZ0c^exM-|z>`BadtXPxaB zQe)?&I&1U;XItxEe;XA#7V{K+G7K$)q5aRP+gMa8>%!^&Prr)q1kU> zc*%geGDO2~<#KUP-cG)g$RgMya%WE-&$i0)p1h@uY{T4oZC!+#bFo~e-Oi%e>1QH; zV(3uI^W!;;;llcNF>c*|p5Ef%MVQSliXz^~nh=a--hneE`E%b8q6_?AflwMRmY(`~ z6SC_wf9A2bH@R`$41<X%K$U6}$i8BEGBk+wjA>F8hpj4cma51( zL2QMs9YLfW{)TqY`QWdAd%g@}YZyBzr#RWItVX`;Wb4`2o>Ujx&qFRjDGa%~uwl-V zexa;QmZd1CiA9~sCdKJ0N-PW|CVe2`k|vz~3fwqx34xK?8p?X&Q*LXhYiw+3Ta}Gj z0`2JGL6%cGbF=&)uVg)Q!`P-^Rv<_KFj3i=MQDI(vzdk+kix!jMse4Q8IaPh@Joq;}Th4RxYO|vcw+8X?~I00PB>8 zY42W@z_2_~Tl!&>%|taN2r?7Y{Qf`97Y+{Pgf=Xm70Tsp*buc6)~(Am!QkA)a^(E>ERAiWAXc7f&os-mOwu^H zma%ACfpm9Zz5YorlGCn9FP8b&r0@I9XN+;2(Y&k2EalKe8NV;DyR^u!|;awF4Tagl((3?$-t|%sHUEMRI z%@6fv9eMe$@|E6X%6^qU_hzX<<-cyhIkp4Y2lrvaoHZCNv%DI4Umw=>8j`5AYw${9 zmaOW`i` zy-&OlN+04f4tKyh?WN)V!z$wkFAXG%)@YWCyiS*K;)A>o>_mA++nc$JgCEeICE8G` zBgkc(|A6)l(Zb#HKn$ci7NeoR6R|BN44?}PgSKNAXhs8#vrU8cFQOTp^rss=gH}eg zSjy{1mm>!4q!fdggz|nihz*RYAfK{7I46}1vF#avygK7hR}SmIJINV?*~_d#x`wc1 z_MjX&giX!5^b*b^RS|}Z!xm2QgNd30HFt_r27=thu_n6PDP99|g*e3vCN%`9f$~ih z0Tk>M51PmcHbxOEBS)mYc{06zCKl69oa`i1Y;$H3PyC zX}*c5P#lqNF%dNeN2CoUMum|;I3oStMAR4@k)8%}qR+tzeop!LP&R^9cs?J>>KLT> zU}ru+y97C7nQfKf9tcBf$&|1qQkfvw9A3Z6bEjZ8`A5|jLC!tIwr9=bN9Zu6IrBZ+<4~TCWwa;teVKWw%BHx z&_t{Z8A2I{-sa1ai7c+A#R%qtVj>HNd{|)eW$i?k*fZbE<=%;9h2k2gi*h5Ol@G-w z!bqTf3Kb#yU&mU{F%l3J9nvz|B3r0aeDFMk*UFH#I7jhD{Ln3g$gKo&o1EO;i09+y z+ML~<7q5e#+x#m^*4@mq6*m#0Q1-o^^;%qjMw-T#VuB4+&<0HzsI)};7&>+m*14mK zM*^u?Qy>I_ntv+W)L*4gnG%e9Q}Y_=Ac{47+hbIIf02#l%l!;Pdkf@y*R!#mn$8$R z7s%A?ZW25>OicsO!WWA+mUvPovp|l$=GdvMHFR&;R5mbT_kqJ?Q{i@6F}R%vJqM>U zH^b6&d>Tul73SAz=1S9fIunFfPG`rkYWAPOHn0-8Zw4EM;NlGShP5F09QhSV98-1t zC%stmSuBg!o%7r?i`~nc=kKTXYG@0pB8P6PjGn_zA%VO&hxO@gF8#)7s?{^@o~k(8 zL+GY@2Mq0kbo@ zk`JL4Q_3>=`b}(EytS$sj*Uf>@cx4)L;mGNCVzkUJN!{t>nNd{$h=*@89`weqJ0Y!EN}LUvk++}k`W7qS}0 zx4vxT^Q5P<(Kd>a#}~u>t9^V4OJcRM;}RBs)v1x3zJv{TKS_i0f~7O0F=bK&=&jQm3qo-9Fw{0S1ABvA zgCQ^6%3|0t>D|6KryRGD*KTWn}lr3rNKuI&9SHc>HivIZqobwnyG{JIdKd z9IG7c_bJochvn*ttc860cJ`cg@b|wD-aXCkV0W{?f;UlAcTp*~-N|wSYpLK4SSz(G zHa@-@$T--c<=FtVd$-c$dG60*Pdb`}?}tr(-doo2ID`VrFYaRxPdtAJWzs8+Sh_zk z$2=ydzY60^zQeuWflcmYlGz5b6E;T z_48a7ALp$n=M8Fz@K%CG?v|1Fvw>zMbPT%ce%9A<5b1Fg~5Im(Sf zOiR;`(@I`N$?1v*+>`PTvpnBp)r>9dM*CSst^8mMo9jiM-Vd-TEVh`$OLr%Vz$zWA zIh=_q-XcAeot?c;?#yG`!)mPou-D}p&x8lrTHfN-#{%h^0Pp%H;n)u^Lm;;k53~Cw zSnGGy5tK}4Q`RN%{-Y*F4lZiz&2u4Kl5$Z^faa#-DVr)Vqtj@fnLXr1b! zq1`5*dX$Z@dsF54;ZgP!=XKbI(z@{UW9+;a6g|!+&dzz8OinAFA}TElsE!II|ELNO ztM650Ff{C`lZ+)W39)1&fnZ=HXNWTD+izA~eTRm(r}*@&!aGe@GqJ!t|Y)D-vg z@UbbUfX9mDh>?lLu{P1Hip(<$ahgc9z*D2$@Co52SOZl&ioG-$5J6KgY?PXn4o-Y7x7a#a17;s8oqh5Jf(XI|lXb2qGD zNQIpb{yyaY<6f4?8;*NI_pv^l<#T|>e_nr&eE37up7SoIb2I(J6D=JN z<Jw9S5nU~c1V=P&fo-aRUuR83xRN>iqnyu#i(x;yAFWIeXxV6qRu@$3rj@sDx zuBY&8)|qv!hgOj{-z=3)4pxlQqB}*S>EeR~b5T$JHl=f#n+LHC2{5nl42NQ(u92=@;38XO zKO-iJvVOFVRTNQdu^dKBT>8|@*-a%F+4H@zMpS0fwp#fD1jS5RfkjgV#Y|d}MPq_u zCaomdA_j{6Rt)|WV(@{{w}VDPY??$FVo+#jMv|iR1AfIb<6B6)`DIK6(Hxxk3{i{e z2}7u##k_1R@EkhJ4*Fqlvf)Q|1l!Ibb!_;UH^0a3+}Oh(GGo}kmzgo_-Cs3h*tZ`v zW0=RyMCfXf|0JA!+Q<0;`>n@z^jIBS*>V~E6Z<>LdZWA>d5p7s%sj&;ACo=Lvq)Zl zPL4g#CXLz(iD5*Vp41tKqIAY+?OhYG9X-FYkGXr-2fZPntu5YcGls_!>Lt z*lsY&;@}UQiAFlOqZuYTCljxRtt`8Cz)5u735Az zRQoARD6S!}fW*h}QVQ`C@wwZ~u7!BBiVN?{JN{xVW2~lH3EWKyF1|1KQ39*+R)XeD z3I2XxeuV@ghaNin*;Rx$CCDX4fX#?#Vmy3Bj!0Sd7i;Be*~HJ6H(p}5gj;&-=h^Wg&3IoseydNs)F*!4C*IN7 zlzx#<{Ar)~DW7h4!d#Axb5U&^7XE9&R7f%u2gW&7()(RyX0&t8k@(f_ZGDOyq9=%)0Bq@HWq&3 zm$R_1OyD)R6u+}Ya#a`JL;li*&(8YX~m!s~Tcic{Q`Wx`f~!<^!Ltx-%1 zprKCjc@r9f6Xzy`+5K`88ib~qC=B@qI>mh^iU%5iquy_fGO3}-=#q)3q5Yj=?>AWn zlUmcyDR!AmLVcY=Hxc!|4-CcEMoQ{MZ@e2f5jC`zQ+#3~l1xvWS2v*^PVsLOMS$1c zDYl!48rsb%wtjs$OTn< zIQBLX4PghIv)3495^C=h=S<`VYUdR10A-#8KIAAiVhi$O5nTU9aPVx7bMltyE z!v)9Zf{CbUFrgne@mgbrHW7(C9%FAJl2aVkQxj3|W1Zr+Gbl5Q2~v|7r#M592~xyp zr#NaN(u*jk*w@5sg@G{@iU=5NTylJEB5EiuIX*EFX%H?s z-Zl}*36~rtCZZ1Fj^hy%Wl`^O$&qbB5)CdnemG@RLG8vR#}_7|cH@%cJrmIw!bz$y zQ4kRPk$om2WrMR+Xdhp2 zRxiH93?k&EUOb}dNtO8u(WCX|ml;1-=fTc4o+Bbfe?D;3Nz;AAHc@dGd~S;}O7dm`(=-nTc#Q$-P{b4CH-U5#N;2F^bVwkfg&E^3p(_ z!Rw!vO9$~=QSw)VcplvLm4mqssz)7y`iLDh6xRjE9EJ`^kUmeC{oQ9N5`OBJN zylYQOlaQj&92*kc|J10VDLm5Ut5I@(3U3`x2L_@Cb-o#%6^KSC8da{+kw<-@d@_Z{ zclJ#qNEOMe_FEn7M>|VHBaAF)Otw6mf}tyrZ3Rybu?zD92W4dlOsPV7Lhzdd428Z_ zC`YC8kty}0-SmTEG=KBrOf*oU2Wy%oI*dx+P30Z4Xa-O_AX5!0IWdL? zn}~*bI)##eZz|AR%^&3$lgD5*O%XxU#-5{;CXZx8wO(WL5ZRbKG-(LPwZr+Kn5(CY z@4y`&&PU+^i+co5nE5}_QHyEr;)ueLA(HWE!I%_Q@BB12-nU;GzIdh64F3%1e74LT z!TaNwvwQ@9bgVH|15uKpQnp@c+!Qb_PO&w@95=4g@uteGfaatzHH?ggiXM|YM)H}_ z=a3*{bTljkPVilc{`Ty}gmR7I6|L)^20xL8033_}iCOc<#bE_9VqRSs#s94tg(N6E z%b55yT-a>-&P_H0&rUxa&7Tc2%7ZUgA+yGCe9kC$jNuovt|>U<`qk*JAvoH7id+m{ zM^5Gx{miyOpg{*E(6CN@MT?s57vQ(mjrka+USwMl`mMnxBt0Ytwi%`ZZ`8?`K-} z*7A{Q=9=K!_~_|8%jInn6UXGU)A`{4)1Vx3n&ZKE3Z(ze3`1AW&fv%U80$6tKw)rj zk!YA11zJPuoS;G%Ay~3;ST9uE-QxtMMt(DsZ^J?Gt+RM{G)>IFEPzhYlGELemfZ>~<569%k#u|Bnh8g;*+tLfS#Z({AG3d>T$)=q9Q@8dPx8 z|DoYo^RL#QW@&uz6}Dw^nqgZOr}0CUZNax6vg-oU7th!QJdUvv&yt1wE`;*?MLaI1 z7RR^{mEmqFSO7KdF5!uI;{g+rkPf{r@eEGqNi5j@R~`Z}SXBrbt7bGK*Q z5Ye zF4$p!M<k)i`;}vqFwmU@HxN75D$h%sco05Mmt}Wg zXugp*-wEgGQ~B|od>n{c7XJ>0GccPkKuycD`L8O=^W2n!;j`P4cP|cP%zs%z?)8aR zHN`!@+{;@BvTd^YL;Ooev3a2L`$L#dHn?jm|K1B$J<4~eEzM*&!E$#dzeE*QzQo6Q zm5X2c#M7{gFJg2GdqWXlkm`L6^(=2S#C`yQhfe+ZCafOj$J@6eRt2xl$YFlIZ5=?HWtfN1vfXn$ zk@`FKIUb2?nNK`vyLo}i>tFL+{x=WcoxE=qs25wyETy#1tj(gVbL$>^dFw0Nd6MLC zu2L@C%b)3L)t&&O|6fkP4VG0{?|r-wT3o%4-yV4hXW$SsEP@uMMY`{UDG--Fk?AG8 z4Yhbn3IBlow?wzR#5-7t-e7X(ej|(Aw4cvr2jwUGdA}Bx#IYL;!Gy;>4Mg7hW!@^w z6zo#0tBzL_qX(#~AOYCzGWZ^%1|R(N;|D1U(tZlh7^D)5R7~M%gS6Ws6;XJ~AQf7q z0t!zWqWgnyELt9g)kd;hiD8xIos-V}viYAdnZ8xImC-V_JD zR6L6D#_`YL$WRY2NjM2o25J-Kf10&AQu}Ov-jmp2S7Bl+=z5i`@$l%Zst4eEBiT-n zPTKKZiSEM0AW>!IbZhQii7~sxF$;Itg-O^-3))#&Scn)nC+(D?#*Cq7G2)z;T5nT# z749l5Ou*CMAm@m}%MU-{*?5p2!A-Jf*sHvb^ZLJK*VlPE|J4Xb%gSPvp>eMX zfk1J3Q9yz%lKzMI%m`Ck@j3qJObLcSt)}PgQir(hq)WDi|%jt$G*K_e5t}=K=f4<8b z*g?;QO1_8TIp~@9d55^NzfjTjsL0RzNw;4rDhdao(n!KidmJC|{r*V>f1*51lp!!~ ztSSk|LQEEJB%FXZF<)2nTL%C92T+X2v$zRCF2#wp>D)Gu+lai!%uMK6)5lTbwc`e} z#`ECEydW^~ai}ENJ#SjN#}1^7a{L)yG^+|7HLl$0k(U=Rt(mC#8yxf28>Wo_ z8DV-@Rc>?e$a^i7rEF&{v}Um6$ff~xC3@UMxN#K$jq-l z%U4l&?kpefy{wkKzvCjS1~$R`y41V`#PPk=O}jBQZ(A+HziZU)~T`Na@k5$+uSANfju@RoV-}BD+SF_})9}v!xjvx8*&=l3Jv_kIDTv1{b zMSgXke+#Mq$k&I}fa4ENvcFql6w2s2-ajnK?`rabI^Kp&llRr}2U26#yOr@s{3LP) zA^dBKTbYFLJylU!`rtm`C-~qL;BW)WsXy^io+p3eD|yKK4?`0EDFyFe_QUC)C#s$w z;B336>KFcN%b>zUx1u3Sxu>$3I+L+_+190Y57OGYAs9vJA?Lc(o5Jz|+W-puQE&2P zr3R^Cg*cUwELR}e>~fHufJb|{ zl}LmeR+s`R0uBLw2_eQrIe?H_V)N~M?;<}9*v|+95&nVDLFweB5Os9B6=`k-gP{!V z;8tP~rsI!9)8ke}=>l4Fip!;;>Sp$)r#@6&!V%7Du71RW8oQV+3Xy#x)aS!Cp#gXs zU_e>_ASmf$&yy;!}!87B}#2Q@%f%^V<@ge3L3H@Llk8dXieG0tHX&M#X8YxmXtis=rCN+)QLx+*t=9J}?(L?hJ<4JHQEfK<198%3 z8~=%TFkx91qYewR$-&mp6iZjEI(4#59uIPBpWNxdMsBON)xacjJN7 z(%0PLQ-l*Wh56u?z^OiXI55e@E_>Wn{2E|tQrg>XcDvTCn|6nCQl5`j zC*3Ieo2uVurdz=bSMt`8!YfLOMYqF&9h!Lg^Gy9-Oa?9!7EZ%|$diLS6!yYj0%5tm zwb~s@^iFGaZCG-$IUBlQ#?Yiome*-&=dk@&rnMkN<8Qy*qN%StzWxO&*IQ0bP^0Di z1l3P2Pf%OV+=+ir#d$1fpxe-uHzCFdWKk^q0p={}xGzrh!Sz?A|Jla!RD#+)Z7Yh2 zL-M=vM^nefnTS(&Y`hV1N@wFcuEOcSzUeb;zRXBepJ&Okc^h?7uq}=gXhc_85~W7S zd)lax7`p9k)H(PL^kN$|3uoW!+Zw^ewvc3jY~N1pj9^MT=t{w0w=x}NPhh4%>xP)N z!!^{cGy~n{{|9l>9h=W>zC7AaU5L^;wO2bi8sHE(Uz9hrR};omBgI_gAYsnH-(+A^ z;#Ce?N$QuIbUQswShAnKD_&1|vAx=+=bgjc%2cqrp&Syfje7!u$_X9R zwOP%D+i1IOwt}w0f23S#q0a-8Ww+CR2OI$W1wxX^M^^YYLh9Ws2&u&{A|y+`7a>i9 zJqV#q(aKYZP{M~1h9bNhA$2oT&Pr1EEv-U(sQmO>SFKpKa9O6dDsAPmg)r2hlX=hGZcjp(;i%QO6w?D4x+Xns5&0mBo%T(nZGp5l{t&o_ z4}KT8kBvQ#^iiEG!cIF7U2A9agQ_acO05T28cnE?9a@orDX)Bg3(lR%$N*|~-xg#+c9dbvC;GWDx_$Zne z?}Hx)CZ)IeBnn^tbHFfZHs7J_0imN@H5&HjQXLP|R;F*bX+`?Vj7Ws9hmfX- z?^5Dh=rsAl!`WVm=dKhr zgt0Mlt57AL&L*X*9inpPx((N%I}(vfd4S0T<;bO}u(lKAwp8^?I%FHJ4oaxF(M?{q zl8c^^ZWz%<^P zTslJSFxe)50&*LlWG8`XC9?VJAhARryabq>CYwJSSR*WJMyP|juE?~^H(Ehcf7!}a z>oT>Q*R9Re7Nl$IG8cDERvN(@2wpEaW~4fmP4E89jm5>(iNN1LUulSy{WW?-M##n|z^9&s%emvR^RdZCKq6BdE-S~Y_uz!} z`U!9bw#n=XYU_60m$;QKV3In~Sm)vobKm&8AECjQ6%(-1*T|%a>LQ-G!LxOuihs#Z zQT{|`5{bIh-0%b-gp?E)LbOE*vFOdMFcKjKPl-ZE%T;THG`Bh+q-C(1Mek#U0}-N+ z${>W)s9^{xe=6Zwh>Wrr<1EI}wdSf&g^(Kd8A7VyD}>arvk1w?{DzQpVZyQ{OEX8L ztyzw^dzqOgLT;L*F3jo$NNpTyh2yO7dW6)LnFy&Z^DX)<2&u{}gw&TQOBRfz(o>gZ zD#xs}?;@n;j7VRcwr)k{tTpLtZ&|lyVLH}PrPj*#BSI`}a^Cgo0QRBGzaIX0j;y?1 z{Q$mQ!DO{xU=9v4oDU~%lcy%Db9zsM6V(>^`{Iwfrs2=F$5a>nHyEoS@vK!*x~8Zd zQu z(lXQS62Pmp(tLrCe4%wKm#@5K!%A&^+8R%nsp=B}{wHBcrptG3PlyQ+?8_|&s{J#aW0%nn?DGB>ks~w>*>vN)KMLCLH1SQ2eCSlVNS*$skyxhe)#jppTpBHGhotz{|8|- BmMZ`N diff --git a/assets/plugins/tab-bar.wasm b/assets/plugins/tab-bar.wasm index 39705bcb12e48b93a98c249b56f3c860807a1a74..6756e7c1ce7c4664d8333bd29e25cdb7156f93c6 100644 GIT binary patch delta 29840 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 delta 24427 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 A_y7O^ From 0f3590adb519e52034019997ee2a5a33d1270ef7 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Wed, 15 Sep 2021 16:41:07 +0200 Subject: [PATCH 32/42] chore(version): bump development version --- Cargo.lock | 12 ++++++------ Cargo.toml | 8 ++++---- zellij-client/Cargo.toml | 4 ++-- zellij-server/Cargo.toml | 4 ++-- zellij-tile-utils/Cargo.toml | 2 +- zellij-tile/Cargo.toml | 2 +- zellij-utils/Cargo.toml | 4 ++-- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8f3d4761..66a0b3cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2640,7 +2640,7 @@ dependencies = [ [[package]] name = "zellij" -version = "0.17.0" +version = "0.18.0" dependencies = [ "insta", "log", @@ -2654,7 +2654,7 @@ dependencies = [ [[package]] name = "zellij-client" -version = "0.17.0" +version = "0.18.0" dependencies = [ "insta", "log", @@ -2665,7 +2665,7 @@ dependencies = [ [[package]] name = "zellij-server" -version = "0.17.0" +version = "0.18.0" dependencies = [ "ansi_term 0.12.1", "async-trait", @@ -2687,7 +2687,7 @@ dependencies = [ [[package]] name = "zellij-tile" -version = "0.17.0" +version = "0.18.0" dependencies = [ "serde", "serde_json", @@ -2697,14 +2697,14 @@ dependencies = [ [[package]] name = "zellij-tile-utils" -version = "0.17.0" +version = "0.18.0" dependencies = [ "ansi_term 0.12.1", ] [[package]] name = "zellij-utils" -version = "0.17.0" +version = "0.18.0" dependencies = [ "async-std", "backtrace", diff --git a/Cargo.toml b/Cargo.toml index e5a7af57..738b4642 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zellij" -version = "0.17.0" +version = "0.18.0" authors = ["Aram Drevekenin "] edition = "2018" description = "A terminal workspace with batteries included" @@ -14,9 +14,9 @@ resolver = "2" [dependencies] names = "0.11.0" -zellij-client = { path = "zellij-client/", version = "0.17.0" } -zellij-server = { path = "zellij-server/", version = "0.17.0" } -zellij-utils = { path = "zellij-utils/", version = "0.17.0" } +zellij-client = { path = "zellij-client/", version = "0.18.0" } +zellij-server = { path = "zellij-server/", version = "0.18.0" } +zellij-utils = { path = "zellij-utils/", version = "0.18.0" } log = "0.4.14" [dev-dependencies] diff --git a/zellij-client/Cargo.toml b/zellij-client/Cargo.toml index 271fb716..b7b61425 100644 --- a/zellij-client/Cargo.toml +++ b/zellij-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zellij-client" -version = "0.17.0" +version = "0.18.0" authors = ["Kunal Mohan "] edition = "2018" description = "The client-side library for Zellij" @@ -11,7 +11,7 @@ license = "MIT" [dependencies] mio = "0.7.11" termbg = "0.2.3" -zellij-utils = { path = "../zellij-utils/", version = "0.17.0" } +zellij-utils = { path = "../zellij-utils/", version = "0.18.0" } log = "0.4.14" [dev-dependencies] diff --git a/zellij-server/Cargo.toml b/zellij-server/Cargo.toml index 1c8c986b..bc0abe31 100644 --- a/zellij-server/Cargo.toml +++ b/zellij-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zellij-server" -version = "0.17.0" +version = "0.18.0" authors = ["Kunal Mohan "] edition = "2018" description = "The server-side library for Zellij" @@ -19,7 +19,7 @@ unicode-width = "0.1.8" wasmer = "1.0.0" wasmer-wasi = "1.0.0" cassowary = "0.3.0" -zellij-utils = { path = "../zellij-utils/", version = "0.17.0" } +zellij-utils = { path = "../zellij-utils/", version = "0.18.0" } log = "0.4.14" typetag = "0.1.7" chrono = "0.4.19" diff --git a/zellij-tile-utils/Cargo.toml b/zellij-tile-utils/Cargo.toml index 79c9763f..6da76ce8 100644 --- a/zellij-tile-utils/Cargo.toml +++ b/zellij-tile-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zellij-tile-utils" -version = "0.17.0" +version = "0.18.0" authors = ["denis "] edition = "2018" description = "A utility library for Zellij plugins" diff --git a/zellij-tile/Cargo.toml b/zellij-tile/Cargo.toml index 0ee63e7f..2399e17e 100644 --- a/zellij-tile/Cargo.toml +++ b/zellij-tile/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zellij-tile" -version = "0.17.0" +version = "0.18.0" authors = ["Brooks J Rady "] edition = "2018" description = "A small client-side library for writing Zellij plugins" diff --git a/zellij-utils/Cargo.toml b/zellij-utils/Cargo.toml index 838b0c57..66e8c1cf 100644 --- a/zellij-utils/Cargo.toml +++ b/zellij-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zellij-utils" -version = "0.17.0" +version = "0.18.0" authors = ["Kunal Mohan "] edition = "2018" description = "A utility library for Zellij client and server" @@ -27,7 +27,7 @@ structopt = "0.3" strum = "0.20.0" termion = "1.5.0" vte = "0.10.1" -zellij-tile = { path = "../zellij-tile/", version = "0.17.0" } +zellij-tile = { path = "../zellij-tile/", version = "0.18.0" } log = "0.4.14" log4rs = "1.0.0" unicode-width = "0.1.8" From 40e74d5b8543a779d20af86f3aa6e996136996b6 Mon Sep 17 00:00:00 2001 From: spacemaison Date: Fri, 17 Sep 2021 00:06:47 -0700 Subject: [PATCH 33/42] fix(cwd-pane): Fix for cwd not being inherited when switching tabs (#729) fixes #727 Inheriting the current working directory didn't work when switching between tabs. This happened because the event to notify the pty of an pane change was triggered when setting the active pane inside of the current tab, but switching between tabs will only cause a re-render of the newly selected tab and it's panes without setting the active pane. This was fixed by moving the event to notify the pty of the pane change into the tabs render method. Co-authored-by: Jesse Tuchsen --- zellij-server/src/tab.rs | 48 ++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/zellij-server/src/tab.rs b/zellij-server/src/tab.rs index dafb2863..c936a06a 100644 --- a/zellij-server/src/tab.rs +++ b/zellij-server/src/tab.rs @@ -295,13 +295,6 @@ impl Tab { } } - fn set_active_terminal(&mut self, pane_id: Option) { - self.active_terminal = pane_id; - self.senders - .send_to_pty(PtyInstruction::UpdateActivePane(self.active_terminal)) - .unwrap(); - } - pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec, tab_index: usize) { // TODO: this should be an attribute on Screen instead of full_screen_ws let free_space = PaneGeom::default(); @@ -400,7 +393,7 @@ impl Tab { self.set_pane_frames(self.draw_pane_frames); // This is the end of the nasty viewport hack... // FIXME: Active / new / current terminal, should be pane - self.set_active_terminal(self.panes.iter().map(|(id, _)| id.to_owned()).next()); + self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next(); self.render(); } pub fn new_pane(&mut self, pid: PaneId) { @@ -473,7 +466,7 @@ impl Tab { } } } - self.set_active_terminal(Some(pid)); + self.active_terminal = Some(pid); self.render(); } pub fn horizontal_split(&mut self, pid: PaneId) { @@ -502,7 +495,7 @@ impl Tab { ); active_pane.set_geom(top_winsize); self.panes.insert(pid, Box::new(new_terminal)); - self.set_active_terminal(Some(pid)); + self.active_terminal = Some(pid); self.relayout_tab(Direction::Vertical); self.render(); } @@ -531,7 +524,7 @@ impl Tab { active_pane.set_geom(left_winsize); self.panes.insert(pid, Box::new(new_terminal)); } - self.set_active_terminal(Some(pid)); + self.active_terminal = Some(pid); self.relayout_tab(Direction::Horizontal); self.render(); } @@ -745,6 +738,9 @@ impl Tab { // or if this session is not attached to a client, we do not have to render return; } + self.senders + .send_to_pty(PtyInstruction::UpdateActivePane(self.active_terminal)) + .unwrap(); let mut output = String::new(); let mut boundaries = Boundaries::new(self.viewport); let hide_cursor = "\u{1b}[?25l"; @@ -1773,7 +1769,7 @@ impl Tab { .or_else(|| terminal_ids.get(0)) .copied(); - self.set_active_terminal(active_terminal); + self.active_terminal = active_terminal; self.render(); } pub fn focus_next_pane(&mut self) { @@ -1802,7 +1798,7 @@ impl Tab { .or_else(|| panes.get(0)) .map(|p| *p.0); - self.set_active_terminal(active_terminal); + self.active_terminal = active_terminal; self.render(); } pub fn focus_previous_pane(&mut self) { @@ -1832,7 +1828,7 @@ impl Tab { } else { Some(*panes.get(active_pane_position - 1).unwrap().0) }; - self.set_active_terminal(active_terminal); + self.active_terminal = active_terminal; self.render(); } // returns a boolean that indicates whether the focus moved @@ -1863,7 +1859,7 @@ impl Tab { let next_active_pane = self.panes.get_mut(&p).unwrap(); next_active_pane.set_should_render(true); - self.set_active_terminal(Some(p)); + self.active_terminal = Some(p); self.render(); return true; } @@ -1872,7 +1868,7 @@ impl Tab { } else { Some(active_terminal.unwrap().pid()) }; - self.set_active_terminal(updated_active_terminal); + self.active_terminal = updated_active_terminal; false } pub fn move_focus_down(&mut self) { @@ -1909,7 +1905,7 @@ impl Tab { } else { Some(active_terminal.unwrap().pid()) }; - self.set_active_terminal(updated_active_terminal); + self.active_terminal = updated_active_terminal; self.render(); } pub fn move_focus_up(&mut self) { @@ -1946,7 +1942,7 @@ impl Tab { } else { Some(active_terminal.unwrap().pid()) }; - self.set_active_terminal(updated_active_terminal); + self.active_terminal = updated_active_terminal; self.render(); } // returns a boolean that indicates whether the focus moved @@ -1977,7 +1973,7 @@ impl Tab { let next_active_pane = self.panes.get_mut(&p).unwrap(); next_active_pane.set_should_render(true); - self.set_active_terminal(Some(p)); + self.active_terminal = Some(p); self.render(); return true; } @@ -1986,7 +1982,7 @@ impl Tab { } else { Some(active_terminal.unwrap().pid()) }; - self.set_active_terminal(updated_active_terminal); + self.active_terminal = updated_active_terminal; false } fn horizontal_borders(&self, terminals: &[PaneId]) -> HashSet { @@ -2132,7 +2128,7 @@ impl Tab { if let Some(pane) = self.panes.get_mut(&id) { pane.set_selectable(selectable); if self.get_active_pane_id() == Some(id) && !selectable { - self.set_active_terminal(self.next_active_pane(&self.get_pane_ids())); + self.active_terminal = self.next_active_pane(&self.get_pane_ids()); } } self.render(); @@ -2153,7 +2149,7 @@ impl Tab { self.panes.remove(&id); if self.active_terminal == Some(id) { let next_active_pane = self.next_active_pane(&panes); - self.set_active_terminal(next_active_pane); + self.active_terminal = next_active_pane; } self.relayout_tab(Direction::Horizontal); return; @@ -2165,7 +2161,7 @@ impl Tab { self.panes.remove(&id); if self.active_terminal == Some(id) { let next_active_pane = self.next_active_pane(&panes); - self.set_active_terminal(next_active_pane); + self.active_terminal = next_active_pane; } self.relayout_tab(Direction::Horizontal); return; @@ -2177,7 +2173,7 @@ impl Tab { self.panes.remove(&id); if self.active_terminal == Some(id) { let next_active_pane = self.next_active_pane(&panes); - self.set_active_terminal(next_active_pane); + self.active_terminal = next_active_pane; } self.relayout_tab(Direction::Vertical); return; @@ -2189,7 +2185,7 @@ impl Tab { self.panes.remove(&id); if self.active_terminal == Some(id) { let next_active_pane = self.next_active_pane(&panes); - self.set_active_terminal(next_active_pane); + self.active_terminal = next_active_pane; } self.relayout_tab(Direction::Vertical); return; @@ -2311,7 +2307,7 @@ impl Tab { } fn focus_pane_at(&mut self, point: &Position) { if let Some(clicked_pane) = self.get_pane_id_at(point) { - self.set_active_terminal(Some(clicked_pane)); + self.active_terminal = Some(clicked_pane); self.render(); } } From 72b0474d024abc799a2f5e90a1078f68b150502a Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Fri, 17 Sep 2021 09:08:10 +0200 Subject: [PATCH 34/42] docs(changelog): update cwd fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff59c9b7..9b35c75f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] +* Fix: Properly open new pane with CWD also when switching to a new tab (https://github.com/zellij-org/zellij/pull/729) ## [0.17.0] - 2021-09-15 * New panes/tabs now open in CWD of focused pane (https://github.com/zellij-org/zellij/pull/691) From ac7bcf11696157399b148ed373b329569112d85f Mon Sep 17 00:00:00 2001 From: Tw Date: Sun, 19 Sep 2021 02:36:15 +0800 Subject: [PATCH 35/42] fix(server): fix leakage of SessionMetaData (#728) There are different reasons leading the server thread exits, currently we only release the cached session data when client exits normally. This fix covers all the cases. Signed-off-by: Tw --- zellij-server/src/lib.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 9073ed3a..419aae4f 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -170,19 +170,13 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { thread_handles.lock().unwrap().push( thread::Builder::new() .name("server_router".to_string()) - .spawn({ - let session_data = session_data.clone(); - let os_input = os_input.clone(); - let to_server = to_server.clone(); - - move || { - route_thread_main( - session_data, - session_state, - os_input, - to_server, - ) - } + .spawn(move || { + route_thread_main( + session_data, + session_state, + os_input, + to_server, + ) }) .unwrap(), ); @@ -267,7 +261,6 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { } } ServerInstruction::ClientExit => { - *session_data.write().unwrap() = None; os_input.send_to_client(ServerToClientMsg::Exit(ExitReason::Normal)); break; } @@ -297,6 +290,10 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { } } } + + // Drop cached session data before exit. + *session_data.write().unwrap() = None; + thread_handles .lock() .unwrap() From eb22a6c1717fee65d1f5e6cab0a31aad0e7972a5 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Sun, 19 Sep 2021 13:38:53 +0200 Subject: [PATCH 36/42] Add link to docs to README.md (#732) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 916f6720..ac36035e 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ - # What is this? [Zellij](https://en.wikipedia.org/wiki/Zellij) is a workspace aimed at developers, ops-oriented people and anyone who loves the terminal. From b761a28702f0a13085bb8b2a516ed4a614971a31 Mon Sep 17 00:00:00 2001 From: GPery Date: Sun, 19 Sep 2021 15:47:52 +0300 Subject: [PATCH 37/42] Implement `attach --create` subcommand flag to create session if one does not exist (#731) * Implement attach --create * fixup! Implement attach --create * fixup! Implement attach --create * fixup! Implement attach --create * fixup! Implement attach --create --- src/main.rs | 71 ++++++++++++++++++++++++++++++++++------- src/sessions.rs | 50 +++++++++++++++++++++-------- zellij-utils/src/cli.rs | 5 +++ 3 files changed, 101 insertions(+), 25 deletions(-) diff --git a/src/main.rs b/src/main.rs index 29446450..0a91ca65 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,10 @@ mod sessions; mod tests; use crate::install::populate_data_dir; -use sessions::{assert_session, assert_session_ne, get_active_session, list_sessions}; +use sessions::{ + assert_session, assert_session_ne, get_active_session, get_sessions, list_sessions, + print_sessions, session_exists, ActiveSession, +}; use std::process; use zellij_client::{os_input_output::get_client_os_input, start_client, ClientInfo}; use zellij_server::{os_input_output::get_server_os_input, start_server}; @@ -52,29 +55,75 @@ pub fn main() { } }; if let Some(Command::Sessions(Sessions::Attach { - mut session_name, + session_name, force, + create, options, })) = opts.command.clone() { - if let Some(session) = session_name.as_ref() { - assert_session(session); - } else { - session_name = Some(get_active_session()); - } - let config_options = match options { Some(SessionCommand::Options(o)) => config_options.merge(o), None => config_options, }; + let (client, attach_layout) = match session_name.as_ref() { + Some(session) => { + if create { + if !session_exists(session).unwrap() { + (ClientInfo::New(session_name.unwrap()), layout) + } else { + ( + ClientInfo::Attach( + session_name.unwrap(), + force, + config_options.clone(), + ), + None, + ) + } + } else { + assert_session(session); + ( + ClientInfo::Attach( + session_name.unwrap(), + force, + config_options.clone(), + ), + None, + ) + } + } + None => match get_active_session() { + ActiveSession::None => { + if create { + ( + ClientInfo::New(names::Generator::default().next().unwrap()), + layout, + ) + } else { + println!("No active zellij sessions found."); + process::exit(1); + } + } + ActiveSession::One(session_name) => ( + ClientInfo::Attach(session_name, force, config_options.clone()), + None, + ), + ActiveSession::Many => { + println!("Please specify the session name to attach to. The following sessions are active:"); + print_sessions(get_sessions().unwrap()); + process::exit(1); + } + }, + }; + start_client( Box::new(os_input), opts, config, - config_options.clone(), - ClientInfo::Attach(session_name.unwrap(), force, config_options), - None, + config_options, + client, + attach_layout, ); } else { let session_name = opts diff --git a/src/sessions.rs b/src/sessions.rs index fb1a9635..9f1e20f0 100644 --- a/src/sessions.rs +++ b/src/sessions.rs @@ -6,7 +6,7 @@ use zellij_utils::{ ipc::{ClientToServerMsg, IpcSenderWithContext}, }; -fn get_sessions() -> Result, io::ErrorKind> { +pub(crate) fn get_sessions() -> Result, io::ErrorKind> { match fs::read_dir(&*ZELLIJ_SOCK_DIR) { Ok(files) => { let mut sessions = Vec::new(); @@ -47,7 +47,7 @@ fn assert_socket(name: &str) -> bool { } } -fn print_sessions(sessions: Vec) { +pub(crate) fn print_sessions(sessions: Vec) { let curr_session = std::env::var("ZELLIJ_SESSION_NAME").unwrap_or_else(|_| "".into()); sessions.iter().for_each(|session| { let suffix = if curr_session == *session { @@ -59,22 +59,29 @@ fn print_sessions(sessions: Vec) { }) } -pub(crate) fn get_active_session() -> String { +pub(crate) enum ActiveSession { + None, + One(String), + Many, +} + +pub(crate) fn get_active_session() -> ActiveSession { match get_sessions() { Ok(mut sessions) => { if sessions.len() == 1 { - return sessions.pop().unwrap(); + return ActiveSession::One(sessions.pop().unwrap()); } if sessions.is_empty() { - println!("No active zellij sessions found."); + ActiveSession::None } else { - println!("Please specify the session name to attach to. The following sessions are active:"); - print_sessions(sessions); + ActiveSession::Many } } - Err(e) => eprintln!("Error occured: {:?}", e), + Err(e) => { + eprintln!("Error occured: {:?}", e); + process::exit(1); + } } - process::exit(1); } pub(crate) fn list_sessions() { @@ -95,15 +102,30 @@ pub(crate) fn list_sessions() { process::exit(exit_code); } -pub(crate) fn assert_session(name: &str) { - match get_sessions() { +pub(crate) fn session_exists(name: &str) -> Result { + return match get_sessions() { Ok(sessions) => { if sessions.iter().any(|s| s == name) { - return; + return Ok(true); } - println!("No session named {:?} found.", name); + Ok(false) + } + Err(e) => Err(e), + }; +} + +pub(crate) fn assert_session(name: &str) { + match session_exists(name) { + Ok(result) => { + if result { + return; + } else { + println!("No session named {:?} found.", name); + } + } + Err(e) => { + eprintln!("Error occured: {:?}", e); } - Err(e) => eprintln!("Error occured: {:?}", e), }; process::exit(1); } diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs index a8dc9c0a..eb52589a 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -85,6 +85,11 @@ pub enum Sessions { /// zellij client (if any) and attach to this. #[structopt(long, short)] force: bool, + + /// Create a session if one does not exist. + #[structopt(short, long)] + create: bool, + /// Change the behaviour of zellij #[structopt(subcommand, name = "options")] options: Option, From d16cd31927052ac97057c25ee964003bb2d82312 Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Sun, 19 Sep 2021 18:32:59 +0530 Subject: [PATCH 38/42] Docs(changelog): attach --create feature (#731) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b35c75f..853e8ebd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] * Fix: Properly open new pane with CWD also when switching to a new tab (https://github.com/zellij-org/zellij/pull/729) +* Feature: Option to create a new session if attach fails (`zellij attach --create`) (https://github.com/zellij-org/zellij/pull/731) ## [0.17.0] - 2021-09-15 * New panes/tabs now open in CWD of focused pane (https://github.com/zellij-org/zellij/pull/691) From 4a7983f1c6edfe39e249f1c5803dcd262d05659a Mon Sep 17 00:00:00 2001 From: Tw Date: Sun, 19 Sep 2021 21:14:50 +0800 Subject: [PATCH 39/42] feat(plugin): add visibility event for the plugin (#717) When a tab becomes active/inactive, it send corresponding visibility event to its containing plugins. Signed-off-by: Tw --- zellij-server/src/screen.rs | 79 +++++++++++++++++++------------------ zellij-server/src/tab.rs | 15 +++++++ zellij-tile/src/data.rs | 1 + 3 files changed, 57 insertions(+), 38 deletions(-) diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index d751f81c..5e1addb5 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -188,22 +188,37 @@ impl Screen { } } + /// A helper function to switch to a new tab at specified position. + fn switch_active_tab(&mut self, new_tab_pos: usize) { + if let Some(new_tab) = self.tabs.values().find(|t| t.position == new_tab_pos) { + let current_tab = self.get_active_tab().unwrap(); + + // If new active tab is same as the current one, do nothing. + if current_tab.position == new_tab_pos { + return; + } + + current_tab.visible(false); + let new_tab_index = new_tab.index; + let new_tab = self.get_indexed_tab_mut(new_tab_index).unwrap(); + new_tab.set_force_render(); + new_tab.visible(true); + + let old_active_index = self.active_tab_index.replace(new_tab_index); + self.tab_history.retain(|&e| e != Some(new_tab_pos)); + self.tab_history.push(old_active_index); + + self.update_tabs(); + self.render(); + } + } + /// Sets this [`Screen`]'s active [`Tab`] to the next tab. pub fn switch_tab_next(&mut self) { let active_tab_pos = self.get_active_tab().unwrap().position; let new_tab_pos = (active_tab_pos + 1) % self.tabs.len(); - for tab in self.tabs.values_mut() { - if tab.position == new_tab_pos { - tab.set_force_render(); - self.tab_history.retain(|&e| e != Some(tab.index)); - self.tab_history.push(self.active_tab_index); - self.active_tab_index = Some(tab.index); - break; - } - } - self.update_tabs(); - self.render(); + self.switch_active_tab(new_tab_pos); } /// Sets this [`Screen`]'s active [`Tab`] to the previous tab. @@ -214,32 +229,12 @@ impl Screen { } else { active_tab_pos - 1 }; - for tab in self.tabs.values_mut() { - if tab.position == new_tab_pos { - tab.set_force_render(); - self.tab_history.retain(|&e| e != Some(tab.index)); - self.tab_history.push(self.active_tab_index); - self.active_tab_index = Some(tab.index); - break; - } - } - self.update_tabs(); - self.render(); + + self.switch_active_tab(new_tab_pos); } - pub fn go_to_tab(&mut self, mut tab_index: usize) { - tab_index -= 1; - let active_tab_index = self.get_active_tab().unwrap().index; - if let Some(t) = self.tabs.values_mut().find(|t| t.position == tab_index) { - if t.index != active_tab_index { - t.set_force_render(); - self.tab_history.retain(|&e| e != Some(t.index)); - self.tab_history.push(self.active_tab_index); - self.active_tab_index = Some(t.index); - self.update_tabs(); - self.render(); - } - } + pub fn go_to_tab(&mut self, tab_index: usize) { + self.switch_active_tab(tab_index - 1); } /// Closes this [`Screen`]'s active [`Tab`], exiting the application if it happens @@ -264,10 +259,14 @@ impl Screen { .unwrap(); } } else { + if let Some(tab) = self.get_active_tab() { + tab.visible(false); + } self.active_tab_index = self.tab_history.pop().unwrap(); for t in self.tabs.values_mut() { if t.index == self.active_tab_index.unwrap() { - t.set_force_render() + t.set_force_render(); + t.visible(true); } if t.position > active_tab.position { t.position -= 1; @@ -357,8 +356,12 @@ impl Screen { self.draw_pane_frames, ); tab.apply_layout(layout, new_pids, tab_index); - self.tab_history.push(self.active_tab_index); - self.active_tab_index = Some(tab_index); + if let Some(active_tab) = self.get_active_tab() { + active_tab.visible(false); + } + self.tab_history + .push(self.active_tab_index.replace(tab_index)); + tab.visible(true); self.tabs.insert(tab_index, tab); self.update_tabs(); } diff --git a/zellij-server/src/tab.rs b/zellij-server/src/tab.rs index c936a06a..62e0a2cb 100644 --- a/zellij-server/src/tab.rs +++ b/zellij-server/src/tab.rs @@ -2396,6 +2396,21 @@ impl Tab { } } } + + pub fn visible(&self, visible: bool) { + let pids_in_this_tab = self.panes.keys().filter_map(|p| match p { + PaneId::Plugin(pid) => Some(pid), + _ => None, + }); + for pid in pids_in_this_tab { + self.senders + .send_to_plugin(PluginInstruction::Update( + Some(*pid), + Event::Visible(visible), + )) + .unwrap(); + } + } } #[allow(clippy::borrowed_box)] diff --git a/zellij-tile/src/data.rs b/zellij-tile/src/data.rs index b22be626..44708627 100644 --- a/zellij-tile/src/data.rs +++ b/zellij-tile/src/data.rs @@ -35,6 +35,7 @@ pub enum Event { Timer(f64), CopyToClipboard, InputReceived, + Visible(bool), } /// Describes the different input modes, which change the way that keystrokes will be interpreted. From 84e045aeefbf5bb83eafaa37450ca18be1d16bee Mon Sep 17 00:00:00 2001 From: Brooks Rady Date: Sun, 19 Sep 2021 14:17:20 +0100 Subject: [PATCH 40/42] chore(docs): add new `Visible` event --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 853e8ebd..77843e1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] * Fix: Properly open new pane with CWD also when switching to a new tab (https://github.com/zellij-org/zellij/pull/729) * Feature: Option to create a new session if attach fails (`zellij attach --create`) (https://github.com/zellij-org/zellij/pull/731) +* Feature: Added the new `Visible` event, allowing plugins to detect if they are visible in the current tab (https://github.com/zellij-org/zellij/pull/717) ## [0.17.0] - 2021-09-15 * New panes/tabs now open in CWD of focused pane (https://github.com/zellij-org/zellij/pull/691) From 4b792ca29fa2c4c783e928cddcaeb8cf1adaf5c4 Mon Sep 17 00:00:00 2001 From: Tw Date: Sun, 19 Sep 2021 23:20:05 +0800 Subject: [PATCH 41/42] feat(plugin): add plugin data directories (#723) Every plugin will have following two directories for its use: `./data`: Plugin's own data should be saved here, every plugin will have its own directory. `/host/`: All plugins have access to this directory, it defaults to the current working directory of Zellij. Signed-off-by: Tw --- zellij-server/src/wasm_vm.rs | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/zellij-server/src/wasm_vm.rs b/zellij-server/src/wasm_vm.rs index cda11429..7475bf46 100644 --- a/zellij-server/src/wasm_vm.rs +++ b/zellij-server/src/wasm_vm.rs @@ -56,6 +56,7 @@ pub(crate) struct PluginEnv { pub subscriptions: Arc>>, // FIXME: Once permission system is ready, this could be removed pub _allow_exec_host_cmd: bool, + plugin_own_data_dir: PathBuf, } // Thread main -------------------------------------------------------------------------------------------------------- @@ -63,12 +64,15 @@ pub(crate) fn wasm_thread_main(bus: Bus, store: Store, data_d info!("Wasm main thread starts"); let mut plugin_id = 0; let mut plugin_map = HashMap::new(); + let plugin_dir = data_dir.join("plugins/"); + let plugin_global_data_dir = plugin_dir.join("data"); + fs::create_dir_all(plugin_global_data_dir.as_path()).unwrap(); + loop { let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel"); err_ctx.add_call(ContextType::Plugin((&event).into())); match event { PluginInstruction::Load(pid_tx, path, tab_index, _allow_exec_host_cmd) => { - let plugin_dir = data_dir.join("plugins/"); let wasm_bytes = fs::read(&path) .or_else(|_| fs::read(&path.with_extension("wasm"))) .or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm"))) @@ -83,15 +87,15 @@ pub(crate) fn wasm_thread_main(bus: Bus, store: Store, data_d path.as_path().file_name().unwrap().to_str().unwrap(), plugin_id, ); + + let plugin_name = path.as_path().file_stem().unwrap(); + let plugin_own_data_dir = plugin_global_data_dir.join(plugin_name); + let mut wasi_env = WasiState::new("Zellij") .env("CLICOLOR_FORCE", "1") - .preopen(|p| { - p.directory(".") // FIXME: Change this to a more meaningful dir - .alias(".") - .read(true) - .write(true) - .create(true) - }) + .map_dir("/host", ".") + .unwrap() + .map_dir("/data", plugin_own_data_dir.as_path()) .unwrap() .stdin(Box::new(input)) .stdout(Box::new(output)) @@ -112,6 +116,7 @@ pub(crate) fn wasm_thread_main(bus: Bus, store: Store, data_d wasi_env, subscriptions: Arc::new(Mutex::new(HashSet::new())), _allow_exec_host_cmd, + plugin_own_data_dir, }; let zellij = zellij_exports(&store, &plugin_env); @@ -154,10 +159,16 @@ pub(crate) fn wasm_thread_main(bus: Bus, store: Store, data_d buf_tx.send(wasi_read_string(&plugin_env.wasi_env)).unwrap(); } } - PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)), + PluginInstruction::Unload(pid) => { + info!("Bye from plugin {}", &pid); + // TODO: remove plugin's own data directory + drop(plugin_map.remove(&pid)); + } PluginInstruction::Exit => break, } } + info!("wasm main thread exits"); + fs::remove_dir_all(plugin_global_data_dir.as_path()).unwrap(); } // Plugin API --------------------------------------------------------------------------------------------------------- From 16844c375dd767b24c5c0e9e322a4191093ffa2f Mon Sep 17 00:00:00 2001 From: Brooks Rady Date: Sun, 19 Sep 2021 16:22:01 +0100 Subject: [PATCH 42/42] chore(docs): update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77843e1a..316de361 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Fix: Properly open new pane with CWD also when switching to a new tab (https://github.com/zellij-org/zellij/pull/729) * Feature: Option to create a new session if attach fails (`zellij attach --create`) (https://github.com/zellij-org/zellij/pull/731) * Feature: Added the new `Visible` event, allowing plugins to detect if they are visible in the current tab (https://github.com/zellij-org/zellij/pull/717) +* Feature: Plugins now have access to a data directory at `/data` – the working directory is now mounted at `/host` instead of `.` (https://github.com/zellij-org/zellij/pull/723) ## [0.17.0] - 2021-09-15 * New panes/tabs now open in CWD of focused pane (https://github.com/zellij-org/zellij/pull/691)

Discord Chat + Zellij documentation