use std::collections::HashMap;
use std::time::Instant;
use crate::output::{CharacterChunk, SixelImageChunk};
use crate::panes::{grid::Grid, sixel::SixelImageStore, LinkHandler, PaneId};
use crate::plugins::PluginInstruction;
use crate::pty::VteBytes;
use crate::tab::Pane;
use crate::ui::pane_boundaries_frame::{FrameParams, PaneFrame};
use crate::ClientId;
use std::cell::RefCell;
use std::rc::Rc;
use zellij_utils::pane_size::{Offset, SizeInPixels};
use zellij_utils::position::Position;
use zellij_utils::{
channels::SenderWithContext,
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: HashMap<ClientId, bool>,
pub selectable: bool,
pub geom: PaneGeom,
pub geom_override: Option<PaneGeom>,
pub content_offset: Offset,
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
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: HashMap<ClientId, PaneFrame>,
borderless: bool,
}
impl PluginPane {
pub fn new(
pid: u32,
position_and_size: PaneGeom,
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: HashMap::new(),
selectable: true,
geom: position_and_size,
geom_override: None,
send_plugin_instructions,
active_at: Instant::now(),
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,
}
}
}
impl Pane for PluginPane {
fn x(&self) -> usize {
self.geom_override.unwrap_or(self.geom).x
}
fn y(&self) -> usize {
self.geom_override.unwrap_or(self.geom).y
}
fn rows(&self) -> usize {
self.geom_override.unwrap_or(self.geom).rows.as_usize()
}
fn cols(&self) -> usize {
self.geom_override.unwrap_or(self.geom).cols.as_usize()
}
fn get_content_x(&self) -> usize {
self.x() + self.content_offset.left
}
fn get_content_y(&self) -> usize {
self.y() + self.content_offset.top
}
fn get_content_columns(&self) -> usize {
self.cols()
.saturating_sub(self.content_offset.left + self.content_offset.right)
}
fn get_content_rows(&self) -> usize {
self.rows()
.saturating_sub(self.content_offset.top + self.content_offset.bottom)
}
fn reset_size_and_position_override(&mut self) {
self.geom_override = None;
self.resize_grids();
self.set_should_render(true);
}
fn set_geom(&mut self, position_and_size: PaneGeom) {
self.geom = position_and_size;
self.resize_grids();
self.set_should_render(true);
}
fn set_geom_override(&mut self, pane_geom: PaneGeom) {
self.geom_override = Some(pane_geom);
self.resize_grids();
self.set_should_render(true);
}
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);
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
}
fn position_and_size(&self) -> PaneGeom {
self.geom
}
fn current_geom(&self) -> PaneGeom {
self.geom_override.unwrap_or(self.geom)
}
fn geom_override(&self) -> Option<PaneGeom> {
self.geom_override
}
fn should_render(&self) -> bool {
self.should_render.values().any(|v| *v)
}
fn set_should_render(&mut self, should_render: bool) {
self.should_render
.values_mut()
.for_each(|v| *v = should_render);
}
fn render_full_viewport(&mut self) {
self.frame.clear();
for grid in self.grids.values_mut() {
grid.render_full_viewport();
}
}
fn selectable(&self) -> bool {
self.selectable
}
fn set_selectable(&mut self, selectable: bool) {
self.selectable = selectable;
}
fn render(
&mut self,
client_id: Option<ClientId>,
) -> Result<Option<(Vec<CharacterChunk>, Option<String>, Vec<SixelImageChunk>)>> {
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,
}
}
}
}
Ok(None)
}
fn render_frame(
&mut self,
client_id: ClientId,
frame_params: FrameParams,
input_mode: InputMode,
) -> Result<Option<(Vec<CharacterChunk>, Option<String>)>> {
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() {
grid.title
.clone()
.unwrap_or_else(|| self.pane_title.clone())
} else {
self.pane_name.clone()
};
let frame = PaneFrame::new(
self.current_geom().into(),
grid.scrollback_position_and_length(),
pane_title,
frame_params,
);
let res = match self.frame.get(&client_id) {
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,
_cursor_color: PaletteColor,
_text_color: PaletteColor,
) -> Option<String> {
None
}
fn render_terminal_title(&mut self, input_mode: InputMode) -> String {
let pane_title = if self.pane_name.is_empty() && input_mode == InputMode::RenamePane {
"Enter name..."
} else if self.pane_name.is_empty() {
&self.pane_title
} else {
&self.pane_name
};
make_terminal_title(pane_title)
}
fn update_name(&mut self, name: &str) {
match name {
"\0" => {
self.pane_name = String::new();
},
"\u{007F}" | "\u{0008}" => {
self.pane_name.pop();
},
c => {
self.pane_name.push_str(c);
},
}
}
fn pid(&self) -> PaneId {
PaneId::Plugin(self.pid)
}
fn reduce_height(&mut self, percent: f64) {
if let Some(p) = self.geom.rows.as_percent() {
self.geom.rows = Dimension::percent(p - percent);
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.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.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.resize_grids();
self.set_should_render(true);
}
}
fn push_down(&mut self, count: usize) {
self.geom.y += count;
self.resize_grids();
self.set_should_render(true);
}
fn push_right(&mut self, count: usize) {
self.geom.x += count;
self.resize_grids();
self.set_should_render(true);
}
fn pull_left(&mut self, count: usize) {
self.geom.x -= count;
self.resize_grids();
self.set_should_render(true);
}
fn pull_up(&mut self, count: usize) {
self.geom.y -= count;
self.resize_grids();
self.set_should_render(true);
}
fn scroll_up(&mut self, count: usize, client_id: ClientId) {
self.send_plugin_instructions
.send(PluginInstruction::Update(vec![(
Some(self.pid),
Some(client_id),
Event::Mouse(Mouse::ScrollUp(count)),
)]))
.unwrap();
}
fn scroll_down(&mut self, count: usize, client_id: ClientId) {
self.send_plugin_instructions
.send(PluginInstruction::Update(vec![(
Some(self.pid),
Some(client_id),
Event::Mouse(Mouse::ScrollDown(count)),
)]))
.unwrap();
}
fn clear_scroll(&mut self) {
}
fn start_selection(&mut self, start: &Position, client_id: ClientId) {
self.send_plugin_instructions
.send(PluginInstruction::Update(vec![(
Some(self.pid),
Some(client_id),
Event::Mouse(Mouse::LeftClick(start.line.0, start.column.0)),
)]))
.unwrap();
}
fn update_selection(&mut self, position: &Position, client_id: ClientId) {
self.send_plugin_instructions
.send(PluginInstruction::Update(vec![(
Some(self.pid),
Some(client_id),
Event::Mouse(Mouse::Hold(position.line.0, position.column.0)),
)]))
.unwrap();
}
fn end_selection(&mut self, end: &Position, client_id: ClientId) {
self.send_plugin_instructions
.send(PluginInstruction::Update(vec![(
Some(self.pid),
Some(client_id),
Event::Mouse(Mouse::Release(end.line(), end.column())),
)]))
.unwrap();
}
fn is_scrolled(&self) -> bool {
false
}
fn active_at(&self) -> Instant {
self.active_at
}
fn set_active_at(&mut self, time: Instant) {
self.active_at = time;
}
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) {
if self.pane_name != self.prev_pane_name {
self.prev_pane_name = self.pane_name.clone()
}
}
fn load_pane_name(&mut self) {
if self.pane_name != self.prev_pane_name {
self.pane_name = self.prev_pane_name.clone()
}
}
fn set_borderless(&mut self, borderless: bool) {
self.borderless = borderless;
}
fn borderless(&self) -> bool {
self.borderless
}
fn handle_right_click(&mut self, to: &Position, client_id: ClientId) {
self.send_plugin_instructions
.send(PluginInstruction::Update(vec![(
Some(self.pid),
Some(client_id),
Event::Mouse(Mouse::RightClick(to.line.0, to.column.0)),
)]))
.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);
}
}