refactor(plugins): change the data flow (#1934)

* refactor(plugins): do not block render loop

* refactor(plugins): cleanup

* style(fmt): rustfmt

* fix(plugins): various rendering pipeline fixes
This commit is contained in:
Aram Drevekenin 2022-11-15 12:21:36 +01:00 committed by GitHub
parent b2b5bdc564
commit 3d2a6d6a5a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1055 additions and 458 deletions

681
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -129,10 +129,10 @@ impl ZellijPlugin for State {
};
match background {
PaletteColor::Rgb((r, g, b)) => {
println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", s, r, g, b);
print!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", s, r, g, b);
},
PaletteColor::EightBit(color) => {
println!("{}\u{1b}[48;5;{}m\u{1b}[0K", s, color);
print!("{}\u{1b}[48;5;{}m\u{1b}[0K", s, color);
},
}
self.should_render = false;

View file

@ -232,7 +232,7 @@ impl ZellijPlugin for State {
}
if rows > 1 {
println!("\u{1b}[m{}\u{1b}[0K", second_line);
print!("\u{1b}[m{}\u{1b}[0K", second_line);
}
}
}

View file

@ -93,6 +93,7 @@ impl ZellijPlugin for State {
*self.scroll_mut() = self.selected() + 2 - rows;
}
let is_last_row = i == rows.saturating_sub(1);
let i = self.scroll() + i;
if let Some(entry) = self.files.get(i) {
let mut path = entry.as_line(cols).normal();
@ -102,11 +103,19 @@ impl ZellijPlugin for State {
}
if i == self.selected() {
if is_last_row {
print!("{}", path.reversed());
} else {
println!("{}", path.reversed());
}
} else {
if is_last_row {
print!("{}", path);
} else {
println!("{}", path);
}
} else {
}
} else if !is_last_row {
println!();
}
}

View file

@ -127,10 +127,10 @@ impl ZellijPlugin for State {
};
match background {
PaletteColor::Rgb((r, g, b)) => {
println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", s, r, g, b);
print!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", s, r, g, b);
},
PaletteColor::EightBit(color) => {
println!("{}\u{1b}[48;5;{}m\u{1b}[0K", s, color);
print!("{}\u{1b}[48;5;{}m\u{1b}[0K", s, color);
},
}
self.should_render = false;

View file

