Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugins): UI components #2898

Merged
merged 13 commits into from
Nov 2, 2023
Prev Previous commit
Next Next commit
move components to DCS and add tests
  • Loading branch information
imsnif committed Oct 24, 2023
commit 554ec044e3796a2f98290e235e11badb940b3360
1 change: 1 addition & 0 deletions src/tests/fixtures/table-ui-component
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Pztable;5;7;116,105,116,108,101,49;116,105,116,108,101,50;116,105,116,108,101,51;116,105,116,108,101,52;116,105,116,108,101,53;49,108,107,115,100,106,102,108,107,115,100,106,102;108,107,106,100,115,102,108,107,115,106,100,102;108,107,106,115,100,108,107,102,106,115,102,100;108,107,106,115,100,108,107,106,102,100;108,107,106,100,115,108,107,106,115,100,102;50,108,107,115,100,106,102,108,107,115,100,106,102;108,107,106,100,115,102,108,107,115,106,100,102;108,107,106,240,159,152,179,240,159,152,179,115,100,108,107,102,106,115,102,100;108,107,106,115,100,108,107,106,102,100;108,107,106,100,115,108,107,106,115,100,102;51,108,107,115,100,106,102,108,107,115,100,106,102;108,107,106,100,115,102,108,107,115,106,100,102;108,107,106,115,100,240,159,152,179,108,107,102,106,115,102,100;108,107,106,115,100,108,107,106,102,100;108,107,106,100,115,108,107,106,115,100,102;52,108,107,115,100,106,102,108,107,115,50,50,50,100,106,102;108,107,240,159,152,179,50,50,50,106,100,115,102,108,107,115,106,100,102;108,107,50,50,50,106,115,100,108,107,102,106,115,102,100;108,107,119,119,119,106,115,100,108,107,106,102,100;108,107,106,100,115,108,107,106,115,100,102;53,108,107,115,100,106,102,108,107,115,100,106,102;108,107,106,100,115,102,108,107,115,106,100,102;108,107,106,115,100,108,107,102,106,115,102,100;108,107,115,100,106,102,108,107,115,106,102,100,108,107,106,115,100,108,107,106,102,100;108,107,106,100,114,114,114,114,115,108,107,106,115,100,102;27,91,51,54,109,54,108,107,115,100,106,102,108,107,240,159,152,179,115,100,106,102;27,91,52,54,109,108,240,159,152,179,107,106,100,115,102,108,107,115,106,100,102;108,107,106,115,100,108,107,102,106,115,102,100;108,107,106,115,100,108,107,106,102,100;108,119,119,107,119,107,106,100,115,108,107,106,115,100,102\
85 changes: 53 additions & 32 deletions zellij-server/src/panes/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const TABSTOP_WIDTH: usize = 8; // TODO: is this always right?
pub const MAX_TITLE_STACK_SIZE: usize = 1000;

use vte::{Params, Perform};
use zellij_utils::{consts::VERSION, shared::version_number};
use zellij_utils::{consts::VERSION, shared::{version_number, ansi_len}};

use crate::output::{CharacterChunk, OutputBuffer, SixelImageChunk};
use crate::panes::alacritty_functions::{parse_number, xparse_color};
Expand All @@ -36,8 +36,9 @@ use crate::panes::search::SearchResult;
use crate::panes::selection::Selection;
use crate::panes::terminal_character::{
AnsiCode, CharacterStyles, CharsetIndex, Cursor, CursorShape, StandardCharset,
TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
TerminalCharacter, EMPTY_TERMINAL_CHARACTER, RESET_STYLES
};
use crate::ui::boundaries::boundary_type;

