diff options
author | Aram Drevekenin <aram@poor.dev> | 2021-11-25 16:21:59 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-25 16:21:59 +0100 |
commit | 6c6a4393f403359838ea9b32b09520de4f50c019 (patch) | |
tree | d1b553cab1a22c271be99bbc35a5e608bc96676d | |
parent | 9fb2c7ca168679905d79dcdaf686d40f4a6dc958 (diff) |
This adds a UI for multiple users in panes (behind a feature flag) (#897)
* feat(ui): multiple users in panes
* style(fmt): make rustfmt happy
* style(fmt): make clippy happy
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | default-plugins/status-bar/src/main.rs | 64 | ||||
-rw-r--r-- | default-plugins/tab-bar/src/line.rs | 12 | ||||
-rw-r--r-- | default-plugins/tab-bar/src/main.rs | 2 | ||||
-rw-r--r-- | default-plugins/tab-bar/src/tab.rs | 8 | ||||
-rw-r--r-- | zellij-server/Cargo.toml | 1 | ||||
-rw-r--r-- | zellij-server/src/lib.rs | 14 | ||||
-rw-r--r-- | zellij-server/src/panes/grid.rs | 13 | ||||
-rw-r--r-- | zellij-server/src/panes/plugin_pane.rs | 42 | ||||
-rw-r--r-- | zellij-server/src/panes/terminal_character.rs | 11 | ||||
-rw-r--r-- | zellij-server/src/panes/terminal_pane.rs | 123 | ||||
-rw-r--r-- | zellij-server/src/tab.rs | 313 | ||||
-rw-r--r-- | zellij-server/src/ui/boundaries.rs | 11 | ||||
-rw-r--r-- | zellij-server/src/ui/mod.rs | 1 | ||||
-rw-r--r-- | zellij-server/src/ui/pane_boundaries_frame.rs | 491 | ||||
-rw-r--r-- | zellij-server/src/ui/pane_contents_and_ui.rs | 165 | ||||
-rw-r--r-- | zellij-tile/src/data.rs | 1 | ||||
-rw-r--r-- | zellij-utils/src/shared.rs | 13 |
18 files changed, 1005 insertions, 281 deletions
diff --git a/Cargo.lock b/Cargo.lock index 2210d2bd8..e454c20c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2924,6 +2924,7 @@ dependencies = [ "url", "wasmer", "wasmer-wasi", + "zellij-tile", "zellij-utils", ] diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index f1b2197e4..1ecd87ab1 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -76,61 +76,55 @@ pub struct ColoredElements { // that can be defined in the config perhaps fn color_elements(palette: Palette) -> ColoredElements { match palette.source { - // "cyan" here is used as a background as a dirty hack - // this is because the Palette struct doesn't have a "gray" section - // and we can't use its "bg" because that is now dynamically taken from the terminal - // and might often not actually fit the rest of the colorscheme - // - // to fix this, we need to restructure the Palette struct PaletteSource::Default => ColoredElements { - selected_prefix_separator: style!(palette.cyan, palette.green), + selected_prefix_separator: style!(palette.gray, palette.green), selected_char_left_separator: style!(palette.black, palette.green).bold(), selected_char_shortcut: style!(palette.red, palette.green).bold(), selected_char_right_separator: style!(palette.black, palette.green).bold(), selected_styled_text: style!(palette.black, palette.green).bold(), - selected_suffix_separator: style!(palette.green, palette.cyan).bold(), - unselected_prefix_separator: style!(palette.cyan, palette.fg), + selected_suffix_separator: style!(palette.green, palette.gray).bold(), + unselected_prefix_separator: style!(palette.gray, palette.fg), unselected_char_left_separator: style!(palette.black, palette.fg).bold(), unselected_char_shortcut: style!(palette.red, palette.fg).bold(), unselected_char_right_separator: style!(palette.black, palette.fg).bold(), unselected_styled_text: style!(palette.black, palette.fg).bold(), - unselected_suffix_separator: style!(palette.fg, palette.cyan), - disabled_prefix_separator: style!(palette.cyan, palette.fg), - disabled_styled_text: style!(palette.cyan, palette.fg).dimmed(), - disabled_suffix_separator: style!(palette.fg, palette.cyan), - selected_single_letter_prefix_separator: style!(palette.cyan, palette.green), + unselected_suffix_separator: style!(palette.fg, palette.gray), + disabled_prefix_separator: style!(palette.gray, palette.fg), + disabled_styled_text: style!(palette.gray, palette.fg).dimmed(), + disabled_suffix_separator: style!(palette.fg, palette.gray), + selected_single_letter_prefix_separator: style!(palette.gray, palette.green), selected_single_letter_char_shortcut: style!(palette.red, palette.green).bold(), - selected_single_letter_suffix_separator: style!(palette.green, palette.cyan), - unselected_single_letter_prefix_separator: style!(palette.cyan, palette.fg), + selected_single_letter_suffix_separator: style!(palette.green, palette.gray), + unselected_single_letter_prefix_separator: style!(palette.gray, palette.fg), unselected_single_letter_char_shortcut: style!(palette.red, palette.fg).bold(), - unselected_single_letter_suffix_separator: style!(palette.fg, palette.cyan), - superkey_prefix: style!(palette.white, palette.cyan).bold(), - superkey_suffix_separator: style!(palette.cyan, palette.cyan), + unselected_single_letter_suffix_separator: style!(palette.fg, palette.gray), + superkey_prefix: style!(palette.white, palette.gray).bold(), + superkey_suffix_separator: style!(palette.gray, palette.gray), }, PaletteSource::Xresources => ColoredElements { - selected_prefix_separator: style!(palette.cyan, palette.green), + selected_prefix_separator: style!(palette.gray, palette.green), selected_char_left_separator: style!(palette.fg, palette.green).bold(), selected_char_shortcut: style!(palette.red, palette.green).bold(), selected_char_right_separator: style!(palette.fg, palette.green).bold(), - selected_styled_text: style!(palette.cyan, palette.green).bold(), - selected_suffix_separator: style!(palette.green, palette.cyan).bold(), - unselected_prefix_separator: style!(palette.cyan, palette.fg), - unselected_char_left_separator: style!(palette.cyan, palette.fg).bold(), + selected_styled_text: style!(palette.gray, palette.green).bold(), + selected_suffix_separator: style!(palette.green, palette.gray).bold(), + unselected_prefix_separator: style!(palette.gray, palette.fg), + unselected_char_left_separator: style!(palette.gray, palette.fg).bold(), unselected_char_shortcut: style!(palette.red, palette.fg).bold(), - unselected_char_right_separator: style!(palette.cyan, palette.fg).bold(), - unselected_styled_text: style!(palette.cyan, palette.fg).bold(), - unselected_suffix_separator: style!(palette.fg, palette.cyan), - disabled_prefix_separator: style!(palette.cyan, palette.fg), - disabled_styled_text: style!(palette.cyan, palette.fg).dimmed(), - disabled_suffix_separator: style!(palette.fg, palette.cyan), + unselected_char_right_separator: style!(palette.gray, palette.fg).bold(), + unselected_styled_text: style!(palette.gray, palette.fg).bold(), + unselected_suffix_separator: style!(palette.fg, palette.gray), + disabled_prefix_separator: style!(palette.gray, palette.fg), + disabled_styled_text: style!(palette.gray, palette.fg).dimmed(), + disabled_suffix_separator: style!(palette.fg, palette.gray), selected_single_letter_prefix_separator: style!(palette.fg, palette.green), selected_single_letter_char_shortcut: style!(palette.red, palette.green).bold(), selected_single_letter_suffix_separator: style!(palette.green, palette.fg), - unselected_single_letter_prefix_separator: style!(palette.fg, palette.cyan), + unselected_single_letter_prefix_separator: style!(palette.fg, palette.gray), unselected_single_letter_char_shortcut: style!(palette.red, palette.fg).bold(), - unselected_single_letter_suffix_separator: style!(palette.fg, palette.cyan), - superkey_prefix: style!(palette.cyan, palette.fg).bold(), - superkey_suffix_separator: style!(palette.fg, palette.cyan), + unselected_single_letter_suffix_separator: style!(palette.fg, palette.gray), + superkey_prefix: style!(palette.gray, palette.fg).bold(), + superkey_suffix_separator: style!(palette.fg, palette.gray), }, } } @@ -231,7 +225,7 @@ impl ZellijPlugin for State { // [48;5;238m is gray background, [0K is so that it fills the rest of the line // [m is background reset, [0K is so that it clears the rest of the line - match self.mode_info.palette.cyan { + match self.mode_info.palette.gray { PaletteColor::Rgb((r, g, b)) => { println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", first_line, r, g, b); } diff --git a/default-plugins/tab-bar/src/line.rs b/default-plugins/tab-bar/src/line.rs index af78b0fcf..2e180845b 100644 --- a/default-plugins/tab-bar/src/line.rs +++ b/default-plugins/tab-bar/src/line.rs @@ -102,11 +102,11 @@ 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.width() + 2 * separator.width(); - let left_separator = style!(palette.cyan, palette.orange).paint(separator); + let left_separator = style!(palette.gray, palette.orange).paint(separator); let more_styled_text = style!(palette.black, palette.orange) .bold() .paint(more_text); - let right_separator = style!(palette.orange, palette.cyan).paint(separator); + let right_separator = style!(palette.orange, palette.gray).paint(separator); let more_styled_text = format!( "{}", ANSIStrings(&[left_separator, more_styled_text, right_separator,]) @@ -132,11 +132,11 @@ fn right_more_message( }; // chars length plus separator length on both sides let more_text_len = more_text.width() + 2 * separator.width(); - let left_separator = style!(palette.cyan, palette.orange).paint(separator); + let left_separator = style!(palette.gray, palette.orange).paint(separator); let more_styled_text = style!(palette.black, palette.orange) .bold() .paint(more_text); - let right_separator = style!(palette.orange, palette.cyan).paint(separator); + let right_separator = style!(palette.orange, palette.gray).paint(separator); let more_styled_text = format!( "{}", ANSIStrings(&[left_separator, more_styled_text, right_separator,]) @@ -151,7 +151,7 @@ fn tab_line_prefix(session_name: Option<&str>, palette: Palette, cols: usize) -> let prefix_text = " Zellij ".to_string(); let prefix_text_len = prefix_text.chars().count(); - let prefix_styled_text = style!(palette.white, palette.cyan) + let prefix_styled_text = style!(palette.white, palette.gray) .bold() .paint(prefix_text); let mut parts = vec![LinePart { @@ -161,7 +161,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.width(); - let name_part_styled_text = style!(palette.white, palette.cyan).bold().paint(name_part); + let name_part_styled_text = style!(palette.white, palette.gray).bold().paint(name_part); if cols.saturating_sub(prefix_text_len) >= name_part_len { parts.push(LinePart { part: format!("{}", name_part_styled_text), diff --git a/default-plugins/tab-bar/src/main.rs b/default-plugins/tab-bar/src/main.rs index 936c18e48..5902050ae 100644 --- a/default-plugins/tab-bar/src/main.rs +++ b/default-plugins/tab-bar/src/main.rs @@ -112,7 +112,7 @@ impl ZellijPlugin for State { } len_cnt += bar_part.len; } - match self.mode_info.palette.cyan { + match self.mode_info.palette.gray { PaletteColor::Rgb((r, g, b)) => { println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", s, r, g, b); } diff --git a/default-plugins/tab-bar/src/tab.rs b/default-plugins/tab-bar/src/tab.rs index fdb498553..fc34b6c36 100644 --- a/default-plugins/tab-bar/src/tab.rs +++ b/default-plugins/tab-bar/src/tab.rs @@ -5,12 +5,12 @@ 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 left_separator = style!(palette.gray, palette.green).paint(separator); 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)); - let right_separator = style!(palette.green, palette.cyan).paint(separator); + let right_separator = style!(palette.green, palette.gray).paint(separator); let tab_styled_text = format!( "{}", ANSIStrings(&[left_separator, tab_styled_text, right_separator,]) @@ -22,12 +22,12 @@ 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 left_separator = style!(palette.gray, palette.fg).paint(separator); 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)); - let right_separator = style!(palette.fg, palette.cyan).paint(separator); + let right_separator = style!(palette.fg, palette.gray).paint(separator); let tab_styled_text = format!( "{}", ANSIStrings(&[left_separator, tab_styled_text, right_separator,]) diff --git a/zellij-server/Cargo.toml b/zellij-server/Cargo.toml index f38739f13..1855daea1 100644 --- a/zellij-server/Cargo.toml +++ b/zellij-server/Cargo.toml @@ -22,6 +22,7 @@ wasmer = "1.0.0" wasmer-wasi = "1.0.0" cassowary = "0.3.0" zellij-utils = { path = "../zellij-utils/", version = "0.21.0" } +zellij-tile = { path = "../zellij-tile/", version = "0.21.0" } log = "0.4.14" typetag = "0.1.7" chrono = "0.4.19" diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 4d94607f4..cb8f050dc 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -11,7 +11,7 @@ mod ui; mod wasm_vm; use log::info; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::{ path::PathBuf, sync::{Arc, Mutex, RwLock}, @@ -134,9 +134,15 @@ impl SessionState { } } pub fn new_client(&mut self) -> ClientId { - let mut clients: Vec<ClientId> = self.clients.keys().copied().collect(); - clients.sort_unstable(); - let next_client_id = clients.last().unwrap_or(&0) + 1; + let clients: HashSet<ClientId> = self.clients.keys().copied().collect(); + let mut next_client_id = 1; + loop { + if clients.contains(&next_client_id) { + next_client_id += 1; + } else { + break; + } + } self.clients.insert(next_client_id, None); next_client_id } diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs index e3752fef1..706e0e549 100644 --- a/zellij-server/src/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -448,6 +448,9 @@ impl Grid { pub fn render_full_viewport(&mut self) { self.output_buffer.update_all_lines(); } + pub fn update_line_for_rendering(&mut self, line_index: usize) { + self.output_buffer.update_line(line_index); + } pub fn advance_to_next_tabstop(&mut self, styles: CharacterStyles) { let mut next_tabstop = None; for tabstop in self.horizontal_tabstops.iter() { @@ -1057,6 +1060,16 @@ impl Grid { self.add_character_at_cursor_position(terminal_character); self.move_cursor_forward_until_edge(character_width); } + pub fn get_character_under_cursor(&self) -> Option<TerminalCharacter> { + let absolute_x_in_line = self.get_absolute_character_index(self.cursor.x, self.cursor.y); + self.viewport + .get(self.cursor.y) + .and_then(|current_line| current_line.columns.get(absolute_x_in_line)) + .copied() + } + pub fn get_absolute_character_index(&self, x: usize, y: usize) -> usize { + self.viewport.get(y).unwrap().absolute_character_index(x) + } pub fn move_cursor_forward_until_edge(&mut self, count: usize) { let count_to_move = std::cmp::min(count, self.width - (self.cursor.x)); self.cursor.x += count_to_move; diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs index 6b1cb4168..36274b347 100644 --- a/zellij-server/src/panes/plugin_pane.rs +++ b/zellij-server/src/panes/plugin_pane.rs @@ -5,8 +5,9 @@ use std::unimplemented; use crate::panes::PaneId; use crate::pty::VteBytes; use crate::tab::Pane; -use crate::ui::pane_boundaries_frame::PaneFrame; +use crate::ui::pane_boundaries_frame::{FrameParams, PaneFrame}; use crate::wasm_vm::PluginInstruction; +use crate::ClientId; use zellij_utils::pane_size::Offset; use zellij_utils::position::Position; use zellij_utils::shared::ansi_len; @@ -27,7 +28,6 @@ pub(crate) struct PluginPane { pub active_at: Instant, pub pane_title: String, frame: bool, - frame_color: Option<PaletteColor>, borderless: bool, } @@ -47,7 +47,6 @@ impl PluginPane { send_plugin_instructions, active_at: Instant::now(), frame: false, - frame_color: None, content_offset: Offset::default(), pane_title: title, borderless: false, @@ -152,17 +151,6 @@ impl Pane for PluginPane { self.should_render = false; let contents = buf_rx.recv().unwrap(); - // FIXME: This is a hack that assumes all fixed-size panes are borderless. This - // will eventually need fixing! - if self.frame && !(self.geom.rows.is_fixed() || self.geom.cols.is_fixed()) { - let frame = PaneFrame { - geom: self.current_geom().into(), - title: self.pane_title.clone(), - color: self.frame_color, - ..Default::default() - }; - vte_output.push_str(&frame.render()); - } for (index, line) in contents.lines().enumerate() { let actual_len = ansi_len(line); let line_to_print = if actual_len > self.get_content_columns() { @@ -212,6 +200,28 @@ impl Pane for PluginPane { None } } + fn render_frame(&mut self, _client_id: ClientId, frame_params: FrameParams) -> Option<String> { + // FIXME: This is a hack that assumes all fixed-size panes are borderless. This + // will eventually need fixing! + if self.frame && !(self.geom.rows.is_fixed() || self.geom.cols.is_fixed()) { + let frame = PaneFrame::new( + self.current_geom().into(), + (0, 0), // scroll position + self.pane_title.clone(), + frame_params, + ); + Some(frame.render()) + } else { + None + } + } + fn render_fake_cursor( + &mut self, + _cursor_color: PaletteColor, + _text_color: PaletteColor, + ) -> Option<String> { + None + } fn pid(&self) -> PaneId { PaneId::Plugin(self.pid) } @@ -317,10 +327,6 @@ impl Pane for PluginPane { fn set_content_offset(&mut self, offset: Offset) { self.content_offset = offset; } - fn set_boundary_color(&mut self, color: Option<PaletteColor>) { - self.frame_color = color; - self.set_should_render(true); - } fn set_borderless(&mut self, borderless: bool) { self.borderless = borderless; } diff --git a/zellij-server/src/panes/terminal_character.rs b/zellij-server/src/panes/terminal_character.rs index c9de37603..e6a9d85cf 100644 --- a/zellij-server/src/panes/terminal_character.rs +++ b/zellij-server/src/panes/terminal_character.rs @@ -1,9 +1,11 @@ +use std::convert::From; use std::fmt::{self, Debug, Display, Formatter}; use std::ops::{Index, IndexMut}; use zellij_utils::vte::ParamsIter; use crate::panes::alacritty_functions::parse_sgr_color; +use zellij_tile::data::PaletteColor; pub const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter { character: ' ', @@ -35,6 +37,15 @@ pub enum AnsiCode { ColorIndex(u8), } +impl From<PaletteColor> for AnsiCode { + fn from(palette_color: PaletteColor) -> Self { + match palette_color { + PaletteColor::Rgb((r, g, b)) => AnsiCode::RgbCode((r, g, b)), + PaletteColor::EightBit(index) => AnsiCode::ColorIndex(index), + } + } +} + #[derive(Clone, Copy, Eq, PartialEq, Debug)] pub enum NamedColor { Black, diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index f2974cee9..0a6942e95 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -7,6 +7,8 @@ use crate::panes::{ }; use crate::pty::VteBytes; use crate::tab::Pane; +use crate::ClientId; +use std::collections::{HashMap, HashSet}; use std::fmt::Debug; use std::os::unix::io::RawFd; use std::time::{self, Instant}; @@ -20,7 +22,7 @@ use zellij_utils::{ pub const SELECTION_SCROLL_INTERVAL_MS: u64 = 10; -use crate::ui::pane_boundaries_frame::PaneFrame; +use crate::ui::pane_boundaries_frame::{FrameParams, PaneFrame}; #[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)] pub enum PaneId { @@ -42,9 +44,9 @@ pub struct TerminalPane { selection_scrolled_at: time::Instant, content_offset: Offset, pane_title: String, - frame: Option<PaneFrame>, - frame_color: Option<PaletteColor>, + frame: HashMap<ClientId, PaneFrame>, borderless: bool, + fake_cursor_locations: HashSet<(usize, usize)>, // (x, y) - these hold a record of previous fake cursors which we need to clear on render } impl Pane for TerminalPane { @@ -172,9 +174,7 @@ impl Pane for TerminalPane { 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 - if self.frame.is_some() { - self.frame.replace(PaneFrame::default()); - } + self.frame.clear(); self.grid.render_full_viewport(); } fn selectable(&self) -> bool { @@ -187,14 +187,14 @@ impl Pane for TerminalPane { if self.should_render() { let mut vte_output = String::new(); let mut character_styles = CharacterStyles::new(); + let content_x = self.get_content_x(); + let content_y = self.get_content_y(); if self.grid.clear_viewport_before_rendering { for line_index in 0..self.grid.height { - let x = self.get_content_x(); - let y = self.get_content_y(); vte_output.push_str(&format!( "\u{1b}[{};{}H\u{1b}[m", - y + line_index + 1, - x + 1 + content_y + line_index + 1, + content_x + 1 )); // goto row/col and reset styles for _col_index in 0..self.grid.width { vte_output.push(EMPTY_TERMINAL_CHARACTER.character); @@ -202,6 +202,19 @@ impl Pane for TerminalPane { } self.grid.clear_viewport_before_rendering = false; } + // here we clear the previous cursor locations by adding an empty style-less character + // in their location, this is done before the main rendering logic so that if there + // actually is another character there, it will be overwritten + for (y, x) in self.fake_cursor_locations.drain() { + // we need to make sure to update the line in the line buffer so that if there's + // another character there it'll override it and we won't create holes with our + // empty character + self.grid.update_line_for_rendering(y); + let x = content_x + x; + let y = content_y + y; |