@ -19,7 +19,6 @@ macro_rules! asset_map {
#[cfg(not(feature = "disable_automatic_asset_installation"))]
pub(crate) fn populate_data_dir(data_dir: &Path) {
// First run installation of default plugins & layouts
let mut assets = asset_map! {
"assets/plugins/compact-bar.wasm" => "plugins/compact-bar.wasm",
"assets/plugins/status-bar.wasm" => "plugins/status-bar.wasm",

View file

@ -847,7 +847,10 @@ pub fn resize_terminal_window() {
name: "wait for terminal to be resized and app to be re-rendered",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(53, 2) && remote_terminal.tip_appears() {
if remote_terminal.cursor_position_is(53, 2)
&& remote_terminal.tip_appears()
&& remote_terminal.snapshot_contains("Ctrl +")
{
// size has been changed
step_is_complete = true;
}

View file

@ -18,8 +18,8 @@ daemonize = "0.4.1"
serde_json = "1.0"
unicode-width = "0.1.8"
url = "2.2.2"
wasmer = "1.0.0"
wasmer-wasi = "1.0.0"
wasmer = "2.3.0"
wasmer-wasi = "2.3.0"
cassowary = "0.3.0"
zellij-utils = { path = "../zellij-utils/", version = "0.34.0" }
log = "0.4.17"

View file

@ -114,7 +114,6 @@ impl Seek for LoggingPipe {
}
}
#[typetag::serde]
impl WasiFile for LoggingPipe {
fn last_accessed(&self) -> u64 {
0

View file

@ -1,6 +1,7 @@
mod floating_pane_grid;
use zellij_utils::position::Position;
use crate::resize_pty;
use crate::tab::Pane;
use floating_pane_grid::FloatingPaneGrid;
@ -8,7 +9,9 @@ use crate::{
os_input_output::ServerOsApi,
output::{FloatingPanesStack, Output},
panes::{ActivePanes, PaneId},
thread_bus::ThreadSenders,
ui::pane_contents_and_ui::PaneContentsAndUi,
wasm_vm::PluginInstruction,
ClientId,
};
use std::cell::RefCell;
@ -22,22 +25,6 @@ use zellij_utils::{
pane_size::{Offset, PaneGeom, Size, Viewport},
};
macro_rules! resize_pty {
($pane:expr, $os_input:expr) => {
if let PaneId::Terminal(ref pid) = $pane.pid() {
// FIXME: This `set_terminal_size_using_terminal_id` call would be best in
// `TerminalPane::reflow_lines`
$os_input.set_terminal_size_using_terminal_id(
*pid,
$pane.get_content_columns() as u16,
$pane.get_content_rows() as u16,
)
} else {
Ok(())
}
};
}
pub struct FloatingPanes {
panes: BTreeMap<PaneId, Box<dyn Pane>>,
display_area: Rc<RefCell<Size>>,
@ -53,6 +40,7 @@ pub struct FloatingPanes {
active_panes: ActivePanes,
show_panes: bool,
pane_being_moved_with_mouse: Option<(PaneId, Position)>,
senders: ThreadSenders,
}
#[allow(clippy::borrowed_box)]
@ -68,6 +56,7 @@ impl FloatingPanes {
default_mode_info: ModeInfo,
style: Style,
os_input: Box<dyn ServerOsApi>,
senders: ThreadSenders,
) -> Self {
FloatingPanes {
panes: BTreeMap::new(),
@ -84,6 +73,7 @@ impl FloatingPanes {
show_panes: false,
active_panes: ActivePanes::new(&os_input),
pane_being_moved_with_mouse: None,
senders,
}
}
pub fn stack(&self) -> Option<FloatingPanesStack> {
@ -241,7 +231,7 @@ impl FloatingPanes {
} else {
pane.set_content_offset(Offset::default());
}
resize_pty!(pane, os_api).unwrap();
resize_pty!(pane, os_api, self.senders).unwrap();
}
}
pub fn render(&mut self, output: &mut Output) -> Result<()> {
@ -321,7 +311,7 @@ impl FloatingPanes {
}
pub fn resize_pty_all_panes(&mut self, os_api: &mut Box<dyn ServerOsApi>) {
for pane in self.panes.values_mut() {
resize_pty!(pane, os_api).unwrap();
resize_pty!(pane, os_api, self.senders).unwrap();
}
}
pub fn resize_active_pane_left(
@ -341,7 +331,7 @@ impl FloatingPanes {
);
floating_pane_grid.resize_pane_left(active_floating_pane_id);
for pane in self.panes.values_mut() {
resize_pty!(pane, os_api).unwrap();
resize_pty!(pane, os_api, self.senders).unwrap();
}
self.set_force_render();
return true;
@ -365,7 +355,7 @@ impl FloatingPanes {
);
floating_pane_grid.resize_pane_right(active_floating_pane_id);
for pane in self.panes.values_mut() {
resize_pty!(pane, os_api).unwrap();
resize_pty!(pane, os_api, self.senders).unwrap();
}
self.set_force_render();
return true;
@ -389,7 +379,7 @@ impl FloatingPanes {
);
floating_pane_grid.resize_pane_down(active_floating_pane_id);
for pane in self.panes.values_mut() {
resize_pty!(pane, os_api).unwrap();
resize_pty!(pane, os_api, self.senders).unwrap();
}
self.set_force_render();
return true;
@ -413,7 +403,7 @@ impl FloatingPanes {
);
floating_pane_grid.resize_pane_up(active_floating_pane_id);
for pane in self.panes.values_mut() {
resize_pty!(pane, os_api).unwrap();
resize_pty!(pane, os_api, self.senders).unwrap();
}
self.set_force_render();
return true;
@ -437,7 +427,7 @@ impl FloatingPanes {
);
floating_pane_grid.resize_increase(active_floating_pane_id);
for pane in self.panes.values_mut() {
resize_pty!(pane, os_api).unwrap();
resize_pty!(pane, os_api, self.senders).unwrap();
}
self.set_force_render();
return true;
@ -461,7 +451,7 @@ impl FloatingPanes {
);
floating_pane_grid.resize_decrease(active_floating_pane_id);
for pane in self.panes.values_mut() {
resize_pty!(pane, os_api).unwrap();
resize_pty!(pane, os_api, self.senders).unwrap();
}
self.set_force_render();
return true;

View file

@ -4,6 +4,8 @@ use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use unicode_width::UnicodeWidthChar;
use zellij_utils::data::Style;
use zellij_utils::errors::prelude::*;
use zellij_utils::regex::Regex;
use std::{
@ -549,7 +551,7 @@ impl Grid {
pub fn cursor_shape(&self) -> CursorShape {
self.cursor.get_shape()
}
pub fn scrollback_position_and_length(&mut self) -> (usize, usize) {
pub fn scrollback_position_and_length(&self) -> (usize, usize) {
// (position, length)
(
self.lines_below.len(),
@ -979,6 +981,71 @@ impl Grid {
(changed_character_chunks, changed_sixel_image_chunks)
}
pub fn render(
&mut self,
content_x: usize,
content_y: usize,
style: &Style,
) -> Result<Option<(Vec<CharacterChunk>, Option<String>, Vec<SixelImageChunk>)>> {
let mut raw_vte_output = String::new();
let (mut character_chunks, sixel_image_chunks) = self.read_changes(content_x, content_y);
for character_chunk in character_chunks.iter_mut() {
character_chunk.add_changed_colors(self.changed_colors);
if self
.selection
.contains_row(character_chunk.y.saturating_sub(content_y))
{
let background_color = match style.colors.bg {
PaletteColor::Rgb(rgb) => AnsiCode::RgbCode(rgb),
PaletteColor::EightBit(col) => AnsiCode::ColorIndex(col),
};
character_chunk.add_selection_and_colors(
self.selection,
background_color,
None,
content_x,
content_y,
);
} else if !self.search_results.selections.is_empty() {
for res in self.search_results.selections.iter() {
if res.contains_row(character_chunk.y.saturating_sub(content_y)) {
let (select_background_palette, select_foreground_palette) =
if Some(res) == self.search_results.active.as_ref() {
(style.colors.orange, style.colors.black)
} else {
(style.colors.green, style.colors.black)
};
let background_color = match select_background_palette {
PaletteColor::Rgb(rgb) => AnsiCode::RgbCode(rgb),
PaletteColor::EightBit(col) => AnsiCode::ColorIndex(col),
};
let foreground_color = match select_foreground_palette {
PaletteColor::Rgb(rgb) => AnsiCode::RgbCode(rgb),
PaletteColor::EightBit(col) => AnsiCode::ColorIndex(col),
};
character_chunk.add_selection_and_colors(
*res,
background_color,
Some(foreground_color),
content_x,
content_y,
);
}
}
}
}
if self.ring_bell {
let ring_bell = '\u{7}';
raw_vte_output.push(ring_bell);
self.ring_bell = false;
}
return Ok(Some((
character_chunks,
Some(raw_vte_output),
sixel_image_chunks,
)));
}
pub fn cursor_coordinates(&self) -> Option<(usize, usize)> {
if self.cursor_is_hidden {
None
@ -1959,6 +2026,14 @@ impl Grid {
None
}
}
pub fn delete_viewport_and_scroll(&mut self) {
self.lines_above.clear();
self.viewport.clear();
self.lines_below.clear();
}
pub fn reset_cursor_position(&mut self) {
self.cursor = Cursor::new(0, 0);
}
}
impl Perform for Grid {
@ -2563,7 +2638,7 @@ impl Perform for Grid {
};
if first_intermediate_is_questionmark {
let query_type = params_iter.next();
let is_query = params_iter.next() == Some(&[1]);
let is_query = matches!(params_iter.next(), Some(&[1]));
if is_query {
// XTSMGRAPHICS
match query_type {

View file

@ -1,28 +1,50 @@
use std::fmt::Write;
use std::sync::mpsc::channel;
use std::collections::HashMap;
use std::time::Instant;
use crate::output::{CharacterChunk, SixelImageChunk};
use crate::panes::PaneId;
use crate::panes::{grid::Grid, sixel::SixelImageStore, LinkHandler, PaneId};
use crate::pty::VteBytes;
use crate::tab::Pane;
use crate::ui::pane_boundaries_frame::{FrameParams, PaneFrame};
use crate::wasm_vm::PluginInstruction;
use crate::ClientId;
use zellij_utils::pane_size::Offset;
use std::cell::RefCell;
use std::rc::Rc;
use zellij_utils::pane_size::{Offset, SizeInPixels};
use zellij_utils::position::Position;
use zellij_utils::shared::ansi_len;
use zellij_utils::{
channels::SenderWithContext,
data::{Event, InputMode, Mouse, PaletteColor},
data::{Event, InputMode, Mouse, Palette, PaletteColor, Style},
errors::prelude::*,
pane_size::{Dimension, PaneGeom},
shared::make_terminal_title,
vte,
};
macro_rules! get_or_create_grid {
($self:ident, $client_id:ident) => {{
let rows = $self.get_content_rows();
let cols = $self.get_content_columns();
$self.grids.entry($client_id).or_insert_with(|| {
let mut grid = Grid::new(
rows,
cols,
$self.terminal_emulator_colors.clone(),
$self.terminal_emulator_color_codes.clone(),
$self.link_handler.clone(),
$self.character_cell_size.clone(),
$self.sixel_image_store.clone(),
);
grid.hide_cursor();
grid
})
}};
}
pub(crate) struct PluginPane {
pub pid: u32,
pub should_render: bool,
pub should_render: HashMap<ClientId, bool>,
pub selectable: bool,
pub geom: PaneGeom,
pub geom_override: Option<PaneGeom>,
@ -31,8 +53,16 @@ pub(crate) struct PluginPane {
pub active_at: Instant,
pub pane_title: String,
pub pane_name: String,
pub style: Style,
sixel_image_store: Rc<RefCell<SixelImageStore>>,
terminal_emulator_colors: Rc<RefCell<Palette>>,
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
link_handler: Rc<RefCell<LinkHandler>>,
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
vte_parsers: HashMap<ClientId, vte::Parser>,
grids: HashMap<ClientId, Grid>,
prev_pane_name: String,
frame: bool,
frame: HashMap<ClientId, PaneFrame>,
borderless: bool,
}
@ -43,21 +73,35 @@ impl PluginPane {
send_plugin_instructions: SenderWithContext<PluginInstruction>,
title: String,
pane_name: String,
sixel_image_store: Rc<RefCell<SixelImageStore>>,
terminal_emulator_colors: Rc<RefCell<Palette>>,
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
link_handler: Rc<RefCell<LinkHandler>>,
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
style: Style,
) -> Self {
Self {
pid,
should_render: true,
should_render: HashMap::new(),
selectable: true,
geom: position_and_size,
geom_override: None,
send_plugin_instructions,
active_at: Instant::now(),
frame: false,
frame: HashMap::new(),
content_offset: Offset::default(),
pane_title: title,
borderless: false,
pane_name: pane_name.clone(),
prev_pane_name: pane_name,
terminal_emulator_colors,
terminal_emulator_color_codes,
link_handler,
character_cell_size,
sixel_image_store,
vte_parsers: HashMap::new(),
grids: HashMap::new(),
style,
}
}
}
@ -98,18 +142,37 @@ impl Pane for PluginPane {
}
fn reset_size_and_position_override(&mut self) {
self.geom_override = None;
self.should_render = true;
self.resize_grids();
self.set_should_render(true);
}
fn set_geom(&mut self, position_and_size: PaneGeom) {
self.geom = position_and_size;
self.should_render = true;
self.resize_grids();
self.set_should_render(true);
}
fn set_geom_override(&mut self, pane_geom: PaneGeom) {
self.geom_override = Some(pane_geom);
self.should_render = true;
self.resize_grids();
self.set_should_render(true);
}
fn handle_pty_bytes(&mut self, _event: VteBytes) {
// noop
fn handle_plugin_bytes(&mut self, client_id: ClientId, bytes: VteBytes) {
self.set_client_should_render(client_id, true);
let grid = get_or_create_grid!(self, client_id);
// this is part of the plugin contract, whenever we update the plugin and call its render function, we delete the existing viewport
// and scroll, reset the cursor position and make sure all the viewport is rendered
grid.delete_viewport_and_scroll();
grid.reset_cursor_position();
grid.render_full_viewport();
let vte_parser = self
.vte_parsers
.entry(client_id)
.or_insert_with(|| vte::Parser::new());
for &byte in &bytes {
vte_parser.advance(grid, byte);
}
self.should_render.insert(client_id, true);
}
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
None
@ -124,10 +187,21 @@ impl Pane for PluginPane {
self.geom_override
}
fn should_render(&self) -> bool {
self.should_render
// set should_render for all clients
self.should_render.values().any(|v| *v)
}
fn set_should_render(&mut self, should_render: bool) {
self.should_render = should_render;
self.should_render
.values_mut()
.for_each(|v| *v = should_render);
}
fn render_full_viewport(&mut self) {
// this marks the pane for a full re-render, rather than just rendering the
// diff as it usually does with the OutputBuffer
self.frame.clear();
for grid in self.grids.values_mut() {
grid.render_full_viewport();
}
}
fn selectable(&self) -> bool {
self.selectable
@ -139,127 +213,86 @@ impl Pane for PluginPane {
&mut self,
client_id: Option<ClientId>,
) -> Result<Option<(Vec<CharacterChunk>, Option<String>, Vec<SixelImageChunk>)>> {
// this is a bit of a hack but works in a pinch
let client_id = match client_id {
Some(id) => id,
None => return Ok(None),
};
// if self.should_render {
if true {
let err_context = || format!("failed to render plugin panes for client {client_id}");
// while checking should_render rather than rendering each pane every time
// is more performant, it causes some problems when the pane to the left should be
// rendered and has wide characters (eg. Chinese characters or emoji)
// as a (hopefully) temporary hack, we render all panes until we find a better solution
let mut vte_output = String::new();
let (buf_tx, buf_rx) = channel();
self.send_plugin_instructions
.send(PluginInstruction::Render(
buf_tx,
self.pid,
client_id,
self.get_content_rows(),
self.get_content_columns(),
))
.to_anyhow()
.with_context(err_context)?;
self.should_render = false;
// This is where we receive the text to render from the plugins.
let contents = buf_rx
.recv()
.with_context(err_context)
.to_log()
.unwrap_or("No output from plugin received. See logs".to_string());
for (index, line) in contents.lines().enumerate() {
let actual_len = ansi_len(line);
let line_to_print = if actual_len > self.get_content_columns() {
let mut line = String::from(line);
line.truncate(self.get_content_columns());
line
} else {
[
line,
&str::repeat(" ", self.get_content_columns() - ansi_len(line)),
]
.concat()
};
write!(
&mut vte_output,
"\u{1b}[{};{}H\u{1b}[m{}",
self.get_content_y() + 1 + index,
self.get_content_x() + 1,
line_to_print,
)
.with_context(err_context)?; // goto row/col and reset styles
let line_len = line_to_print.len();
if line_len < self.get_content_columns() {
// pad line
for _ in line_len..self.get_content_columns() {
vte_output.push(' ');
if client_id.is_none() {
return Ok(None);
}
if let Some(client_id) = client_id {
if self.should_render.get(&client_id).copied().unwrap_or(false) {
let content_x = self.get_content_x();
let content_y = self.get_content_y();
if let Some(grid) = self.grids.get_mut(&client_id) {
match grid.render(content_x, content_y, &self.style) {
Ok(rendered_assets) => {
self.should_render.insert(client_id, false);
return Ok(rendered_assets);
},
e => return e,
}
}
}
let total_line_count = contents.lines().count();
if total_line_count < self.get_content_rows() {
// pad lines
for line_index in total_line_count..self.get_content_rows() {
let x = self.get_content_x();
let y = self.get_content_y();
write!(
&mut vte_output,
"\u{1b}[{};{}H\u{1b}[m",
y + line_index + 1,
x + 1
)
.with_context(err_context)?; // goto row/col and reset styles
for _col_index in 0..self.get_content_columns() {
vte_output.push(' ');
}
}
}
Ok(Some((vec![], Some(vte_output), vec![]))) // TODO: PluginPanes should have their own grid so that we can return the non-serialized TerminalCharacters and have them participate in the render buffer
} else {
Ok(None)
}
}
fn render_frame(
&mut self,
_client_id: ClientId,
client_id: ClientId,
frame_params: FrameParams,
input_mode: InputMode,
) -> Result<Option<(Vec<CharacterChunk>, Option<String>)>> {
// FIXME: This is a hack that assumes all fixed-size panes are borderless. This
// will eventually need fixing!
let res = if self.frame && !(self.geom.rows.is_fixed() || self.geom.cols.is_fixed()) {
if self.borderless {
return Ok(None);
}
if let Some(grid) = self.grids.get(&client_id) {
let err_context = || format!("failed to render frame for client {client_id}");
let pane_title = if self.pane_name.is_empty()
&& input_mode == InputMode::RenamePane
&& frame_params.is_main_client
{
String::from("Enter name...")
} else if self.pane_name.is_empty() {
self.pane_title.clone()
grid.title
.clone()
.unwrap_or_else(|| self.pane_title.clone())
} else {
self.pane_name.clone()
};
let frame = PaneFrame::new(
self.current_geom().into(),
(0, 0), // scroll position
grid.scrollback_position_and_length(),
pane_title,
frame_params,
);
Some(
frame
.render()
.with_context(|| format!("failed to render frame for client {_client_id}"))?,
)
let res = match self.frame.get(&client_id) {
// TODO: use and_then or something?
Some(last_frame) => {
if &frame != last_frame {
if !self.borderless {
let frame_output = frame.render().with_context(err_context)?;
self.frame.insert(client_id, frame);
Some(frame_output)
} else {
None
}
} else {
None
}
},
None => {
if !self.borderless {
let frame_output = frame.render().with_context(err_context)?;
self.frame.insert(client_id, frame);
Some(frame_output)
} else {
None
}
},
};
Ok(res)
} else {
Ok(None)
}
}
fn render_fake_cursor(
&mut self,
@ -298,42 +331,50 @@ impl Pane for PluginPane {
fn reduce_height(&mut self, percent: f64) {
if let Some(p) = self.geom.rows.as_percent() {
self.geom.rows = Dimension::percent(p - percent);
self.should_render = true;
self.resize_grids();
self.set_should_render(true);
}
}
fn increase_height(&mut self, percent: f64) {
if let Some(p) = self.geom.rows.as_percent() {
self.geom.rows = Dimension::percent(p + percent);
self.should_render = true;
self.resize_grids();
self.set_should_render(true);
}
}
fn reduce_width(&mut self, percent: f64) {
if let Some(p) = self.geom.cols.as_percent() {
self.geom.cols = Dimension::percent(p - percent);
self.should_render = true;
self.resize_grids();
self.set_should_render(true);
}
}
fn increase_width(&mut self, percent: f64) {
if let Some(p) = self.geom.cols.as_percent() {
self.geom.cols = Dimension::percent(p + percent);
self.should_render = true;
self.resize_grids();
self.set_should_render(true);
}
}
fn push_down(&mut self, count: usize) {
self.geom.y += count;
self.should_render = true;
self.resize_grids();
self.set_should_render(true);
}
fn push_right(&mut self, count: usize) {
self.geom.x += count;
self.should_render = true;
self.resize_grids();
self.set_should_render(true);
}
fn pull_left(&mut self, count: usize) {
self.geom.x -= count;
self.should_render = true;
self.resize_grids();
self.set_should_render(true);
}
fn pull_up(&mut self, count: usize) {
self.geom.y -= count;
self.should_render = true;
self.resize_grids();
self.set_should_render(true);
}
fn scroll_up(&mut self, count: usize, client_id: ClientId) {
self.send_plugin_instructions
@ -394,11 +435,12 @@ impl Pane for PluginPane {
fn set_active_at(&mut self, time: Instant) {
self.active_at = time;
}
fn set_frame(&mut self, frame: bool) {
self.frame = frame;
fn set_frame(&mut self, _frame: bool) {
self.frame.clear();
}
fn set_content_offset(&mut self, offset: Offset) {
self.content_offset = offset;
self.resize_grids();
}
fn store_pane_name(&mut self) {
@ -428,3 +470,17 @@ impl Pane for PluginPane {
.unwrap();
}
}
impl PluginPane {
fn resize_grids(&mut self) {
let content_rows = self.get_content_rows();
let content_columns = self.get_content_columns();
for grid in self.grids.values_mut() {
grid.change_size(content_rows, content_columns);
}
self.set_should_render(true);
}
fn set_client_should_render(&mut self, client_id: ClientId, should_render: bool) {
self.should_render.insert(client_id, should_render);
}
}

View file

@ -1,10 +1,10 @@
use crate::output::{CharacterChunk, SixelImageChunk};
use crate::panes::sixel::SixelImageStore;
use crate::panes::LinkHandler;
use crate::panes::{
grid::Grid,
terminal_character::{render_first_run_banner, TerminalCharacter, EMPTY_TERMINAL_CHARACTER},
};
use crate::panes::{AnsiCode, LinkHandler};
use crate::pty::VteBytes;
use crate::tab::{AdjustedInput, Pane};
use crate::ClientId;
@ -280,69 +280,15 @@ impl Pane for TerminalPane {
_client_id: Option<ClientId>,
) -> Result<Option<(Vec<CharacterChunk>, Option<String>, Vec<SixelImageChunk>)>> {
if self.should_render() {
let mut raw_vte_output = String::new();
let content_x = self.get_content_x();
let content_y = self.get_content_y();
let (mut character_chunks, sixel_image_chunks) =
self.grid.read_changes(content_x, content_y);
for character_chunk in character_chunks.iter_mut() {
character_chunk.add_changed_colors(self.grid.changed_colors);
if self
.grid
.selection
.contains_row(character_chunk.y.saturating_sub(content_y))
{
let background_color = match self.style.colors.bg {
PaletteColor::Rgb(rgb) => AnsiCode::RgbCode(rgb),
PaletteColor::EightBit(col) => AnsiCode::ColorIndex(col),
};
character_chunk.add_selection_and_colors(
self.grid.selection,
background_color,
None,
content_x,
content_y,
);
} else if !self.grid.search_results.selections.is_empty() {
for res in self.grid.search_results.selections.iter() {
if res.contains_row(character_chunk.y.saturating_sub(content_y)) {
let (select_background_palette, select_foreground_palette) =
if Some(res) == self.grid.search_results.active.as_ref() {
(self.style.colors.orange, self.style.colors.black)
} else {
(self.style.colors.green, self.style.colors.black)
};
let background_color = match select_background_palette {
PaletteColor::Rgb(rgb) => AnsiCode::RgbCode(rgb),
PaletteColor::EightBit(col) => AnsiCode::ColorIndex(col),
};
let foreground_color = match select_foreground_palette {
PaletteColor::Rgb(rgb) => AnsiCode::RgbCode(rgb),
PaletteColor::EightBit(col) => AnsiCode::ColorIndex(col),
};
character_chunk.add_selection_and_colors(
*res,
background_color,
Some(foreground_color),
content_x,
content_y,
);
}
}
}
}
if self.grid.ring_bell {
let ring_bell = '\u{7}';
raw_vte_output.push(ring_bell);
self.grid.ring_bell = false;
}
match self.grid.render(content_x, content_y, &self.style) {
Ok(rendered_assets) => {
self.set_should_render(false);
Ok(Some((
character_chunks,
Some(raw_vte_output),
sixel_image_chunks,
)))
return Ok(rendered_assets);
},
e => return e,
}
} else {
Ok(None)
}

View file

@ -1,15 +1,20 @@
mod pane_resizer;
mod tiled_pane_grid;
use crate::resize_pty;
use tiled_pane_grid::{split, TiledPaneGrid};
use crate::{
os_input_output::ServerOsApi,
output::Output,
panes::{ActivePanes, PaneId},
tab::{Pane, MIN_TERMINAL_HEIGHT, MIN_TERMINAL_WIDTH},
ui::{boundaries::Boundaries, pane_contents_and_ui::PaneContentsAndUi},
thread_bus::ThreadSenders,
ui::boundaries::Boundaries,
ui::pane_contents_and_ui::PaneContentsAndUi,
wasm_vm::PluginInstruction,
ClientId,
};
use tiled_pane_grid::{split, TiledPaneGrid};
use zellij_utils::{
data::{ModeInfo, Style},
errors::prelude::*,
@ -24,22 +29,6 @@ use std::{
time::Instant,
};
macro_rules! resize_pty {
($pane:expr, $os_input:expr) => {
if let PaneId::Terminal(ref pid) = $pane.pid() {
// FIXME: This `set_terminal_size_using_terminal_id` call would be best in
// `TerminalPane::reflow_lines`
$os_input.set_terminal_size_using_terminal_id(
*pid,
$pane.get_content_columns() as u16,
$pane.get_content_rows() as u16,
)
} else {
Ok(())
}
};
}
fn pane_content_offset(position_and_size: &PaneGeom, viewport: &Viewport) -> (usize, usize) {
// (columns_offset, rows_offset)
// if the pane is not on the bottom or right edge on the screen, we need to reserve one space
@ -75,6 +64,7 @@ pub struct TiledPanes {
panes_to_hide: HashSet<PaneId>,
fullscreen_is_active: bool,
os_api: Box<dyn ServerOsApi>,
senders: ThreadSenders,
}
impl TiledPanes {
@ -91,6 +81,7 @@ impl TiledPanes {
default_mode_info: ModeInfo,
style: Style,
os_api: Box<dyn ServerOsApi>,
senders: ThreadSenders,
) -> Self {
TiledPanes {
panes: BTreeMap::new(),
@ -108,6 +99,7 @@ impl TiledPanes {
panes_to_hide: HashSet::new(),
fullscreen_is_active: false,
os_api,
senders,
}
}
pub fn add_pane_with_existing_geom(&mut self, pane_id: PaneId, mut pane: Box<dyn Pane>) {
@ -270,7 +262,7 @@ impl TiledPanes {
pane.set_content_offset(Offset::shift(pane_rows_offset, pane_columns_offset));
}
resize_pty!(pane, self.os_api).unwrap();
resize_pty!(pane, self.os_api, self.senders).unwrap();
}
}
pub fn can_split_pane_horizontally(&mut self, client_id: ClientId) -> bool {
@ -523,7 +515,7 @@ impl TiledPanes {
);
pane_grid.resize_pane_left(&active_pane_id);
for pane in self.panes.values_mut() {
resize_pty!(pane, self.os_api).unwrap();
resize_pty!(pane, self.os_api, self.senders).unwrap();
}
}
}
@ -537,7 +529,7 @@ impl TiledPanes {
);
pane_grid.resize_pane_right(&active_pane_id);
for pane in self.panes.values_mut() {
resize_pty!(pane, self.os_api).unwrap();
resize_pty!(pane, self.os_api, self.senders).unwrap();
}
}
}
@ -551,7 +543,7 @@ impl TiledPanes {
);
pane_grid.resize_pane_up(&active_pane_id);
for pane in self.panes.values_mut() {
resize_pty!(pane, self.os_api).unwrap();
resize_pty!(pane, self.os_api, self.senders).unwrap();
}
}
}
@ -565,7 +557,7 @@ impl TiledPanes {
);
pane_grid.resize_pane_down(&active_pane_id);
for pane in self.panes.values_mut() {
resize_pty!(pane, self.os_api).unwrap();
resize_pty!(pane, self.os_api, self.senders).unwrap();
}
}
}
@ -579,7 +571,7 @@ impl TiledPanes {
);
pane_grid.resize_increase(&active_pane_id);
for pane in self.panes.values_mut() {
resize_pty!(pane, self.os_api).unwrap();
resize_pty!(pane, self.os_api, self.senders).unwrap();
}
}
}
@ -593,7 +585,7 @@ impl TiledPanes {
);
pane_grid.resize_decrease(&active_pane_id);
for pane in self.panes.values_mut() {
resize_pty!(pane, self.os_api).unwrap();
resize_pty!(pane, self.os_api, self.senders).unwrap();
}
}
}
@ -826,7 +818,7 @@ impl TiledPanes {
if let Some(geom) = prev_geom_override {
new_position.set_geom_override(geom);
}
resize_pty!(new_position, self.os_api).unwrap();
resize_pty!(new_position, self.os_api, self.senders).unwrap();
new_position.set_should_render(true);
let current_position = self.panes.get_mut(&active_pane_id).unwrap();
@ -834,7 +826,7 @@ impl TiledPanes {
if let Some(geom) = next_geom_override {
current_position.set_geom_override(geom);
}
resize_pty!(current_position, self.os_api).unwrap();
resize_pty!(current_position, self.os_api, self.senders).unwrap();
current_position.set_should_render(true);
self.set_pane_frames(self.draw_pane_frames);
}
@ -860,7 +852,7 @@ impl TiledPanes {
if let Some(geom) = prev_geom_override {
new_position.set_geom_override(geom);
}
resize_pty!(new_position, self.os_api).unwrap();
resize_pty!(new_position, self.os_api, self.senders).unwrap();
new_position.set_should_render(true);
let current_position = self.panes.get_mut(active_pane_id).unwrap();
@ -868,7 +860,7 @@ impl TiledPanes {
if let Some(geom) = next_geom_override {
current_position.set_geom_override(geom);
}
resize_pty!(current_position, self.os_api).unwrap();
resize_pty!(current_position, self.os_api, self.senders).unwrap();
current_position.set_should_render(true);
self.set_pane_frames(self.draw_pane_frames);
}
@ -896,7 +888,7 @@ impl TiledPanes {
if let Some(geom) = prev_geom_override {
new_position.set_geom_override(geom);
}
resize_pty!(new_position, self.os_api).unwrap();
resize_pty!(new_position, self.os_api, self.senders).unwrap();
new_position.set_should_render(true);
let current_position = self.panes.get_mut(active_pane_id).unwrap();
@ -904,7 +896,7 @@ impl TiledPanes {
if let Some(geom) = next_geom_override {
current_position.set_geom_override(geom);
}
resize_pty!(current_position, self.os_api).unwrap();
resize_pty!(current_position, self.os_api, self.senders).unwrap();
current_position.set_should_render(true);
self.set_pane_frames(self.draw_pane_frames);
}
@ -932,7 +924,7 @@ impl TiledPanes {
if let Some(geom) = prev_geom_override {
new_position.set_geom_override(geom);
}
resize_pty!(new_position, self.os_api).unwrap();
resize_pty!(new_position, self.os_api, self.senders).unwrap();
new_position.set_should_render(true);
let current_position = self.panes.get_mut(active_pane_id).unwrap();
@ -940,7 +932,7 @@ impl TiledPanes {
if let Some(geom) = next_geom_override {
current_position.set_geom_override(geom);
}
resize_pty!(current_position, self.os_api).unwrap();
resize_pty!(current_position, self.os_api, self.senders).unwrap();
current_position.set_should_render(true);
self.set_pane_frames(self.draw_pane_frames);
}
@ -968,7 +960,7 @@ impl TiledPanes {
if let Some(geom) = prev_geom_override {
new_position.set_geom_override(geom);
}
resize_pty!(new_position, self.os_api).unwrap();
resize_pty!(new_position, self.os_api, self.senders).unwrap();
new_position.set_should_render(true);
let current_position = self.panes.get_mut(active_pane_id).unwrap();
@ -976,7 +968,7 @@ impl TiledPanes {
if let Some(geom) = next_geom_override {
current_position.set_geom_override(geom);
}
resize_pty!(current_position, self.os_api).unwrap();
resize_pty!(current_position, self.os_api, self.senders).unwrap();
current_position.set_should_render(true);
self.set_pane_frames(self.draw_pane_frames);
}

View file

@ -30,7 +30,7 @@ pub enum ClientOrTabIndex {
/// Instructions related to PTYs (pseudoterminals).
#[derive(Clone, Debug)]
pub(crate) enum PtyInstruction {
pub enum PtyInstruction {
SpawnTerminal(
Option<TerminalAction>,
Option<bool>,

View file

@ -3,7 +3,7 @@ use zellij_utils::errors::{prelude::*, ContextType, PtyWriteContext};
use crate::thread_bus::Bus;
#[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) enum PtyWriteInstruction {
pub enum PtyWriteInstruction {
Write(Vec<u8>, u32),
Exit,
}

View file

@ -118,6 +118,7 @@ type HoldForCommand = Option<RunCommand>;
#[derive(Debug, Clone)]
pub enum ScreenInstruction {
PtyBytes(u32, VteBytes),
PluginBytes(u32, ClientId, VteBytes), // u32 is plugin_id
Render,
NewPane(
PaneId,
@ -217,6 +218,7 @@ impl From<&ScreenInstruction> for ScreenContext {
fn from(screen_instruction: &ScreenInstruction) -> Self {
match *screen_instruction {
ScreenInstruction::PtyBytes(..) => ScreenContext::HandlePtyBytes,
ScreenInstruction::PluginBytes(..) => ScreenContext::PluginBytes,
ScreenInstruction::Render => ScreenContext::Render,
ScreenInstruction::NewPane(..) => ScreenContext::NewPane,
ScreenInstruction::OpenInPlaceEditor(..) => ScreenContext::OpenInPlaceEditor,
@ -531,7 +533,6 @@ impl Screen {
};
if let Some(new_tab) = self.tabs.values().find(|t| t.position == new_tab_pos) {
//if let Some(current_tab) = self.get_active_tab(client_id) {
match self.get_active_tab(client_id) {
Ok(current_tab) => {
// If new active tab is same as the current one, do nothing.
@ -1276,6 +1277,17 @@ pub(crate) fn screen_thread_main(
}
}
},
ScreenInstruction::PluginBytes(pid, client_id, vte_bytes) => {
let all_tabs = screen.get_tabs_mut();
for tab in all_tabs.values_mut() {
if tab.has_plugin(pid) {
tab.handle_plugin_bytes(pid, client_id, vte_bytes)
.context("failed to process plugin bytes")?;
break;
}
}
screen.render()?;
},
ScreenInstruction::Render => {
screen.render()?;
},

View file

@ -47,20 +47,27 @@ use zellij_utils::{
pane_size::{Offset, PaneGeom, Size, SizeInPixels, Viewport},
};
#[macro_export]
macro_rules! resize_pty {
($pane:expr, $os_input:expr) => {
if let PaneId::Terminal(ref pid) = $pane.pid() {
// FIXME: This `set_terminal_size_using_terminal_id` call would be best in
// `TerminalPane::reflow_lines`
$os_input.set_terminal_size_using_terminal_id(
($pane:expr, $os_input:expr, $senders:expr) => {{
match $pane.pid() {
PaneId::Terminal(ref pid) => $os_input.set_terminal_size_using_terminal_id(
*pid,
$pane.get_content_columns() as u16,
$pane.get_content_rows() as u16,
)
} else {
Ok(())
),
PaneId::Plugin(ref pid) => {
let err_context = || format!("failed to resize plugin {pid}");
$senders
.send_to_plugin(PluginInstruction::Resize(
*pid,
$pane.get_content_columns(),
$pane.get_content_rows(),
))
.with_context(err_context)
},
}
};
}};
}
type HoldForCommand = Option<RunCommand>;
@ -130,7 +137,8 @@ pub trait Pane {
fn reset_size_and_position_override(&mut self);
fn set_geom(&mut self, position_and_size: PaneGeom);
fn set_geom_override(&mut self, pane_geom: PaneGeom);
fn handle_pty_bytes(&mut self, bytes: VteBytes);
fn handle_pty_bytes(&mut self, _bytes: VteBytes) {}
fn handle_plugin_bytes(&mut self, _client_id: ClientId, _bytes: VteBytes) {}
fn cursor_coordinates(&self) -> Option<(usize, usize)>;
fn adjust_input_to_terminal(&mut self, _input_bytes: Vec<u8>) -> Option<AdjustedInput> {
None
@ -425,6 +433,7 @@ impl Tab {
default_mode_info.clone(),
style,
os_api.clone(),
senders.clone(),
);
let floating_panes = FloatingPanes::new(
display_area.clone(),
@ -436,6 +445,7 @@ impl Tab {
default_mode_info.clone(),
style,
os_api.clone(),
senders.clone(),
);
let clipboard_provider = match copy_options.command {
@ -525,7 +535,11 @@ impl Tab {
let pane_title = run.location.to_string();
self.senders
.send_to_plugin(PluginInstruction::Load(
pid_tx, run, tab_index, client_id,
pid_tx,
run,
tab_index,
client_id,
position_and_size.into(),
))
.with_context(err_context)?;
let pid = pid_rx.recv().with_context(err_context)?;
@ -539,6 +553,12 @@ impl Tab {
.clone(),
pane_title,
layout.name.clone().unwrap_or_default(),
self.sixel_image_store.clone(),
self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(),
self.link_handler.clone(),
self.character_cell_size.clone(),
self.style,
);
new_plugin.set_borderless(layout.borderless);
self.tiled_panes
@ -779,7 +799,8 @@ impl Tab {
embedded_pane_to_float.set_content_offset(Offset::default());
}
embedded_pane_to_float.set_geom(new_pane_geom);
resize_pty!(embedded_pane_to_float, self.os_api).with_context(err_context)?;
resize_pty!(embedded_pane_to_float, self.os_api, self.senders)
.with_context(err_context)?;
embedded_pane_to_float.set_active_at(Instant::now());
self.floating_panes
.add_pane(focused_pane_id, embedded_pane_to_float);
@ -860,7 +881,7 @@ impl Tab {
initial_pane_title,
);
new_pane.set_content_offset(Offset::frame(1)); // floating panes always have a frame
resize_pty!(new_pane, self.os_api).with_context(err_context)?;
resize_pty!(new_pane, self.os_api, self.senders).with_context(err_context)?;
self.floating_panes.add_pane(pid, Box::new(new_pane));
self.floating_panes.focus_pane_for_all_clients(pid);
}
@ -934,7 +955,7 @@ impl Tab {
self.get_active_pane(client_id)
.with_context(|| format!("no active pane found for client {client_id}"))
.and_then(|current_active_pane| {
resize_pty!(current_active_pane, self.os_api)
resize_pty!(current_active_pane, self.os_api, self.senders)
})
.with_context(err_context)?;
},
@ -1084,6 +1105,16 @@ impl Tab {
.values()
.any(|s_p| s_p.pid() == PaneId::Terminal(pid))
}
pub fn has_plugin(&self, plugin_id: u32) -> bool {
self.tiled_panes.panes_contain(&PaneId::Plugin(plugin_id))
|| self
.floating_panes
.panes_contain(&PaneId::Plugin(plugin_id))
|| self
.suppressed_panes
.values()
.any(|s_p| s_p.pid() == PaneId::Plugin(plugin_id))
}
pub fn handle_pty_bytes(&mut self, pid: u32, bytes: VteBytes) -> Result<()> {
let err_context = || format!("failed to handle pty bytes from fd {pid}");
if let Some(terminal_output) = self
@ -1112,6 +1143,26 @@ impl Tab {
}
self.process_pty_bytes(pid, bytes).with_context(err_context)
}
pub fn handle_plugin_bytes(
&mut self,
pid: u32,
client_id: ClientId,
bytes: VteBytes,
) -> Result<()> {
if let Some(plugin_pane) = self
.tiled_panes
.get_pane_mut(PaneId::Plugin(pid))
.or_else(|| self.floating_panes.get_pane_mut(PaneId::Plugin(pid)))
.or_else(|| {
self.suppressed_panes
.values_mut()
.find(|s_p| s_p.pid() == PaneId::Plugin(pid))
})
{
plugin_pane.handle_plugin_bytes(client_id, bytes);
}
Ok(())
}
pub fn process_pending_vte_events(&mut self, pid: u32) -> Result<()> {
if let Some(pending_vte_events) = self.pending_vte_events.get_mut(&pid) {
let vte_events: Vec<VteBytes> = pending_vte_events.drain(..).collect();
@ -1136,7 +1187,8 @@ impl Tab {
})
{
if self.pids_waiting_resize.remove(&pid) {
resize_pty!(terminal_output, self.os_api).with_context(err_context)?;
resize_pty!(terminal_output, self.os_api, self.senders)
.with_context(err_context)?;
}
terminal_output.handle_pty_bytes(bytes);
let messages_to_pty = terminal_output.drain_messages_to_pty();
@ -1833,7 +1885,7 @@ impl Tab {
// the pane there we replaced. Now, we need to update its pty about its new size.
// We couldn't do that before, and we can't use the original moved item now - so we
// need to refetch it
resize_pty!(suppressed_pane, self.os_api).unwrap();
resize_pty!(suppressed_pane, self.os_api, self.senders).unwrap();
}
replaced_pane
})

View file

@ -9,7 +9,7 @@ use zellij_utils::{channels, channels::SenderWithContext, errors::ErrorContext};
/// A container for senders to the different threads in zellij on the server side
#[derive(Default, Clone)]
pub(crate) struct ThreadSenders {
pub struct ThreadSenders {
pub to_screen: Option<SenderWithContext<ScreenInstruction>>,
pub to_pty: Option<SenderWithContext<PtyInstruction>>,
pub to_plugin: Option<SenderWithContext<PluginInstruction>>,

View file

@ -37,6 +37,7 @@ use zellij_utils::{
layout::RunPlugin,
plugins::{PluginConfig, PluginType, PluginsConfig},
},
pane_size::Size,
serde,
};
@ -95,11 +96,11 @@ folder from the output of the `zellij setup --check` command.
}
#[derive(Clone, Debug)]
pub(crate) enum PluginInstruction {
Load(Sender<u32>, RunPlugin, usize, ClientId), // tx_pid, plugin metadata, tab_index, client_ids
pub enum PluginInstruction {
Load(Sender<u32>, RunPlugin, usize, ClientId, Size), // tx_pid, plugin metadata, tab_index, client_ids
Update(Option<u32>, Option<ClientId>, Event), // Focused plugin / broadcast, client_id, event data
Render(Sender<String>, u32, ClientId, usize, usize), // String buffer, plugin id, client_id, rows, cols
Unload(u32), // plugin_id
Resize(u32, usize, usize), // plugin_id, columns, rows
AddClient(ClientId),
RemoveClient(ClientId),
Exit,
@ -110,8 +111,8 @@ impl From<&PluginInstruction> for PluginContext {
match *plugin_instruction {
PluginInstruction::Load(..) => PluginContext::Load,
PluginInstruction::Update(..) => PluginContext::Update,
PluginInstruction::Render(..) => PluginContext::Render,
PluginInstruction::Unload(..) => PluginContext::Unload,
PluginInstruction::Resize(..) => PluginContext::Resize,
PluginInstruction::Exit => PluginContext::Exit,
PluginInstruction::AddClient(_) => PluginContext::AddClient,
PluginInstruction::RemoveClient(_) => PluginContext::RemoveClient,
@ -143,7 +144,8 @@ pub(crate) fn wasm_thread_main(
let mut plugin_id = 0;
let mut headless_plugins = HashMap::new();
let mut plugin_map: HashMap<(u32, ClientId), (Instance, PluginEnv)> = HashMap::new(); // u32 => pid
let mut plugin_map: HashMap<(u32, ClientId), (Instance, PluginEnv, (usize, usize))> =
HashMap::new(); // u32 => pid, (usize, usize) => rows/columns TODO: clean this up into a struct or something
let mut connected_clients: Vec<ClientId> = vec![];
let plugin_dir = data_dir.join("plugins/");
let plugin_global_data_dir = plugin_dir.join("data");
@ -157,7 +159,7 @@ pub(crate) fn wasm_thread_main(
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, run, tab_index, client_id) => {
PluginInstruction::Load(pid_tx, run, tab_index, client_id, size) => {
let err_context = || format!("failed to load plugin for client {client_id}");
let plugin = plugins
@ -175,7 +177,10 @@ pub(crate) fn wasm_thread_main(
let main_user_env = plugin_env.clone();
load_plugin(&mut main_user_instance).with_context(err_context)?;
plugin_map.insert((plugin_id, client_id), (main_user_instance, main_user_env));
plugin_map.insert(
(plugin_id, client_id),
(main_user_instance, main_user_env, (size.rows, size.cols)),
);
// clone plugins for the rest of the client ids if they exist
for client_id in connected_clients.iter() {
@ -190,7 +195,10 @@ pub(crate) fn wasm_thread_main(
let mut instance = Instance::new(&module, &zellij.chain_back(wasi))
.with_context(err_context)?;
load_plugin(&mut instance).with_context(err_context)?;
plugin_map.insert((plugin_id, *client_id), (instance, new_plugin_env));
plugin_map.insert(
(plugin_id, *client_id),
(instance, new_plugin_env, (size.rows, size.cols)),
);
}
pid_tx.send(plugin_id).with_context(err_context)?;
plugin_id += 1;
@ -204,7 +212,9 @@ pub(crate) fn wasm_thread_main(
}
};
for (&(plugin_id, client_id), (instance, plugin_env)) in &plugin_map {
for (&(plugin_id, client_id), (instance, plugin_env, (rows, columns))) in
&plugin_map
{
let subs = plugin_env
.subscriptions
.lock()
@ -237,36 +247,23 @@ pub(crate) fn wasm_thread_main(
Err(e) => Err(e).with_context(err_context),
}
})?;
}
}
drop(bus.senders.send_to_screen(ScreenInstruction::Render));
},
PluginInstruction::Render(buf_tx, pid, cid, rows, cols) => {
let err_context = || {
format!(
"failed to render plugin with pid {pid} and cid {cid} at ({rows}, {cols})"
)
};
if rows == 0 || cols == 0 {
buf_tx.send(String::new()).with_context(err_context)?;
} else {
let (instance, plugin_env) = plugin_map
.get(&(pid, cid))
.context("failed to find plugin for rendering")
.with_context(err_context)?;
if *rows > 0 && *columns > 0 {
let render = instance
.exports
.get_function("render")
.with_context(err_context)?;
render
.call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
.with_context(err_context)?;
buf_tx
.send(wasi_read_string(&plugin_env.wasi_env))
.call(&[Value::I32(*rows as i32), Value::I32(*columns as i32)])
.with_context(err_context)?;
let rendered_bytes = wasi_read_string(&plugin_env.wasi_env);
drop(bus.senders.send_to_screen(ScreenInstruction::PluginBytes(
plugin_id,
client_id,
rendered_bytes.as_bytes().to_vec(),
)));
}
}
}
},
PluginInstruction::Unload(pid) => {
@ -279,6 +276,38 @@ pub(crate) fn wasm_thread_main(
}
}
},
PluginInstruction::Resize(pid, new_columns, new_rows) => {
let err_context = || format!("failed to resize plugin {pid}");
for (
(plugin_id, client_id),
(instance, plugin_env, (current_rows, current_columns)),
) in plugin_map.iter_mut()
{
if *plugin_id == pid {
*current_rows = new_rows;
*current_columns = new_columns;
// TODO: consolidate with above render function
let render = instance
.exports
.get_function("render")
.with_context(err_context)?;
render
.call(&[
Value::I32(*current_rows as i32),
Value::I32(*current_columns as i32),
])
.with_context(err_context)?;
let rendered_bytes = wasi_read_string(&plugin_env.wasi_env);
drop(bus.senders.send_to_screen(ScreenInstruction::PluginBytes(
*plugin_id,
*client_id,
rendered_bytes.as_bytes().to_vec(),
)));
}
}
},
PluginInstruction::AddClient(client_id) => {
let err_context = || format!("failed to add plugins for client {client_id}");
@ -286,7 +315,7 @@ pub(crate) fn wasm_thread_main(
let mut seen = HashSet::new();
let mut new_plugins = HashMap::new();
for (&(plugin_id, _), (instance, plugin_env)) in &plugin_map {
for (&(plugin_id, _), (instance, plugin_env, (rows, columns))) in &plugin_map {
if seen.contains(&plugin_id) {
continue;
}
@ -294,9 +323,14 @@ pub(crate) fn wasm_thread_main(
let mut new_plugin_env = plugin_env.clone();
new_plugin_env.client_id = client_id;
new_plugins.insert(plugin_id, (instance.module().clone(), new_plugin_env));
new_plugins.insert(
plugin_id,
(instance.module().clone(), new_plugin_env, (*rows, *columns)),
);
}
for (plugin_id, (module, mut new_plugin_env)) in new_plugins.drain() {
for (plugin_id, (module, mut new_plugin_env, (rows, columns))) in
new_plugins.drain()
{
let wasi = new_plugin_env
.wasi_env
.import_object(&module)
@ -305,7 +339,10 @@ pub(crate) fn wasm_thread_main(
let mut instance = Instance::new(&module, &zellij.chain_back(wasi))
.with_context(err_context)?;
load_plugin(&mut instance).with_context(err_context)?;
plugin_map.insert((plugin_id, client_id), (instance, new_plugin_env));
plugin_map.insert(
(plugin_id, client_id),
(instance, new_plugin_env, (rows, columns)),
);
}
// load headless plugins
@ -640,7 +677,8 @@ pub fn wasi_read_string(wasi_env: &WasiEnv) -> String {
let wasi_file = state.fs.stdout_mut().unwrap().as_mut().unwrap();
let mut buf = String::new();
wasi_file.read_to_string(&mut buf).unwrap();
buf
// https://stackoverflow.com/questions/66450942/in-rust-is-there-a-way-to-make-literal-newlines-in-r-using-windows-c
buf.replace("\n", "\n\r")
}
pub fn wasi_write_string(wasi_env: &WasiEnv, buf: &str) {

View file

@ -218,6 +218,7 @@ impl Display for ContextType {
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum ScreenContext {
HandlePtyBytes,
PluginBytes,
Render,
NewPane,
OpenInPlaceEditor,
@ -336,6 +337,7 @@ pub enum PluginContext {
Update,
Render,
Unload,
Resize,
Exit,
AddClient,
RemoveClient,

View file

@ -176,3 +176,12 @@ impl From<Size> for Viewport {
}
}
}
impl From<&PaneGeom> for Size {
fn from(pane_geom: &PaneGeom) -> Self {
Self {
rows: pane_geom.rows.as_usize(),
cols: pane_geom.cols.as_usize(),
}
}
}