fn get_top_non_canonical_rows(rows: &mut Vec<Row>) -> Vec<Row> {
let mut index_of_last_non_canonical_row = None;
Expand Down Expand Up @@ -369,6 +370,8 @@ pub struct Grid {
pub focus_event_tracking: bool,
pub search_results: SearchResult,
pub pending_clipboard_update: Option<String>,
ui_component_bytes: Option<Vec<u8>>,
style: Style,
debug: bool,
}

Expand Down Expand Up @@ -457,6 +460,7 @@ impl Grid {
link_handler: Rc<RefCell<LinkHandler>>,
character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
sixel_image_store: Rc<RefCell<SixelImageStore>>,
style: Style, // TODO: consolidate this with terminal_emulator_colors
debug: bool,
) -> Self {
let sixel_grid = SixelGrid::new(character_cell_size.clone(), sixel_image_store);
Expand Down Expand Up @@ -507,6 +511,8 @@ impl Grid {
search_results: Default::default(),
sixel_grid,
pending_clipboard_update: None,
ui_component_bytes: None,
style,
debug,
}
}
Expand Down Expand Up @@ -2125,35 +2131,31 @@ impl Grid {
pub fn reset_cursor_position(&mut self) {
self.cursor = Cursor::new(0, 0);
}
fn parse_ui_component(&mut self, params: &[&[u8]]) -> Result<()> {
let mut params_iter = params.iter();
let _ = params_iter.next(); // remove OSC code
fn parse_ui_component(&mut self, params: Vec<u8>) -> Result<()> {
let decoded: Vec<String> = String::from_utf8_lossy(&params).to_string().split(';').map(|c| c.to_owned()).collect();
let mut params_iter = decoded.iter();
let component_name = params_iter
.next()
.and_then(|s| str::from_utf8(s).ok())
.with_context(|| format!("ui component must have a name"))?;

if component_name == "table" {
if component_name == &"table" {
let columns = params_iter.next()
.and_then(|stringified_columns| String::from_utf8_lossy(stringified_columns).parse::<usize>().ok())
.and_then(|stringified_columns| stringified_columns.parse::<usize>().ok())
.with_context(|| format!("table must have columns"))?;
let rows = params_iter.next()
.and_then(|stringified_rows| String::from_utf8_lossy(stringified_rows).parse::<usize>().ok())
.and_then(|stringified_rows| stringified_rows.parse::<usize>().ok())
.with_context(|| format!("table must have rows"))?;
let stringified_params = params_iter
.flat_map(|x| str::from_utf8(x))
.flat_map(|stringified| {
let mut utf8 = vec![];
for stringified_character in stringified.split(',') {
utf8.push(stringified_character.parse::<u8>().map_err(|e| format!("Failed to parse utf8: {:?}", e))?);
utf8.push(stringified_character.to_string().parse::<u8>().map_err(|e| format!("Failed to parse utf8: {:?}", e))?);
}
Ok::<String, String>(String::from_utf8_lossy(&utf8).to_string())
})
.collect::<Vec<String>>().into_iter();


let mut vte_parser = vte::Parser::new();
let encoded_table = table(columns, rows, stringified_params);
let encoded_table = table(columns, rows, stringified_params, Some(self.style.colors.green));
for &byte in &encoded_table {
vte_parser.advance(self, byte);
}
Expand All @@ -2166,7 +2168,6 @@ impl Grid {

impl Perform for Grid {
fn print(&mut self, c: char) {
log::info!("print: {:?}", c);
let c = self.cursor.charsets[self.active_charset].map(c);

// apparently, building TerminalCharacter like this without a "new" method
Expand Down Expand Up @@ -2234,6 +2235,8 @@ impl Perform for Grid {
params.iter().collect(),
);
}
} else if c == 'z' { // UI-component (Zellij internal)
self.ui_component_bytes = Some(vec![]);
}
}

Expand All @@ -2243,12 +2246,17 @@ impl Perform for Grid {
// we explicitly set this to false here because in the context of Sixel, we only render the
// image when it's done, i.e. in the unhook method
self.should_render = false;
} else if let Some(ui_component_bytes) = self.ui_component_bytes.as_mut() {
ui_component_bytes.push(byte);
}
}

fn unhook(&mut self) {
if self.sixel_grid.is_parsing() {
self.create_sixel_image();
} else if let Some(mut ui_component_bytes) = self.ui_component_bytes.take() {
let component_bytes = ui_component_bytes.drain(..);
self.parse_ui_component(component_bytes.collect()).non_fatal();
}
self.mark_for_rerender();
}
Expand Down Expand Up @@ -2440,11 +2448,6 @@ impl Perform for Grid {
// TBD - reset text cursor color - currently unimplemented
},

// Zellij UI elements
b"113" => { // TODO: find private osc range?
self.parse_ui_component(params).non_fatal();
},

_ => {
if self.debug {
log::warn!("Unhandled osc: {:?}", params);
Expand Down Expand Up @@ -3432,47 +3435,65 @@ impl Row {
}

// UI COMPONENTS - TODO: move elsewhere
fn table(columns: usize, rows: usize, contents: impl Iterator<Item=String>) -> Vec<u8> {
log::info!("columns: {:?}, rows: {:?}", columns, rows);
fn table(columns: usize, rows: usize, contents: impl Iterator<Item=String>, title_color: Option<PaletteColor>) -> Vec<u8> {
let mut stringified = String::new();
let mut stringified_columns: BTreeMap<usize, Vec<String>> = BTreeMap::new();
// for (i, cell) in contents.iter().enumerate() {
for (i, cell) in contents.enumerate() {
let column_index = i % columns;
stringified_columns.entry(column_index).or_insert_with(Vec::new).push(cell.to_owned());
}
let mut stringified_rows: BTreeMap<usize, Vec<String>> = BTreeMap::new();
let mut row_width = 0;
for (column_index, stringified_column) in stringified_columns.values().into_iter().enumerate() {
let mut max_column_width = 0;
for cell in stringified_column {
let cell_width = cell.width();
let cell_width = ansi_len(cell);
if cell_width > max_column_width {
max_column_width = cell_width;
}
}
row_width += max_column_width + 1;
for (row_index, cell) in stringified_column.into_iter().enumerate() {
let padded_string = format!("{:width$}", cell, width=max_column_width);
stringified_rows.entry(row_index).or_insert_with(Vec::new).push(padded_string);
let mut padded = cell.to_owned();
for _ in ansi_len(cell)..max_column_width {
padded.push(' ');
}
stringified_rows.entry(row_index).or_insert_with(Vec::new).push(padded);
}

}
let title_styles = CharacterStyles::new()
.foreground(title_color.map(|t| t.into()))
.bold(Some(AnsiCode::On));
let cell_styles = CharacterStyles::new()
.bold(Some(AnsiCode::On));
for (row_index, row) in stringified_rows.values().into_iter().enumerate() {
let is_title_row = row_index == 0;
let is_last_row = row_index == rows.saturating_sub(1);
for cell in row {
if is_title_row {
stringified.push_str(&format!("\u{1b}[36;1m{}\u{1b}[0m ", cell));
// stringified.push_str(&format!("\u{1b}[36;1m{}\u{1b}[0m ", cell));
stringified.push_str(&format!("{}{}{} ", title_styles, cell, RESET_STYLES));
} else {
stringified.push_str(&format!("\u{1b}[1m{}\u{1b}[0m ", cell));
// stringified.push_str(&format!("\u{1b}[1m{}\u{1b}[0m ", cell));
stringified.push_str(&format!("{}{}{} ", cell_styles, cell, RESET_STYLES));
}
}
stringified.push_str("\n\r");
let mut title_underline = String::new();
for _ in 0..row_width.saturating_sub(1) { // removing 1 because the last cell doesn't have
// padding
title_underline.push_str(boundary_type::HORIZONTAL);
}
if !is_last_row {
stringified.push_str("\n\r");
}
if is_title_row {
stringified.push_str(&format!("{}\n\r", title_underline));
}
}
log::info!("stringified: {:?}", stringified);
stringified.as_bytes().to_vec()
}



#[cfg(test)]
#[path = "./unit/grid_tests.rs"]
mod grid_tests;
1 change: 1 addition & 0 deletions zellij-server/src/panes/plugin_pane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ macro_rules! get_or_create_grid {
$self.link_handler.clone(),
$self.character_cell_size.clone(),
$self.sixel_image_store.clone(),
$self.style.clone(),
$self.debug,
);
grid.hide_cursor();
Expand Down
1 change: 1 addition & 0 deletions zellij-server/src/panes/terminal_pane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,7 @@ impl TerminalPane {
link_handler,
character_cell_size,
sixel_image_store,
style.clone(),
debug,
);
TerminalPane {
Expand Down
Loading