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
This commit is contained in:
parent
9fb2c7ca16
commit
6c6a4393f4
18 changed files with 1008 additions and 284 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2924,6 +2924,7 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
"wasmer",
|
"wasmer",
|
||||||
"wasmer-wasi",
|
"wasmer-wasi",
|
||||||
|
"zellij-tile",
|
||||||
"zellij-utils",
|
"zellij-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,61 +76,55 @@ pub struct ColoredElements {
|
||||||
// that can be defined in the config perhaps
|
// that can be defined in the config perhaps
|
||||||
fn color_elements(palette: Palette) -> ColoredElements {
|
fn color_elements(palette: Palette) -> ColoredElements {
|
||||||
match palette.source {
|
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 {
|
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_left_separator: style!(palette.black, palette.green).bold(),
|
||||||
selected_char_shortcut: style!(palette.red, palette.green).bold(),
|
selected_char_shortcut: style!(palette.red, palette.green).bold(),
|
||||||
selected_char_right_separator: style!(palette.black, palette.green).bold(),
|
selected_char_right_separator: style!(palette.black, palette.green).bold(),
|
||||||
selected_styled_text: style!(palette.black, palette.green).bold(),
|
selected_styled_text: style!(palette.black, palette.green).bold(),
|
||||||
selected_suffix_separator: style!(palette.green, palette.cyan).bold(),
|
selected_suffix_separator: style!(palette.green, palette.gray).bold(),
|
||||||
unselected_prefix_separator: style!(palette.cyan, palette.fg),
|
unselected_prefix_separator: style!(palette.gray, palette.fg),
|
||||||
unselected_char_left_separator: style!(palette.black, palette.fg).bold(),
|
unselected_char_left_separator: style!(palette.black, palette.fg).bold(),
|
||||||
unselected_char_shortcut: style!(palette.red, palette.fg).bold(),
|
unselected_char_shortcut: style!(palette.red, palette.fg).bold(),
|
||||||
unselected_char_right_separator: style!(palette.black, palette.fg).bold(),
|
unselected_char_right_separator: style!(palette.black, palette.fg).bold(),
|
||||||
unselected_styled_text: style!(palette.black, palette.fg).bold(),
|
unselected_styled_text: style!(palette.black, palette.fg).bold(),
|
||||||
unselected_suffix_separator: style!(palette.fg, palette.cyan),
|
unselected_suffix_separator: style!(palette.fg, palette.gray),
|
||||||
disabled_prefix_separator: style!(palette.cyan, palette.fg),
|
disabled_prefix_separator: style!(palette.gray, palette.fg),
|
||||||
disabled_styled_text: style!(palette.cyan, palette.fg).dimmed(),
|
disabled_styled_text: style!(palette.gray, palette.fg).dimmed(),
|
||||||
disabled_suffix_separator: style!(palette.fg, palette.cyan),
|
disabled_suffix_separator: style!(palette.fg, palette.gray),
|
||||||
selected_single_letter_prefix_separator: style!(palette.cyan, palette.green),
|
selected_single_letter_prefix_separator: style!(palette.gray, palette.green),
|
||||||
selected_single_letter_char_shortcut: style!(palette.red, palette.green).bold(),
|
selected_single_letter_char_shortcut: style!(palette.red, palette.green).bold(),
|
||||||
selected_single_letter_suffix_separator: style!(palette.green, palette.cyan),
|
selected_single_letter_suffix_separator: style!(palette.green, palette.gray),
|
||||||
unselected_single_letter_prefix_separator: style!(palette.cyan, palette.fg),
|
unselected_single_letter_prefix_separator: style!(palette.gray, palette.fg),
|
||||||
unselected_single_letter_char_shortcut: style!(palette.red, palette.fg).bold(),
|
unselected_single_letter_char_shortcut: style!(palette.red, palette.fg).bold(),
|
||||||
unselected_single_letter_suffix_separator: style!(palette.fg, palette.cyan),
|
unselected_single_letter_suffix_separator: style!(palette.fg, palette.gray),
|
||||||
superkey_prefix: style!(palette.white, palette.cyan).bold(),
|
superkey_prefix: style!(palette.white, palette.gray).bold(),
|
||||||
superkey_suffix_separator: style!(palette.cyan, palette.cyan),
|
superkey_suffix_separator: style!(palette.gray, palette.gray),
|
||||||
},
|
},
|
||||||
PaletteSource::Xresources => ColoredElements {
|
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_left_separator: style!(palette.fg, palette.green).bold(),
|
||||||
selected_char_shortcut: style!(palette.red, palette.green).bold(),
|
selected_char_shortcut: style!(palette.red, palette.green).bold(),
|
||||||
selected_char_right_separator: style!(palette.fg, palette.green).bold(),
|
selected_char_right_separator: style!(palette.fg, palette.green).bold(),
|
||||||
selected_styled_text: style!(palette.cyan, palette.green).bold(),
|
selected_styled_text: style!(palette.gray, palette.green).bold(),
|
||||||
selected_suffix_separator: style!(palette.green, palette.cyan).bold(),
|
selected_suffix_separator: style!(palette.green, palette.gray).bold(),
|
||||||
unselected_prefix_separator: style!(palette.cyan, palette.fg),
|
unselected_prefix_separator: style!(palette.gray, palette.fg),
|
||||||
unselected_char_left_separator: style!(palette.cyan, palette.fg).bold(),
|
unselected_char_left_separator: style!(palette.gray, palette.fg).bold(),
|
||||||
unselected_char_shortcut: style!(palette.red, palette.fg).bold(),
|
unselected_char_shortcut: style!(palette.red, palette.fg).bold(),
|
||||||
unselected_char_right_separator: style!(palette.cyan, palette.fg).bold(),
|
unselected_char_right_separator: style!(palette.gray, palette.fg).bold(),
|
||||||
unselected_styled_text: style!(palette.cyan, palette.fg).bold(),
|
unselected_styled_text: style!(palette.gray, palette.fg).bold(),
|
||||||
unselected_suffix_separator: style!(palette.fg, palette.cyan),
|
unselected_suffix_separator: style!(palette.fg, palette.gray),
|
||||||
disabled_prefix_separator: style!(palette.cyan, palette.fg),
|
disabled_prefix_separator: style!(palette.gray, palette.fg),
|
||||||
disabled_styled_text: style!(palette.cyan, palette.fg).dimmed(),
|
disabled_styled_text: style!(palette.gray, palette.fg).dimmed(),
|
||||||
disabled_suffix_separator: style!(palette.fg, palette.cyan),
|
disabled_suffix_separator: style!(palette.fg, palette.gray),
|
||||||
selected_single_letter_prefix_separator: style!(palette.fg, palette.green),
|
selected_single_letter_prefix_separator: style!(palette.fg, palette.green),
|
||||||
selected_single_letter_char_shortcut: style!(palette.red, palette.green).bold(),
|
selected_single_letter_char_shortcut: style!(palette.red, palette.green).bold(),
|
||||||
selected_single_letter_suffix_separator: style!(palette.green, palette.fg),
|
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_char_shortcut: style!(palette.red, palette.fg).bold(),
|
||||||
unselected_single_letter_suffix_separator: style!(palette.fg, palette.cyan),
|
unselected_single_letter_suffix_separator: style!(palette.fg, palette.gray),
|
||||||
superkey_prefix: style!(palette.cyan, palette.fg).bold(),
|
superkey_prefix: style!(palette.gray, palette.fg).bold(),
|
||||||
superkey_suffix_separator: style!(palette.fg, palette.cyan),
|
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
|
// [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
|
// [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)) => {
|
PaletteColor::Rgb((r, g, b)) => {
|
||||||
println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", first_line, r, g, b);
|
println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", first_line, r, g, b);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,11 +102,11 @@ fn left_more_message(tab_count_to_the_left: usize, palette: Palette, separator:
|
||||||
// 238
|
// 238
|
||||||
// chars length plus separator length on both sides
|
// chars length plus separator length on both sides
|
||||||
let more_text_len = more_text.width() + 2 * separator.width();
|
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)
|
let more_styled_text = style!(palette.black, palette.orange)
|
||||||
.bold()
|
.bold()
|
||||||
.paint(more_text);
|
.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!(
|
let more_styled_text = format!(
|
||||||
"{}",
|
"{}",
|
||||||
ANSIStrings(&[left_separator, more_styled_text, right_separator,])
|
ANSIStrings(&[left_separator, more_styled_text, right_separator,])
|
||||||
|
|
@ -132,11 +132,11 @@ fn right_more_message(
|
||||||
};
|
};
|
||||||
// chars length plus separator length on both sides
|
// chars length plus separator length on both sides
|
||||||
let more_text_len = more_text.width() + 2 * separator.width();
|
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)
|
let more_styled_text = style!(palette.black, palette.orange)
|
||||||
.bold()
|
.bold()
|
||||||
.paint(more_text);
|
.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!(
|
let more_styled_text = format!(
|
||||||
"{}",
|
"{}",
|
||||||
ANSIStrings(&[left_separator, more_styled_text, right_separator,])
|
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 = " Zellij ".to_string();
|
||||||
|
|
||||||
let prefix_text_len = prefix_text.chars().count();
|
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()
|
.bold()
|
||||||
.paint(prefix_text);
|
.paint(prefix_text);
|
||||||
let mut parts = vec![LinePart {
|
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 {
|
if let Some(name) = session_name {
|
||||||
let name_part = format!("({}) ", name);
|
let name_part = format!("({}) ", name);
|
||||||
let name_part_len = name_part.width();
|
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 {
|
if cols.saturating_sub(prefix_text_len) >= name_part_len {
|
||||||
parts.push(LinePart {
|
parts.push(LinePart {
|
||||||
part: format!("{}", name_part_styled_text),
|
part: format!("{}", name_part_styled_text),
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ impl ZellijPlugin for State {
|
||||||
}
|
}
|
||||||
len_cnt += bar_part.len;
|
len_cnt += bar_part.len;
|
||||||
}
|
}
|
||||||
match self.mode_info.palette.cyan {
|
match self.mode_info.palette.gray {
|
||||||
PaletteColor::Rgb((r, g, b)) => {
|
PaletteColor::Rgb((r, g, b)) => {
|
||||||
println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", s, r, g, b);
|
println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", s, r, g, b);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,12 @@ use zellij_tile::prelude::*;
|
||||||
use zellij_tile_utils::style;
|
use zellij_tile_utils::style;
|
||||||
|
|
||||||
pub fn active_tab(text: String, palette: Palette, separator: &str) -> LinePart {
|
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_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)
|
let tab_styled_text = style!(palette.black, palette.green)
|
||||||
.bold()
|
.bold()
|
||||||
.paint(format!(" {} ", text));
|
.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!(
|
let tab_styled_text = format!(
|
||||||
"{}",
|
"{}",
|
||||||
ANSIStrings(&[left_separator, tab_styled_text, right_separator,])
|
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 {
|
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_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)
|
let tab_styled_text = style!(palette.black, palette.fg)
|
||||||
.bold()
|
.bold()
|
||||||
.paint(format!(" {} ", text));
|
.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!(
|
let tab_styled_text = format!(
|
||||||
"{}",
|
"{}",
|
||||||
ANSIStrings(&[left_separator, tab_styled_text, right_separator,])
|
ANSIStrings(&[left_separator, tab_styled_text, right_separator,])
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ wasmer = "1.0.0"
|
||||||
wasmer-wasi = "1.0.0"
|
wasmer-wasi = "1.0.0"
|
||||||
cassowary = "0.3.0"
|
cassowary = "0.3.0"
|
||||||
zellij-utils = { path = "../zellij-utils/", version = "0.21.0" }
|
zellij-utils = { path = "../zellij-utils/", version = "0.21.0" }
|
||||||
|
zellij-tile = { path = "../zellij-tile/", version = "0.21.0" }
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
typetag = "0.1.7"
|
typetag = "0.1.7"
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ mod ui;
|
||||||
mod wasm_vm;
|
mod wasm_vm;
|
||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::{
|
use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{Arc, Mutex, RwLock},
|
sync::{Arc, Mutex, RwLock},
|
||||||
|
|
@ -134,9 +134,15 @@ impl SessionState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn new_client(&mut self) -> ClientId {
|
pub fn new_client(&mut self) -> ClientId {
|
||||||
let mut clients: Vec<ClientId> = self.clients.keys().copied().collect();
|
let clients: HashSet<ClientId> = self.clients.keys().copied().collect();
|
||||||
clients.sort_unstable();
|
let mut next_client_id = 1;
|
||||||
let next_client_id = clients.last().unwrap_or(&0) + 1;
|
loop {
|
||||||
|
if clients.contains(&next_client_id) {
|
||||||
|
next_client_id += 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
self.clients.insert(next_client_id, None);
|
self.clients.insert(next_client_id, None);
|
||||||
next_client_id
|
next_client_id
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -448,6 +448,9 @@ impl Grid {
|
||||||
pub fn render_full_viewport(&mut self) {
|
pub fn render_full_viewport(&mut self) {
|
||||||
self.output_buffer.update_all_lines();
|
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) {
|
pub fn advance_to_next_tabstop(&mut self, styles: CharacterStyles) {
|
||||||
let mut next_tabstop = None;
|
let mut next_tabstop = None;
|
||||||
for tabstop in self.horizontal_tabstops.iter() {
|
for tabstop in self.horizontal_tabstops.iter() {
|
||||||
|
|
@ -1057,6 +1060,16 @@ impl Grid {
|
||||||
self.add_character_at_cursor_position(terminal_character);
|
self.add_character_at_cursor_position(terminal_character);
|
||||||
self.move_cursor_forward_until_edge(character_width);
|
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) {
|
pub fn move_cursor_forward_until_edge(&mut self, count: usize) {
|
||||||
let count_to_move = std::cmp::min(count, self.width - (self.cursor.x));
|
let count_to_move = std::cmp::min(count, self.width - (self.cursor.x));
|
||||||
self.cursor.x += count_to_move;
|
self.cursor.x += count_to_move;
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ use std::unimplemented;
|
||||||
use crate::panes::PaneId;
|
use crate::panes::PaneId;
|
||||||
use crate::pty::VteBytes;
|
use crate::pty::VteBytes;
|
||||||
use crate::tab::Pane;
|
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::wasm_vm::PluginInstruction;
|
||||||
|
use crate::ClientId;
|
||||||
use zellij_utils::pane_size::Offset;
|
use zellij_utils::pane_size::Offset;
|
||||||
use zellij_utils::position::Position;
|
use zellij_utils::position::Position;
|
||||||
use zellij_utils::shared::ansi_len;
|
use zellij_utils::shared::ansi_len;
|
||||||
|
|
@ -27,7 +28,6 @@ pub(crate) struct PluginPane {
|
||||||
pub active_at: Instant,
|
pub active_at: Instant,
|
||||||
pub pane_title: String,
|
pub pane_title: String,
|
||||||
frame: bool,
|
frame: bool,
|
||||||
frame_color: Option<PaletteColor>,
|
|
||||||
borderless: bool,
|
borderless: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,7 +47,6 @@ impl PluginPane {
|
||||||
send_plugin_instructions,
|
send_plugin_instructions,
|
||||||
active_at: Instant::now(),
|
active_at: Instant::now(),
|
||||||
frame: false,
|
frame: false,
|
||||||
frame_color: None,
|
|
||||||
content_offset: Offset::default(),
|
content_offset: Offset::default(),
|
||||||
pane_title: title,
|
pane_title: title,
|
||||||
borderless: false,
|
borderless: false,
|
||||||
|
|
@ -152,17 +151,6 @@ impl Pane for PluginPane {
|
||||||
|
|
||||||
self.should_render = false;
|
self.should_render = false;
|
||||||
let contents = buf_rx.recv().unwrap();
|
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() {
|
for (index, line) in contents.lines().enumerate() {
|
||||||
let actual_len = ansi_len(line);
|
let actual_len = ansi_len(line);
|
||||||
let line_to_print = if actual_len > self.get_content_columns() {
|
let line_to_print = if actual_len > self.get_content_columns() {
|
||||||
|
|
@ -212,6 +200,28 @@ impl Pane for PluginPane {
|
||||||
None
|
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 {
|
fn pid(&self) -> PaneId {
|
||||||
PaneId::Plugin(self.pid)
|
PaneId::Plugin(self.pid)
|
||||||
}
|
}
|
||||||
|
|
@ -317,10 +327,6 @@ impl Pane for PluginPane {
|
||||||
fn set_content_offset(&mut self, offset: Offset) {
|
fn set_content_offset(&mut self, offset: Offset) {
|
||||||
self.content_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) {
|
fn set_borderless(&mut self, borderless: bool) {
|
||||||
self.borderless = borderless;
|
self.borderless = borderless;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
|
use std::convert::From;
|
||||||
use std::fmt::{self, Debug, Display, Formatter};
|
use std::fmt::{self, Debug, Display, Formatter};
|
||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
use zellij_utils::vte::ParamsIter;
|
use zellij_utils::vte::ParamsIter;
|
||||||
|
|
||||||
use crate::panes::alacritty_functions::parse_sgr_color;
|
use crate::panes::alacritty_functions::parse_sgr_color;
|
||||||
|
use zellij_tile::data::PaletteColor;
|
||||||
|
|
||||||
pub const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter {
|
pub const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter {
|
||||||
character: ' ',
|
character: ' ',
|
||||||
|
|
@ -35,6 +37,15 @@ pub enum AnsiCode {
|
||||||
ColorIndex(u8),
|
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)]
|
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||||
pub enum NamedColor {
|
pub enum NamedColor {
|
||||||
Black,
|
Black,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ use crate::panes::{
|
||||||
};
|
};
|
||||||
use crate::pty::VteBytes;
|
use crate::pty::VteBytes;
|
||||||
use crate::tab::Pane;
|
use crate::tab::Pane;
|
||||||
|
use crate::ClientId;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use std::time::{self, Instant};
|
use std::time::{self, Instant};
|
||||||
|
|
@ -20,7 +22,7 @@ use zellij_utils::{
|
||||||
|
|
||||||
pub const SELECTION_SCROLL_INTERVAL_MS: u64 = 10;
|
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)]
|
#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)]
|
||||||
pub enum PaneId {
|
pub enum PaneId {
|
||||||
|
|
@ -42,9 +44,9 @@ pub struct TerminalPane {
|
||||||
selection_scrolled_at: time::Instant,
|
selection_scrolled_at: time::Instant,
|
||||||
content_offset: Offset,
|
content_offset: Offset,
|
||||||
pane_title: String,
|
pane_title: String,
|
||||||
frame: Option<PaneFrame>,
|
frame: HashMap<ClientId, PaneFrame>,
|
||||||
frame_color: Option<PaletteColor>,
|
|
||||||
borderless: bool,
|
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 {
|
impl Pane for TerminalPane {
|
||||||
|
|
@ -172,9 +174,7 @@ impl Pane for TerminalPane {
|
||||||
fn render_full_viewport(&mut self) {
|
fn render_full_viewport(&mut self) {
|
||||||
// this marks the pane for a full re-render, rather than just rendering the
|
// this marks the pane for a full re-render, rather than just rendering the
|
||||||
// diff as it usually does with the OutputBuffer
|
// diff as it usually does with the OutputBuffer
|
||||||
if self.frame.is_some() {
|
self.frame.clear();
|
||||||
self.frame.replace(PaneFrame::default());
|
|
||||||
}
|
|
||||||
self.grid.render_full_viewport();
|
self.grid.render_full_viewport();
|
||||||
}
|
}
|
||||||
fn selectable(&self) -> bool {
|
fn selectable(&self) -> bool {
|
||||||
|
|
@ -187,14 +187,14 @@ impl Pane for TerminalPane {
|
||||||
if self.should_render() {
|
if self.should_render() {
|
||||||
let mut vte_output = String::new();
|
let mut vte_output = String::new();
|
||||||
let mut character_styles = CharacterStyles::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 {
|
if self.grid.clear_viewport_before_rendering {
|
||||||
for line_index in 0..self.grid.height {
|
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!(
|
vte_output.push_str(&format!(
|
||||||
"\u{1b}[{};{}H\u{1b}[m",
|
"\u{1b}[{};{}H\u{1b}[m",
|
||||||
y + line_index + 1,
|
content_y + line_index + 1,
|
||||||
x + 1
|
content_x + 1
|
||||||
)); // goto row/col and reset styles
|
)); // goto row/col and reset styles
|
||||||
for _col_index in 0..self.grid.width {
|
for _col_index in 0..self.grid.width {
|
||||||
vte_output.push(EMPTY_TERMINAL_CHARACTER.character);
|
vte_output.push(EMPTY_TERMINAL_CHARACTER.character);
|
||||||
|
|
@ -202,6 +202,19 @@ impl Pane for TerminalPane {
|
||||||
}
|
}
|
||||||
self.grid.clear_viewport_before_rendering = false;
|
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;
|
||||||
|
vte_output.push_str(&format!("\u{1b}[{};{}H\u{1b}[m", y + 1, x + 1));
|
||||||
|
vte_output.push(EMPTY_TERMINAL_CHARACTER.character);
|
||||||
|
}
|
||||||
let max_width = self.get_content_columns();
|
let max_width = self.get_content_columns();
|
||||||
for character_chunk in self.grid.read_changes() {
|
for character_chunk in self.grid.read_changes() {
|
||||||
let pane_x = self.get_content_x();
|
let pane_x = self.get_content_x();
|
||||||
|
|
@ -244,30 +257,70 @@ impl Pane for TerminalPane {
|
||||||
}
|
}
|
||||||
character_styles.clear();
|
character_styles.clear();
|
||||||
}
|
}
|
||||||
if let Some(last_frame) = &self.frame {
|
|
||||||
let frame = PaneFrame {
|
|
||||||
geom: self.current_geom().into(),
|
|
||||||
title: self
|
|
||||||
.grid
|
|
||||||
.title
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| self.pane_title.clone()),
|
|
||||||
scroll_position: self.grid.scrollback_position_and_length(),
|
|
||||||
color: self.frame_color,
|
|
||||||
};
|
|
||||||
if &frame != last_frame {
|
|
||||||
if !self.borderless {
|
|
||||||
vte_output.push_str(&frame.render());
|
|
||||||
}
|
|
||||||
self.frame = Some(frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.set_should_render(false);
|
self.set_should_render(false);
|
||||||
Some(vte_output)
|
Some(vte_output)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn render_frame(&mut self, client_id: ClientId, frame_params: FrameParams) -> Option<String> {
|
||||||
|
// TODO: remove the cursor stuff from here
|
||||||
|
let mut vte_output = None;
|
||||||
|
let frame = PaneFrame::new(
|
||||||
|
self.current_geom().into(),
|
||||||
|
self.grid.scrollback_position_and_length(),
|
||||||
|
self.grid
|
||||||
|
.title
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| self.pane_title.clone()),
|
||||||
|
frame_params,
|
||||||
|
);
|
||||||
|
match self.frame.get(&client_id) {
|
||||||
|
// TODO: use and_then or something?
|
||||||
|
Some(last_frame) => {
|
||||||
|
if &frame != last_frame {
|
||||||
|
if !self.borderless {
|
||||||
|
vte_output = Some(frame.render());
|
||||||
|
}
|
||||||
|
self.frame.insert(client_id, frame);
|
||||||
|
}
|
||||||
|
vte_output
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if !self.borderless {
|
||||||
|
vte_output = Some(frame.render());
|
||||||
|
}
|
||||||
|
self.frame.insert(client_id, frame);
|
||||||
|
vte_output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn render_fake_cursor(
|
||||||
|
&mut self,
|
||||||
|
cursor_color: PaletteColor,
|
||||||
|
text_color: PaletteColor,
|
||||||
|
) -> Option<String> {
|
||||||
|
let mut vte_output = None;
|
||||||
|
if let Some((cursor_x, cursor_y)) = self.cursor_coordinates() {
|
||||||
|
let mut character_under_cursor = self
|
||||||
|
.grid
|
||||||
|
.get_character_under_cursor()
|
||||||
|
.unwrap_or(EMPTY_TERMINAL_CHARACTER);
|
||||||
|
character_under_cursor.styles.background = Some(cursor_color.into());
|
||||||
|
character_under_cursor.styles.foreground = Some(text_color.into());
|
||||||
|
// we keep track of these so that we can clear them up later (see render function)
|
||||||
|
self.fake_cursor_locations.insert((cursor_y, cursor_x));
|
||||||
|
let mut fake_cursor = format!(
|
||||||
|
"\u{1b}[{};{}H\u{1b}[m{}", // goto row column and clear styles
|
||||||
|
self.get_content_y() + cursor_y + 1, // + 1 because goto is 1 indexed
|
||||||
|
self.get_content_x() + cursor_x + 1,
|
||||||
|
&character_under_cursor.styles,
|
||||||
|
);
|
||||||
|
fake_cursor.push(character_under_cursor.character);
|
||||||
|
vte_output = Some(fake_cursor);
|
||||||
|
}
|
||||||
|
vte_output
|
||||||
|
}
|
||||||
fn pid(&self) -> PaneId {
|
fn pid(&self) -> PaneId {
|
||||||
PaneId::Terminal(self.pid)
|
PaneId::Terminal(self.pid)
|
||||||
}
|
}
|
||||||
|
|
@ -384,12 +437,8 @@ impl Pane for TerminalPane {
|
||||||
self.grid.get_selected_text()
|
self.grid.get_selected_text()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_frame(&mut self, frame: bool) {
|
fn set_frame(&mut self, _frame: bool) {
|
||||||
self.frame = if frame {
|
self.frame.clear();
|
||||||
Some(PaneFrame::default())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_content_offset(&mut self, offset: Offset) {
|
fn set_content_offset(&mut self, offset: Offset) {
|
||||||
|
|
@ -397,10 +446,6 @@ impl Pane for TerminalPane {
|
||||||
self.reflow_lines();
|
self.reflow_lines();
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
fn set_borderless(&mut self, borderless: bool) {
|
||||||
self.borderless = borderless;
|
self.borderless = borderless;
|
||||||
}
|
}
|
||||||
|
|
@ -423,8 +468,7 @@ impl TerminalPane {
|
||||||
palette,
|
palette,
|
||||||
);
|
);
|
||||||
TerminalPane {
|
TerminalPane {
|
||||||
frame: None,
|
frame: HashMap::new(),
|
||||||
frame_color: None,
|
|
||||||
content_offset: Offset::default(),
|
content_offset: Offset::default(),
|
||||||
pid,
|
pid,
|
||||||
grid,
|
grid,
|
||||||
|
|
@ -437,6 +481,7 @@ impl TerminalPane {
|
||||||
selection_scrolled_at: time::Instant::now(),
|
selection_scrolled_at: time::Instant::now(),
|
||||||
pane_title: initial_pane_title,
|
pane_title: initial_pane_title,
|
||||||
borderless: false,
|
borderless: false,
|
||||||
|
fake_cursor_locations: HashSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_x(&self) -> usize {
|
pub fn get_x(&self) -> usize {
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,16 @@
|
||||||
|
|
||||||
use zellij_utils::{position::Position, serde, zellij_tile};
|
use zellij_utils::{position::Position, serde, zellij_tile};
|
||||||
|
|
||||||
|
use crate::ui::pane_boundaries_frame::FrameParams;
|
||||||
use crate::ui::pane_resizer::PaneResizer;
|
use crate::ui::pane_resizer::PaneResizer;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
os_input_output::ServerOsApi,
|
os_input_output::ServerOsApi,
|
||||||
panes::{PaneId, PluginPane, TerminalPane},
|
panes::{PaneId, PluginPane, TerminalPane},
|
||||||
pty::{PtyInstruction, VteBytes},
|
pty::{PtyInstruction, VteBytes},
|
||||||
thread_bus::ThreadSenders,
|
thread_bus::ThreadSenders,
|
||||||
ui::boundaries::Boundaries,
|
ui::boundaries::Boundaries,
|
||||||
|
ui::pane_contents_and_ui::PaneContentsAndUi,
|
||||||
wasm_vm::PluginInstruction,
|
wasm_vm::PluginInstruction,
|
||||||
ClientId, ServerInstruction,
|
ClientId, ServerInstruction,
|
||||||
};
|
};
|
||||||
|
|
@ -21,7 +24,7 @@ use std::{
|
||||||
cmp::Reverse,
|
cmp::Reverse,
|
||||||
collections::{BTreeMap, HashMap, HashSet},
|
collections::{BTreeMap, HashMap, HashSet},
|
||||||
};
|
};
|
||||||
use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, PaletteColor};
|
use zellij_tile::data::{Event, ModeInfo, Palette, PaletteColor};
|
||||||
use zellij_utils::{
|
use zellij_utils::{
|
||||||
input::{
|
input::{
|
||||||
layout::{Direction, Layout, Run},
|
layout::{Direction, Layout, Run},
|
||||||
|
|
@ -112,6 +115,11 @@ impl Output {
|
||||||
render_instruction.push_str(to_push)
|
render_instruction.push_str(to_push)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn push_to_client(&mut self, client_id: ClientId, to_push: &str) {
|
||||||
|
if let Some(render_instructions) = self.client_render_instructions.get_mut(&client_id) {
|
||||||
|
render_instructions.push_str(to_push);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Tab {
|
pub(crate) struct Tab {
|
||||||
|
|
@ -133,6 +141,7 @@ pub(crate) struct Tab {
|
||||||
pub colors: Palette,
|
pub colors: Palette,
|
||||||
connected_clients: HashSet<ClientId>,
|
connected_clients: HashSet<ClientId>,
|
||||||
draw_pane_frames: bool,
|
draw_pane_frames: bool,
|
||||||
|
session_is_mirrored: bool,
|
||||||
pending_vte_events: HashMap<RawFd, Vec<VteBytes>>,
|
pending_vte_events: HashMap<RawFd, Vec<VteBytes>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,6 +180,12 @@ pub trait Pane {
|
||||||
fn selectable(&self) -> bool;
|
fn selectable(&self) -> bool;
|
||||||
fn set_selectable(&mut self, selectable: bool);
|
fn set_selectable(&mut self, selectable: bool);
|
||||||
fn render(&mut self) -> Option<String>;
|
fn render(&mut self) -> Option<String>;
|
||||||
|
fn render_frame(&mut self, client_id: ClientId, frame_params: FrameParams) -> Option<String>;
|
||||||
|
fn render_fake_cursor(
|
||||||
|
&mut self,
|
||||||
|
cursor_color: PaletteColor,
|
||||||
|
text_color: PaletteColor,
|
||||||
|
) -> Option<String>;
|
||||||
fn pid(&self) -> PaneId;
|
fn pid(&self) -> PaneId;
|
||||||
fn reduce_height(&mut self, percent: f64);
|
fn reduce_height(&mut self, percent: f64);
|
||||||
fn increase_height(&mut self, percent: f64);
|
fn increase_height(&mut self, percent: f64);
|
||||||
|
|
@ -266,7 +281,6 @@ pub trait Pane {
|
||||||
fn relative_position(&self, position_on_screen: &Position) -> Position {
|
fn relative_position(&self, position_on_screen: &Position) -> Position {
|
||||||
position_on_screen.relative_to(self.get_content_y(), self.get_content_x())
|
position_on_screen.relative_to(self.get_content_y(), self.get_content_x())
|
||||||
}
|
}
|
||||||
fn set_boundary_color(&mut self, _color: Option<PaletteColor>) {}
|
|
||||||
fn set_borderless(&mut self, borderless: bool);
|
fn set_borderless(&mut self, borderless: bool);
|
||||||
fn borderless(&self) -> bool;
|
fn borderless(&self) -> bool;
|
||||||
fn handle_right_click(&mut self, _to: &Position) {}
|
fn handle_right_click(&mut self, _to: &Position) {}
|
||||||
|
|
@ -331,6 +345,9 @@ impl Tab {
|
||||||
mode_info,
|
mode_info,
|
||||||
colors,
|
colors,
|
||||||
draw_pane_frames,
|
draw_pane_frames,
|
||||||
|
// at the moment this is hard-coded while the feature is being developed
|
||||||
|
// the only effect this has is to make sure the UI is drawn without additional information about other connected clients
|
||||||
|
session_is_mirrored: true,
|
||||||
pending_vte_events: HashMap::new(),
|
pending_vte_events: HashMap::new(),
|
||||||
connected_clients,
|
connected_clients,
|
||||||
}
|
}
|
||||||
|
|
@ -478,6 +495,8 @@ impl Tab {
|
||||||
}
|
}
|
||||||
pub fn remove_client(&mut self, client_id: ClientId) {
|
pub fn remove_client(&mut self, client_id: ClientId) {
|
||||||
self.connected_clients.remove(&client_id);
|
self.connected_clients.remove(&client_id);
|
||||||
|
self.active_panes.remove(&client_id);
|
||||||
|
self.set_force_render();
|
||||||
}
|
}
|
||||||
pub fn drain_connected_clients(&mut self) -> Vec<ClientId> {
|
pub fn drain_connected_clients(&mut self) -> Vec<ClientId> {
|
||||||
self.connected_clients.drain().collect()
|
self.connected_clients.drain().collect()
|
||||||
|
|
@ -552,11 +571,15 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if client_id.is_some() {
|
if let Some(client_id) = client_id {
|
||||||
// right now we administratively change focus of all clients until the
|
if self.session_is_mirrored {
|
||||||
// mirroring/multiplayer situation is sorted out
|
// move all clients
|
||||||
let connected_clients: Vec<ClientId> = self.connected_clients.iter().copied().collect();
|
let connected_clients: Vec<ClientId> =
|
||||||
for client_id in connected_clients {
|
self.connected_clients.iter().copied().collect();
|
||||||
|
for client_id in connected_clients {
|
||||||
|
self.active_panes.insert(client_id, pid);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
self.active_panes.insert(client_id, pid);
|
self.active_panes.insert(client_id, pid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -588,11 +611,14 @@ impl Tab {
|
||||||
active_pane.set_geom(top_winsize);
|
active_pane.set_geom(top_winsize);
|
||||||
self.panes.insert(pid, Box::new(new_terminal));
|
self.panes.insert(pid, Box::new(new_terminal));
|
||||||
|
|
||||||
// right now we administratively change focus of all clients until the
|
if self.session_is_mirrored {
|
||||||
// mirroring/multiplayer situation is sorted out
|
// move all clients
|
||||||
let connected_clients: Vec<ClientId> =
|
let connected_clients: Vec<ClientId> =
|
||||||
self.connected_clients.iter().copied().collect();
|
self.connected_clients.iter().copied().collect();
|
||||||
for client_id in connected_clients {
|
for client_id in connected_clients {
|
||||||
|
self.active_panes.insert(client_id, pid);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
self.active_panes.insert(client_id, pid);
|
self.active_panes.insert(client_id, pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -623,11 +649,14 @@ impl Tab {
|
||||||
active_pane.set_geom(left_winsize);
|
active_pane.set_geom(left_winsize);
|
||||||
self.panes.insert(pid, Box::new(new_terminal));
|
self.panes.insert(pid, Box::new(new_terminal));
|
||||||
}
|
}
|
||||||
|
if self.session_is_mirrored {
|
||||||
// right now we administratively change focus of all clients until the
|
// move all clients
|
||||||
// mirroring/multiplayer situation is sorted out
|
let connected_clients: Vec<ClientId> =
|
||||||
let connected_clients: Vec<ClientId> = self.connected_clients.iter().copied().collect();
|
self.connected_clients.iter().copied().collect();
|
||||||
for client_id in connected_clients {
|
for client_id in connected_clients {
|
||||||
|
self.active_panes.insert(client_id, pid);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
self.active_panes.insert(client_id, pid);
|
self.active_panes.insert(client_id, pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -805,6 +834,10 @@ impl Tab {
|
||||||
};
|
};
|
||||||
active_terminal.get_geom_override(full_screen_geom);
|
active_terminal.get_geom_override(full_screen_geom);
|
||||||
}
|
}
|
||||||
|
let active_panes: Vec<ClientId> = self.active_panes.keys().copied().collect();
|
||||||
|
for client_id in active_panes {
|
||||||
|
self.active_panes.insert(client_id, active_pane_id);
|
||||||
|
}
|
||||||
self.set_force_render();
|
self.set_force_render();
|
||||||
self.resize_whole_tab(self.display_area);
|
self.resize_whole_tab(self.display_area);
|
||||||
self.toggle_fullscreen_is_active();
|
self.toggle_fullscreen_is_active();
|
||||||
|
|
@ -872,12 +905,10 @@ impl Tab {
|
||||||
resize_pty!(pane, self.os_api);
|
resize_pty!(pane, self.os_api);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn render(&mut self, output: &mut Output, overlay: Option<String>) {
|
fn update_active_panes_in_pty_thread(&self) {
|
||||||
if self.connected_clients.is_empty() || self.active_panes.is_empty() {
|
// this is a bit hacky and we should ideally not keep this state in two different places at
|
||||||
return;
|
// some point
|
||||||
}
|
|
||||||
for connected_client in self.connected_clients.iter() {
|
for connected_client in self.connected_clients.iter() {
|
||||||
// TODO: move this out of the render function
|
|
||||||
self.senders
|
self.senders
|
||||||
.send_to_pty(PtyInstruction::UpdateActivePane(
|
.send_to_pty(PtyInstruction::UpdateActivePane(
|
||||||
self.active_panes.get(connected_client).copied(),
|
self.active_panes.get(connected_client).copied(),
|
||||||
|
|
@ -885,8 +916,57 @@ impl Tab {
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
pub fn render(&mut self, output: &mut Output, overlay: Option<String>) {
|
||||||
|
if self.connected_clients.is_empty() || self.active_panes.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.update_active_panes_in_pty_thread();
|
||||||
output.add_clients(&self.connected_clients);
|
output.add_clients(&self.connected_clients);
|
||||||
let mut boundaries = Boundaries::new(self.viewport);
|
let mut client_id_to_boundaries: HashMap<ClientId, Boundaries> = HashMap::new();
|
||||||
|
self.hide_cursor_and_clear_display_as_needed(output);
|
||||||
|
// render panes and their frames
|
||||||
|
for pane in self.panes.values_mut() {
|
||||||
|
if !self.panes_to_hide.contains(&pane.pid()) {
|
||||||
|
let mut pane_contents_and_ui = PaneContentsAndUi::new(
|
||||||
|
pane,
|
||||||
|
output,
|
||||||
|
self.colors,
|
||||||
|
&self.active_panes,
|
||||||
|
self.mode_info.mode,
|
||||||
|
);
|
||||||
|
pane_contents_and_ui.render_pane_contents_for_all_clients();
|
||||||
|
for client_id in self.connected_clients.iter() {
|
||||||
|
if self.draw_pane_frames {
|
||||||
|
pane_contents_and_ui
|
||||||
|
.render_pane_frame(*client_id, self.session_is_mirrored);
|
||||||
|
} else {
|
||||||
|
let mut boundaries = client_id_to_boundaries
|
||||||
|
.entry(*client_id)
|
||||||
|
.or_insert_with(|| Boundaries::new(self.viewport));
|
||||||
|
pane_contents_and_ui.render_pane_boundaries(
|
||||||
|
*client_id,
|
||||||
|
&mut boundaries,
|
||||||
|
self.session_is_mirrored,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// this is done for panes that don't have their own cursor (eg. panes of
|
||||||
|
// another user)
|
||||||
|
pane_contents_and_ui.render_fake_cursor_if_needed(*client_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// render boundaries if needed
|
||||||
|
for (client_id, boundaries) in client_id_to_boundaries.iter_mut() {
|
||||||
|
output.push_to_client(*client_id, &boundaries.vte_output());
|
||||||
|
}
|
||||||
|
// FIXME: Once clients can be distinguished
|
||||||
|
if let Some(overlay_vte) = &overlay {
|
||||||
|
output.push_str_to_all_clients(overlay_vte);
|
||||||
|
}
|
||||||
|
self.render_cursor(output);
|
||||||
|
}
|
||||||
|
fn hide_cursor_and_clear_display_as_needed(&mut self, output: &mut Output) {
|
||||||
let hide_cursor = "\u{1b}[?25l";
|
let hide_cursor = "\u{1b}[?25l";
|
||||||
output.push_str_to_all_clients(hide_cursor);
|
output.push_str_to_all_clients(hide_cursor);
|
||||||
if self.should_clear_display_before_rendering {
|
if self.should_clear_display_before_rendering {
|
||||||
|
|
@ -894,76 +974,27 @@ impl Tab {
|
||||||
output.push_str_to_all_clients(clear_display);
|
output.push_str_to_all_clients(clear_display);
|
||||||
self.should_clear_display_before_rendering = false;
|
self.should_clear_display_before_rendering = false;
|
||||||
}
|
}
|
||||||
let first_client_id = self.connected_clients.iter().next().unwrap(); // this is a temporary hack until we fix the ui for multiple clients
|
}
|
||||||
for (_kind, pane) in self.panes.iter_mut() {
|
fn render_cursor(&self, output: &mut Output) {
|
||||||
if !self.panes_to_hide.contains(&pane.pid()) {
|
for client_id in self.connected_clients.iter() {
|
||||||
match self.active_panes.get(first_client_id).copied().unwrap() == pane.pid() {
|
match self.get_active_terminal_cursor_position(*client_id) {
|
||||||
true => {
|
Some((cursor_position_x, cursor_position_y)) => {
|
||||||
pane.set_active_at(Instant::now());
|
let show_cursor = "\u{1b}[?25h";
|
||||||
match self.mode_info.mode {
|
let change_cursor_shape =
|
||||||
InputMode::Normal | InputMode::Locked => {
|
self.get_active_pane(*client_id).unwrap().cursor_shape_csi();
|
||||||
pane.set_boundary_color(Some(self.colors.green));
|
let goto_cursor_position = &format!(
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
pane.set_boundary_color(Some(self.colors.orange));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !self.draw_pane_frames {
|
|
||||||
boundaries.add_rect(
|
|
||||||
pane.as_ref(),
|
|
||||||
self.mode_info.mode,
|
|
||||||
Some(self.colors),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false => {
|
|
||||||
pane.set_boundary_color(None);
|
|
||||||
if !self.draw_pane_frames {
|
|
||||||
boundaries.add_rect(pane.as_ref(), self.mode_info.mode, None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Once clients can be distinguished
|
|
||||||
if let Some(overlay_vte) = &overlay {
|
|
||||||
output.push_str_to_all_clients(overlay_vte);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(vte_output) = pane.render() {
|
|
||||||
// FIXME: Use Termion for cursor and style clearing?
|
|
||||||
output.push_str_to_all_clients(&format!(
|
|
||||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||||
pane.y() + 1,
|
cursor_position_y + 1,
|
||||||
pane.x() + 1,
|
cursor_position_x + 1,
|
||||||
vte_output
|
change_cursor_shape
|
||||||
));
|
); // goto row/col
|
||||||
|
output.push_to_client(*client_id, show_cursor);
|
||||||
|
output.push_to_client(*client_id, goto_cursor_position);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let hide_cursor = "\u{1b}[?25l";
|
||||||
|
output.push_to_client(*client_id, hide_cursor);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.draw_pane_frames {
|
|
||||||
output.push_str_to_all_clients(&boundaries.vte_output());
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.get_active_terminal_cursor_position(*first_client_id) {
|
|
||||||
Some((cursor_position_x, cursor_position_y)) => {
|
|
||||||
let show_cursor = "\u{1b}[?25h";
|
|
||||||
let change_cursor_shape = self
|
|
||||||
.get_active_pane(*first_client_id)
|
|
||||||
.unwrap()
|
|
||||||
.cursor_shape_csi();
|
|
||||||
let goto_cursor_position = &format!(
|
|
||||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
|
||||||
cursor_position_y + 1,
|
|
||||||
cursor_position_x + 1,
|
|
||||||
change_cursor_shape
|
|
||||||
); // goto row/col
|
|
||||||
output.push_str_to_all_clients(show_cursor);
|
|
||||||
output.push_str_to_all_clients(goto_cursor_position);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let hide_cursor = "\u{1b}[?25l";
|
|
||||||
output.push_str_to_all_clients(hide_cursor);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2396,15 +2427,29 @@ impl Tab {
|
||||||
.panes
|
.panes
|
||||||
.get_mut(self.active_panes.get(&client_id).unwrap())
|
.get_mut(self.active_panes.get(&client_id).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
previously_active_pane.set_should_render(true);
|
previously_active_pane.set_should_render(true);
|
||||||
|
// we render the full viewport to remove any ui elements that might have been
|
||||||
|
// there before (eg. another user's cursor)
|
||||||
|
previously_active_pane.render_full_viewport();
|
||||||
|
|
||||||
let next_active_pane = self.panes.get_mut(&p).unwrap();
|
let next_active_pane = self.panes.get_mut(&p).unwrap();
|
||||||
next_active_pane.set_should_render(true);
|
next_active_pane.set_should_render(true);
|
||||||
|
// we render the full viewport to remove any ui elements that might have been
|
||||||
|
// there before (eg. another user's cursor)
|
||||||
|
next_active_pane.render_full_viewport();
|
||||||
|
|
||||||
let connected_clients: Vec<ClientId> =
|
if self.session_is_mirrored {
|
||||||
self.connected_clients.iter().copied().collect();
|
// move all clients
|
||||||
for client_id in connected_clients {
|
let connected_clients: Vec<ClientId> =
|
||||||
|
self.connected_clients.iter().copied().collect();
|
||||||
|
for client_id in connected_clients {
|
||||||
|
self.active_panes.insert(client_id, p);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
self.active_panes.insert(client_id, p);
|
self.active_panes.insert(client_id, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
None => Some(active.pid()),
|
None => Some(active.pid()),
|
||||||
|
|
@ -2454,8 +2499,14 @@ impl Tab {
|
||||||
.get_mut(self.active_panes.get(&client_id).unwrap())
|
.get_mut(self.active_panes.get(&client_id).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
previously_active_pane.set_should_render(true);
|
previously_active_pane.set_should_render(true);
|
||||||
|
// we render the full viewport to remove any ui elements that might have been
|
||||||
|
// there before (eg. another user's cursor)
|
||||||
|
previously_active_pane.render_full_viewport();
|
||||||
let next_active_pane = self.panes.get_mut(&p).unwrap();
|
let next_active_pane = self.panes.get_mut(&p).unwrap();
|
||||||
next_active_pane.set_should_render(true);
|
next_active_pane.set_should_render(true);
|
||||||
|
// we render the full viewport to remove any ui elements that might have been
|
||||||
|
// there before (eg. another user's cursor)
|
||||||
|
next_active_pane.render_full_viewport();
|
||||||
|
|
||||||
Some(p)
|
Some(p)
|
||||||
}
|
}
|
||||||
|
|
@ -2466,9 +2517,14 @@ impl Tab {
|
||||||
};
|
};
|
||||||
match updated_active_pane {
|
match updated_active_pane {
|
||||||
Some(updated_active_pane) => {
|
Some(updated_active_pane) => {
|
||||||
let connected_clients: Vec<ClientId> =
|
if self.session_is_mirrored {
|
||||||
self.connected_clients.iter().copied().collect();
|
// move all clients
|
||||||
for client_id in connected_clients {
|
let connected_clients: Vec<ClientId> =
|
||||||
|
self.connected_clients.iter().copied().collect();
|
||||||
|
for client_id in connected_clients {
|
||||||
|
self.active_panes.insert(client_id, updated_active_pane);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
self.active_panes.insert(client_id, updated_active_pane);
|
self.active_panes.insert(client_id, updated_active_pane);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2504,8 +2560,14 @@ impl Tab {
|
||||||
.get_mut(self.active_panes.get(&client_id).unwrap())
|
.get_mut(self.active_panes.get(&client_id).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
previously_active_pane.set_should_render(true);
|
previously_active_pane.set_should_render(true);
|
||||||
|
// we render the full viewport to remove any ui elements that might have been
|
||||||
|
// there before (eg. another user's cursor)
|
||||||
|
previously_active_pane.render_full_viewport();
|
||||||
let next_active_pane = self.panes.get_mut(&p).unwrap();
|
let next_active_pane = self.panes.get_mut(&p).unwrap();
|
||||||
next_active_pane.set_should_render(true);
|
next_active_pane.set_should_render(true);
|
||||||
|
// we render the full viewport to remove any ui elements that might have been
|
||||||
|
// there before (eg. another user's cursor)
|
||||||
|
next_active_pane.render_full_viewport();
|
||||||
|
|
||||||
Some(p)
|
Some(p)
|
||||||
}
|
}
|
||||||
|
|
@ -2516,9 +2578,14 @@ impl Tab {
|
||||||
};
|
};
|
||||||
match updated_active_pane {
|
match updated_active_pane {
|
||||||
Some(updated_active_pane) => {
|
Some(updated_active_pane) => {
|
||||||
let connected_clients: Vec<ClientId> =
|
if self.session_is_mirrored {
|
||||||
self.connected_clients.iter().copied().collect();
|
// move all clients
|
||||||
for client_id in connected_clients {
|
let connected_clients: Vec<ClientId> =
|
||||||
|
self.connected_clients.iter().copied().collect();
|
||||||
|
for client_id in connected_clients {
|
||||||
|
self.active_panes.insert(client_id, updated_active_pane);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
self.active_panes.insert(client_id, updated_active_pane);
|
self.active_panes.insert(client_id, updated_active_pane);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2555,12 +2622,23 @@ impl Tab {
|
||||||
.get_mut(self.active_panes.get(&client_id).unwrap())
|
.get_mut(self.active_panes.get(&client_id).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
previously_active_pane.set_should_render(true);
|
previously_active_pane.set_should_render(true);
|
||||||
|
// we render the full viewport to remove any ui elements that might have been
|
||||||
|
// there before (eg. another user's cursor)
|
||||||
|
previously_active_pane.render_full_viewport();
|
||||||
let next_active_pane = self.panes.get_mut(&p).unwrap();
|
let next_active_pane = self.panes.get_mut(&p).unwrap();
|
||||||
next_active_pane.set_should_render(true);
|
next_active_pane.set_should_render(true);
|
||||||
|
// we render the full viewport to remove any ui elements that might have been
|
||||||
|
// there before (eg. another user's cursor)
|
||||||
|
next_active_pane.render_full_viewport();
|
||||||
|
|
||||||
let connected_clients: Vec<ClientId> =
|
if self.session_is_mirrored {
|
||||||
self.connected_clients.iter().copied().collect();
|
// move all clients
|
||||||
for client_id in connected_clients {
|
let connected_clients: Vec<ClientId> =
|
||||||
|
self.connected_clients.iter().copied().collect();
|
||||||
|
for client_id in connected_clients {
|
||||||
|
self.active_panes.insert(client_id, p);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
self.active_panes.insert(client_id, p);
|
self.active_panes.insert(client_id, p);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -2572,9 +2650,14 @@ impl Tab {
|
||||||
};
|
};
|
||||||
match updated_active_pane {
|
match updated_active_pane {
|
||||||
Some(updated_active_pane) => {
|
Some(updated_active_pane) => {
|
||||||
let connected_clients: Vec<ClientId> =
|
if self.session_is_mirrored {
|
||||||
self.connected_clients.iter().copied().collect();
|
// move all clients
|
||||||
for client_id in connected_clients {
|
let connected_clients: Vec<ClientId> =
|
||||||
|
self.connected_clients.iter().copied().collect();
|
||||||
|
for client_id in connected_clients {
|
||||||
|
self.active_panes.insert(client_id, updated_active_pane);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
self.active_panes.insert(client_id, updated_active_pane);
|
self.active_panes.insert(client_id, updated_active_pane);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3179,7 +3262,7 @@ impl Tab {
|
||||||
|
|
||||||
fn get_pane_id_at(&self, point: &Position, search_selectable: bool) -> Option<PaneId> {
|
fn get_pane_id_at(&self, point: &Position, search_selectable: bool) -> Option<PaneId> {
|
||||||
if self.fullscreen_is_active {
|
if self.fullscreen_is_active {
|
||||||
let first_client_id = self.connected_clients.iter().next().unwrap(); // this is a temporary hack until we fix the ui for multiple clients
|
let first_client_id = self.connected_clients.iter().next().unwrap(); // TODO: instead of doing this, record the pane that is in fullscreen
|
||||||
return self.get_active_pane_id(*first_client_id);
|
return self.get_active_pane_id(*first_client_id);
|
||||||
}
|
}
|
||||||
if search_selectable {
|
if search_selectable {
|
||||||
|
|
@ -3208,10 +3291,16 @@ impl Tab {
|
||||||
pane.handle_right_click(&relative_position);
|
pane.handle_right_click(&relative_position);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
fn focus_pane_at(&mut self, point: &Position, _client_id: ClientId) {
|
fn focus_pane_at(&mut self, point: &Position, client_id: ClientId) {
|
||||||
if let Some(clicked_pane) = self.get_pane_id_at(point, true) {
|
if let Some(clicked_pane) = self.get_pane_id_at(point, true) {
|
||||||
let connected_clients: Vec<ClientId> = self.connected_clients.iter().copied().collect();
|
if self.session_is_mirrored {
|
||||||
for client_id in connected_clients {
|
// move all clients
|
||||||
|
let connected_clients: Vec<ClientId> =
|
||||||
|
self.connected_clients.iter().copied().collect();
|
||||||
|
for client_id in connected_clients {
|
||||||
|
self.active_panes.insert(client_id, clicked_pane);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
self.active_panes.insert(client_id, clicked_pane);
|
self.active_panes.insert(client_id, clicked_pane);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use zellij_utils::{pane_size::Viewport, zellij_tile};
|
||||||
use crate::tab::Pane;
|
use crate::tab::Pane;
|
||||||
use ansi_term::Colour::{Fixed, RGB};
|
use ansi_term::Colour::{Fixed, RGB};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use zellij_tile::data::{InputMode, Palette, PaletteColor};
|
use zellij_tile::data::PaletteColor;
|
||||||
use zellij_utils::shared::colors;
|
use zellij_utils::shared::colors;
|
||||||
|
|
||||||
use std::fmt::{Display, Error, Formatter};
|
use std::fmt::{Display, Error, Formatter};
|
||||||
|
|
@ -413,17 +413,10 @@ impl Boundaries {
|
||||||
boundary_characters: HashMap::new(),
|
boundary_characters: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn add_rect(&mut self, rect: &dyn Pane, input_mode: InputMode, palette: Option<Palette>) {
|
pub fn add_rect(&mut self, rect: &dyn Pane, color: Option<PaletteColor>) {
|
||||||
if !self.is_fully_inside_screen(rect) {
|
if !self.is_fully_inside_screen(rect) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let color = match palette.is_some() {
|
|
||||||
true => match input_mode {
|
|
||||||
InputMode::Normal | InputMode::Locked => Some(palette.unwrap().green),
|
|
||||||
_ => Some(palette.unwrap().orange),
|
|
||||||
},
|
|
||||||
false => None,
|
|
||||||
};
|
|
||||||
if rect.x() > self.viewport.x {
|
if rect.x() > self.viewport.x {
|
||||||
// left boundary
|
// left boundary
|
||||||
let boundary_x_coords = rect.x() - 1;
|
let boundary_x_coords = rect.x() - 1;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod boundaries;
|
pub mod boundaries;
|
||||||
pub mod overlay;
|
pub mod overlay;
|
||||||
pub mod pane_boundaries_frame;
|
pub mod pane_boundaries_frame;
|
||||||
|
pub mod pane_contents_and_ui;
|
||||||
pub mod pane_resizer;
|
pub mod pane_resizer;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use crate::ui::boundaries::boundary_type;
|
use crate::ui::boundaries::boundary_type;
|
||||||
|
use crate::ClientId;
|
||||||
use ansi_term::Colour::{Fixed, RGB};
|
use ansi_term::Colour::{Fixed, RGB};
|
||||||
use ansi_term::Style;
|
use ansi_term::Style;
|
||||||
use zellij_utils::pane_size::Viewport;
|
use zellij_utils::pane_size::Viewport;
|
||||||
use zellij_utils::zellij_tile::prelude::PaletteColor;
|
use zellij_utils::zellij_tile::prelude::{Palette, PaletteColor};
|
||||||
|
|
||||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||||
|
|
||||||
|
|
@ -20,27 +21,104 @@ fn color_string(character: &str, color: Option<PaletteColor>) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn background_color(character: &str, color: Option<PaletteColor>) -> String {
|
||||||
|
match color {
|
||||||
|
Some(PaletteColor::Rgb((r, g, b))) => {
|
||||||
|
format!("{}", Style::new().on(RGB(r, g, b)).paint(character))
|
||||||
|
}
|
||||||
|
Some(PaletteColor::EightBit(color)) => {
|
||||||
|
format!("{}", Style::new().on(Fixed(color)).paint(character))
|
||||||
|
}
|
||||||
|
None => String::from(character),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: move elsewhere
|
||||||
|
pub(crate) fn client_id_to_colors(
|
||||||
|
client_id: ClientId,
|
||||||
|
colors: Palette,
|
||||||
|
) -> Option<(PaletteColor, PaletteColor)> {
|
||||||
|
// (primary color, secondary color)
|
||||||
|
match client_id {
|
||||||
|
1 => Some((colors.green, colors.black)),
|
||||||
|
2 => Some((colors.blue, colors.black)),
|
||||||
|
3 => Some((colors.cyan, colors.black)),
|
||||||
|
4 => Some((colors.magenta, colors.black)),
|
||||||
|
5 => Some((colors.yellow, colors.black)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FrameParams {
|
||||||
|
pub focused_client: Option<ClientId>,
|
||||||
|
pub is_main_client: bool,
|
||||||
|
pub other_focused_clients: Vec<ClientId>,
|
||||||
|
pub colors: Palette,
|
||||||
|
pub color: Option<PaletteColor>,
|
||||||
|
pub other_cursors_exist_in_session: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq)]
|
#[derive(Default, PartialEq)]
|
||||||
pub struct PaneFrame {
|
pub struct PaneFrame {
|
||||||
pub geom: Viewport,
|
pub geom: Viewport,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub scroll_position: (usize, usize), // (position, length)
|
pub scroll_position: (usize, usize), // (position, length)
|
||||||
|
pub colors: Palette,
|
||||||
pub color: Option<PaletteColor>,
|
pub color: Option<PaletteColor>,
|
||||||
|
pub focused_client: Option<ClientId>,
|
||||||
|
pub is_main_client: bool,
|
||||||
|
pub other_cursors_exist_in_session: bool,
|
||||||
|
pub other_focused_clients: Vec<ClientId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PaneFrame {
|
impl PaneFrame {
|
||||||
fn render_title_right_side(&self, max_length: usize) -> Option<String> {
|
pub fn new(
|
||||||
|
geom: Viewport,
|
||||||
|
scroll_position: (usize, usize),
|
||||||
|
main_title: String,
|
||||||
|
frame_params: FrameParams,
|
||||||
|
) -> Self {
|
||||||
|
PaneFrame {
|
||||||
|
geom,
|
||||||
|
title: main_title,
|
||||||
|
scroll_position,
|
||||||
|
colors: frame_params.colors,
|
||||||
|
color: frame_params.color,
|
||||||
|
focused_client: frame_params.focused_client,
|
||||||
|
is_main_client: frame_params.is_main_client,
|
||||||
|
other_focused_clients: frame_params.other_focused_clients,
|
||||||
|
other_cursors_exist_in_session: frame_params.other_cursors_exist_in_session,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn client_cursor(&self, client_id: ClientId) -> String {
|
||||||
|
let color = client_id_to_colors(client_id, self.colors);
|
||||||
|
background_color(" ", color.map(|c| c.0))
|
||||||
|
}
|
||||||
|
fn render_title_right_side(&self, max_length: usize) -> Option<(String, usize)> {
|
||||||
|
// string and length because of color
|
||||||
if self.scroll_position.0 > 0 || self.scroll_position.1 > 0 {
|
if self.scroll_position.0 > 0 || self.scroll_position.1 > 0 {
|
||||||
let prefix = " SCROLL: ";
|
let prefix = " SCROLL: ";
|
||||||
let full_indication =
|
let full_indication =
|
||||||
format!(" {}/{} ", self.scroll_position.0, self.scroll_position.1);
|
format!(" {}/{} ", self.scroll_position.0, self.scroll_position.1);
|
||||||
let short_indication = format!(" {} ", self.scroll_position.0);
|
let short_indication = format!(" {} ", self.scroll_position.0);
|
||||||
if prefix.width() + full_indication.width() <= max_length {
|
let full_indication_len = full_indication.chars().count();
|
||||||
Some(format!("{}{}", prefix, full_indication))
|
let short_indication_len = short_indication.chars().count();
|
||||||
} else if full_indication.width() <= max_length {
|
let prefix_len = prefix.chars().count();
|
||||||
Some(full_indication)
|
if prefix_len + full_indication_len <= max_length {
|
||||||
} else if short_indication.width() <= max_length {
|
Some((
|
||||||
Some(short_indication)
|
color_string(&format!("{}{}", prefix, full_indication), self.color),
|
||||||
|
prefix_len + full_indication_len,
|
||||||
|
))
|
||||||
|
} else if full_indication_len <= max_length {
|
||||||
|
Some((
|
||||||
|
color_string(&full_indication, self.color),
|
||||||
|
full_indication_len,
|
||||||
|
))
|
||||||
|
} else if short_indication_len <= max_length {
|
||||||
|
Some((
|
||||||
|
color_string(&short_indication, self.color),
|
||||||
|
short_indication_len,
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -48,14 +126,148 @@ impl PaneFrame {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn render_title_left_side(&self, max_length: usize) -> Option<String> {
|
fn render_my_focus(&self, max_length: usize) -> Option<(String, usize)> {
|
||||||
|
let left_separator = color_string(boundary_type::VERTICAL_LEFT, self.color);
|
||||||
|
let right_separator = color_string(boundary_type::VERTICAL_RIGHT, self.color);
|
||||||
|
let full_indication_text = "MY FOCUS";
|
||||||
|
let full_indication = format!(
|
||||||
|
"{} {} {}",
|
||||||
|
left_separator,
|
||||||
|
color_string(full_indication_text, self.color),
|
||||||
|
right_separator
|
||||||
|
);
|
||||||
|
let full_indication_len = full_indication_text.width() + 4; // 2 for separators 2 for padding
|
||||||
|
let short_indication_text = "ME";
|
||||||
|
let short_indication = format!(
|
||||||
|
"{} {} {}",
|
||||||
|
left_separator,
|
||||||
|
color_string(short_indication_text, self.color),
|
||||||
|
right_separator
|
||||||
|
);
|
||||||
|
let short_indication_len = short_indication_text.width() + 4; // 2 for separators 2 for padding
|
||||||
|
if full_indication_len <= max_length {
|
||||||
|
Some((full_indication, full_indication_len))
|
||||||
|
} else if short_indication_len <= max_length {
|
||||||
|
Some((short_indication, short_indication_len))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn render_my_and_others_focus(&self, max_length: usize) -> Option<(String, usize)> {
|
||||||
|
let left_separator = color_string(boundary_type::VERTICAL_LEFT, self.color);
|
||||||
|
let right_separator = color_string(boundary_type::VERTICAL_RIGHT, self.color);
|
||||||
|
let full_indication_text = "MY FOCUS AND:";
|
||||||
|
let short_indication_text = "+";
|
||||||
|
let mut full_indication = color_string(full_indication_text, self.color);
|
||||||
|
let mut full_indication_len = full_indication_text.width();
|
||||||
|
let mut short_indication = color_string(short_indication_text, self.color);
|
||||||
|
let mut short_indication_len = short_indication_text.width();
|
||||||
|
for client_id in &self.other_focused_clients {
|
||||||
|
let text = format!(" {}", self.client_cursor(*client_id));
|
||||||
|
full_indication_len += 2;
|
||||||
|
full_indication.push_str(&text);
|
||||||
|
short_indication_len += 2;
|
||||||
|
short_indication.push_str(&text);
|
||||||
|
}
|
||||||
|
if full_indication_len + 4 <= max_length {
|
||||||
|
// 2 for separators, 2 for padding
|
||||||
|
Some((
|
||||||
|
format!("{} {} {}", left_separator, full_indication, right_separator),
|
||||||
|
full_indication_len + 4,
|
||||||
|
))
|
||||||
|
} else if short_indication_len + 4 <= max_length {
|
||||||
|
// 2 for separators, 2 for padding
|
||||||
|
Some((
|
||||||
|
format!(
|
||||||
|
"{} {} {}",
|
||||||
|
left_separator, short_indication, right_separator
|
||||||
|
),
|
||||||
|
short_indication_len + 4,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn render_other_focused_users(&self, max_length: usize) -> Option<(String, usize)> {
|
||||||
|
let left_separator = color_string(boundary_type::VERTICAL_LEFT, self.color);
|
||||||
|
let right_separator = color_string(boundary_type::VERTICAL_RIGHT, self.color);
|
||||||
|
let full_indication_text = if self.other_focused_clients.len() == 1 {
|
||||||
|
"FOCUSED USER:"
|
||||||
|
} else {
|
||||||
|
"FOCUSED USERS:"
|
||||||
|
};
|
||||||
|
let middle_indication_text = "U:";
|
||||||
|
let mut full_indication = color_string(full_indication_text, self.color);
|
||||||
|
let mut full_indication_len = full_indication_text.width();
|
||||||
|
let mut middle_indication = color_string(middle_indication_text, self.color);
|
||||||
|
let mut middle_indication_len = middle_indication_text.width();
|
||||||
|
let mut short_indication = String::from("");
|
||||||
|
let mut short_indication_len = 0;
|
||||||
|
for client_id in &self.other_focused_clients {
|
||||||
|
let text = format!(" {}", self.client_cursor(*client_id));
|
||||||
|
full_indication_len += 2;
|
||||||
|
full_indication.push_str(&text);
|
||||||
|
middle_indication_len += 2;
|
||||||
|
middle_indication.push_str(&text);
|
||||||
|
short_indication_len += 2;
|
||||||
|
short_indication.push_str(&text);
|
||||||
|
}
|
||||||
|
if full_indication_len + 4 <= max_length {
|
||||||
|
// 2 for separators, 2 for padding
|
||||||
|
Some((
|
||||||
|
format!("{} {} {}", left_separator, full_indication, right_separator),
|
||||||
|
full_indication_len + 4,
|
||||||
|
))
|
||||||
|
} else if middle_indication_len + 4 <= max_length {
|
||||||
|
// 2 for separators, 2 for padding
|
||||||
|
Some((
|
||||||
|
format!(
|
||||||
|
"{} {} {}",
|
||||||
|
left_separator, middle_indication, right_separator
|
||||||
|
),
|
||||||
|
middle_indication_len + 4,
|
||||||
|
))
|
||||||
|
} else if short_indication_len + 3 <= max_length {
|
||||||
|
// 2 for separators, 1 for padding
|
||||||
|
Some((
|
||||||
|
format!("{}{} {}", left_separator, short_indication, right_separator),
|
||||||
|
short_indication_len + 3,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn render_title_middle(&self, max_length: usize) -> Option<(String, usize)> {
|
||||||
|
// string and length because of color
|
||||||
|
if self.is_main_client
|
||||||
|
&& self.other_focused_clients.is_empty()
|
||||||
|
&& !self.other_cursors_exist_in_session
|
||||||
|
{
|
||||||
|
None
|
||||||
|
} else if self.is_main_client
|
||||||
|
&& self.other_focused_clients.is_empty()
|
||||||
|
&& self.other_cursors_exist_in_session
|
||||||
|
{
|
||||||
|
self.render_my_focus(max_length)
|
||||||
|
} else if self.is_main_client && !self.other_focused_clients.is_empty() {
|
||||||
|
self.render_my_and_others_focus(max_length)
|
||||||
|
} else if !self.other_focused_clients.is_empty() {
|
||||||
|
self.render_other_focused_users(max_length)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn render_title_left_side(&self, max_length: usize) -> Option<(String, usize)> {
|
||||||
let middle_truncated_sign = "[..]";
|
let middle_truncated_sign = "[..]";
|
||||||
let middle_truncated_sign_long = "[...]";
|
let middle_truncated_sign_long = "[...]";
|
||||||
let full_text = format!(" {} ", &self.title);
|
let full_text = format!(" {} ", &self.title);
|
||||||
if max_length <= 6 || self.title.is_empty() {
|
if max_length <= 6 || self.title.is_empty() {
|
||||||
None
|
None
|
||||||
} else if full_text.width() <= max_length {
|
} else if full_text.width() <= max_length {
|
||||||
Some(full_text)
|
Some((
|
||||||
|
color_string(&full_text, self.color),
|
||||||
|
full_text.chars().count(),
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
let length_of_each_half = (max_length - middle_truncated_sign.width()) / 2;
|
let length_of_each_half = (max_length - middle_truncated_sign.width()) / 2;
|
||||||
|
|
||||||
|
|
@ -89,53 +301,234 @@ impl PaneFrame {
|
||||||
} else {
|
} else {
|
||||||
format!("{}{}{}", first_part, middle_truncated_sign, second_part)
|
format!("{}{}{}", first_part, middle_truncated_sign, second_part)
|
||||||
};
|
};
|
||||||
Some(title_left_side)
|
Some((
|
||||||
|
color_string(&title_left_side, self.color),
|
||||||
|
title_left_side.chars().count(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn three_part_title_line(
|
||||||
|
&self,
|
||||||
|
left_side: &str,
|
||||||
|
left_side_len: &usize,
|
||||||
|
middle: &str,
|
||||||
|
middle_len: &usize,
|
||||||
|
right_side: &str,
|
||||||
|
right_side_len: &usize,
|
||||||
|
) -> String {
|
||||||
|
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||||
|
let mut title_line = String::new();
|
||||||
|
let left_side_start_position = self.geom.x + 1;
|
||||||
|
let middle_start_position = self.geom.x + (total_title_length / 2) - (middle_len / 2) + 1;
|
||||||
|
let right_side_start_position =
|
||||||
|
(self.geom.x + self.geom.cols - 1).saturating_sub(*right_side_len);
|
||||||
|
|
||||||
|
let mut col = self.geom.x;
|
||||||
|
loop {
|
||||||
|
if col == self.geom.x {
|
||||||
|
title_line.push_str(&color_string(boundary_type::TOP_LEFT, self.color));
|
||||||
|
} else if col == self.geom.x + self.geom.cols - 1 {
|
||||||
|
title_line.push_str(&color_string(boundary_type::TOP_RIGHT, self.color));
|
||||||
|
} else if col == left_side_start_position {
|
||||||
|
title_line.push_str(left_side);
|
||||||
|
col += left_side_len;
|
||||||
|
continue;
|
||||||
|
} else if col == middle_start_position {
|
||||||
|
title_line.push_str(middle);
|
||||||
|
col += middle_len;
|
||||||
|
continue;
|
||||||
|
} else if col == right_side_start_position {
|
||||||
|
title_line.push_str(right_side);
|
||||||
|
col += right_side_len;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
title_line.push_str(&color_string(boundary_type::HORIZONTAL, self.color));
|
||||||
|
// TODO: BETTER
|
||||||
|
}
|
||||||
|
if col == self.geom.x + self.geom.cols - 1 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
col += 1;
|
||||||
|
}
|
||||||
|
title_line
|
||||||
|
}
|
||||||
|
fn left_and_middle_title_line(
|
||||||
|
&self,
|
||||||
|
left_side: &str,
|
||||||
|
left_side_len: &usize,
|
||||||
|
middle: &str,
|
||||||
|
middle_len: &usize,
|
||||||
|
) -> String {
|
||||||
|
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||||
|
let mut title_line = String::new();
|
||||||
|
let left_side_start_position = self.geom.x + 1;
|
||||||
|
let middle_start_position = self.geom.x + (total_title_length / 2) - (*middle_len / 2) + 1;
|
||||||
|
|
||||||
|
let mut col = self.geom.x;
|
||||||
|
loop {
|
||||||
|
if col == self.geom.x {
|
||||||
|
title_line.push_str(&color_string(boundary_type::TOP_LEFT, self.color));
|
||||||
|
} else if col == self.geom.x + self.geom.cols - 1 {
|
||||||
|
title_line.push_str(&color_string(boundary_type::TOP_RIGHT, self.color));
|
||||||
|
} else if col == left_side_start_position {
|
||||||
|
title_line.push_str(left_side);
|
||||||
|
col += *left_side_len;
|
||||||
|
continue;
|
||||||
|
} else if col == middle_start_position {
|
||||||
|
title_line.push_str(middle);
|
||||||
|
col += *middle_len;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
title_line.push_str(&color_string(boundary_type::HORIZONTAL, self.color));
|
||||||
|
// TODO: BETTER
|
||||||
|
}
|
||||||
|
if col == self.geom.x + self.geom.cols - 1 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
col += 1;
|
||||||
|
}
|
||||||
|
title_line
|
||||||
|
}
|
||||||
|
fn middle_only_title_line(&self, middle: &str, middle_len: &usize) -> String {
|
||||||
|
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||||
|
let mut title_line = String::new();
|
||||||
|
let middle_start_position = self.geom.x + (total_title_length / 2) - (*middle_len / 2) + 1;
|
||||||
|
|
||||||
|
let mut col = self.geom.x;
|
||||||
|
loop {
|
||||||
|
if col == self.geom.x {
|
||||||
|
title_line.push_str(&color_string(boundary_type::TOP_LEFT, self.color));
|
||||||
|
} else if col == self.geom.x + self.geom.cols - 1 {
|
||||||
|
title_line.push_str(&color_string(boundary_type::TOP_RIGHT, self.color));
|
||||||
|
} else if col == middle_start_position {
|
||||||
|
title_line.push_str(middle);
|
||||||
|
col += *middle_len;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
title_line.push_str(&color_string(boundary_type::HORIZONTAL, self.color));
|
||||||
|
// TODO: BETTER
|
||||||
|
}
|
||||||
|
if col == self.geom.x + self.geom.cols - 1 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
col += 1;
|
||||||
|
}
|
||||||
|
title_line
|
||||||
|
}
|
||||||
|
fn two_part_title_line(
|
||||||
|
&self,
|
||||||
|
left_side: &str,
|
||||||
|
left_side_len: &usize,
|
||||||
|
right_side: &str,
|
||||||
|
right_side_len: &usize,
|
||||||
|
) -> String {
|
||||||
|
let left_boundary = color_string(boundary_type::TOP_LEFT, self.color);
|
||||||
|
let right_boundary = color_string(boundary_type::TOP_RIGHT, self.color);
|
||||||
|
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||||
|
let mut middle = String::new();
|
||||||
|
for _ in (left_side_len + right_side_len)..total_title_length {
|
||||||
|
middle.push_str(boundary_type::HORIZONTAL);
|
||||||
|
}
|
||||||
|
format!(
|
||||||
|
"{}{}{}{}{}",
|
||||||
|
left_boundary,
|
||||||
|
left_side,
|
||||||
|
color_string(&middle, self.color),
|
||||||
|
color_string(right_side, self.color),
|
||||||
|
&right_boundary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn left_only_title_line(&self, left_side: &str, left_side_len: &usize) -> String {
|
||||||
|
let left_boundary = color_string(boundary_type::TOP_LEFT, self.color);
|
||||||
|
let right_boundary = color_string(boundary_type::TOP_RIGHT, self.color);
|
||||||
|
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||||
|
let mut middle_padding = String::new();
|
||||||
|
for _ in *left_side_len..total_title_length {
|
||||||
|
middle_padding.push_str(boundary_type::HORIZONTAL);
|
||||||
|
}
|
||||||
|
format!(
|
||||||
|
"{}{}{}{}",
|
||||||
|
left_boundary,
|
||||||
|
left_side,
|
||||||
|
color_string(&middle_padding, self.color),
|
||||||
|
&right_boundary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn empty_title_line(&self) -> String {
|
||||||
|
let left_boundary = color_string(boundary_type::TOP_LEFT, self.color);
|
||||||
|
let right_boundary = color_string(boundary_type::TOP_RIGHT, self.color);
|
||||||
|
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||||
|
let mut middle_padding = String::new();
|
||||||
|
for _ in 0..total_title_length {
|
||||||
|
middle_padding.push_str(boundary_type::HORIZONTAL);
|
||||||
|
}
|
||||||
|
format!(
|
||||||
|
"{}{}{}",
|
||||||
|
left_boundary,
|
||||||
|
color_string(&middle_padding, self.color),
|
||||||
|
right_boundary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn title_line_with_middle(&self, middle: &str, middle_len: &usize) -> String {
|
||||||
|
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||||
|
let length_of_each_side = total_title_length.saturating_sub(*middle_len + 2) / 2;
|
||||||
|
let mut left_side = self.render_title_left_side(length_of_each_side);
|
||||||
|
let mut right_side = self.render_title_right_side(length_of_each_side);
|
||||||
|
|
||||||
|
match (left_side.as_mut(), right_side.as_mut()) {
|
||||||
|
(Some((left_side, left_side_len)), Some((right_side, right_side_len))) => self
|
||||||
|
.three_part_title_line(
|
||||||
|
left_side,
|
||||||
|
left_side_len,
|
||||||
|
middle,
|
||||||
|
middle_len,
|
||||||
|
right_side,
|
||||||
|
right_side_len,
|
||||||
|
),
|
||||||
|
(Some((left_side, left_side_len)), None) => {
|
||||||
|
self.left_and_middle_title_line(left_side, left_side_len, middle, middle_len)
|
||||||
|
}
|
||||||
|
_ => self.middle_only_title_line(middle, middle_len),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn title_line_without_middle(&self) -> String {
|
||||||
|
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||||
|
let left_side = self.render_title_left_side(total_title_length);
|
||||||
|
let right_side = left_side.as_ref().and_then(|(_left_side, left_side_len)| {
|
||||||
|
let space_left = total_title_length.saturating_sub(*left_side_len + 1); // 1 for a middle separator
|
||||||
|
self.render_title_right_side(space_left)
|
||||||
|
});
|
||||||
|
match (left_side, right_side) {
|
||||||
|
(Some((left_side, left_side_len)), Some((right_side, right_side_len))) => {
|
||||||
|
self.two_part_title_line(&left_side, &left_side_len, &right_side, &right_side_len)
|
||||||
|
}
|
||||||
|
(Some((left_side, left_side_len)), None) => {
|
||||||
|
self.left_only_title_line(&left_side, &left_side_len)
|
||||||
|
}
|
||||||
|
_ => self.empty_title_line(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn render_title(&self, vte_output: &mut String) {
|
fn render_title(&self, vte_output: &mut String) {
|
||||||
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
|
||||||
let left_boundary = boundary_type::TOP_LEFT;
|
|
||||||
let right_boundary = boundary_type::TOP_RIGHT;
|
if let Some((middle, middle_length)) = self.render_title_middle(total_title_length).as_mut()
|
||||||
let left_side = self.render_title_left_side(total_title_length);
|
{
|
||||||
let right_side = left_side.as_ref().and_then(|left_side| {
|
let title_text = self.title_line_with_middle(middle, middle_length);
|
||||||
let space_left = total_title_length.saturating_sub(left_side.width() + 1); // 1 for a middle separator
|
vte_output.push_str(&format!(
|
||||||
self.render_title_right_side(space_left)
|
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||||
});
|
self.geom.y + 1, // +1 because goto is 1 indexed
|
||||||
let title_text = match (left_side, right_side) {
|
self.geom.x + 1, // +1 because goto is 1 indexed
|
||||||
(Some(left_side), Some(right_side)) => {
|
color_string(&title_text, self.color),
|
||||||
let mut middle = String::new();
|
)); // goto row/col + boundary character
|
||||||
for _ in (left_side.width() + right_side.width())..total_title_length {
|
} else {
|
||||||
middle.push_str(boundary_type::HORIZONTAL);
|
let title_text = self.title_line_without_middle();
|
||||||
}
|
vte_output.push_str(&format!(
|
||||||
format!(
|
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||||
"{}{}{}{}{}",
|
self.geom.y + 1, // +1 because goto is 1 indexed
|
||||||
left_boundary, left_side, middle, right_side, right_boundary
|
self.geom.x + 1, // +1 because goto is 1 indexed
|
||||||
)
|
color_string(&title_text, self.color),
|
||||||
}
|
)); // goto row/col + boundary character
|
||||||
(Some(left_side), None) => {
|
}
|
||||||
let mut middle_padding = String::new();
|
|
||||||
for _ in left_side.width()..total_title_length {
|
|
||||||
middle_padding.push_str(boundary_type::HORIZONTAL);
|
|
||||||
}
|
|
||||||
format!(
|
|
||||||
"{}{}{}{}",
|
|
||||||
left_boundary, left_side, middle_padding, right_boundary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let mut middle_padding = String::new();
|
|
||||||
for _ in 0..total_title_length {
|
|
||||||
middle_padding.push_str(boundary_type::HORIZONTAL);
|
|
||||||
}
|
|
||||||
format!("{}{}{}", left_boundary, middle_padding, right_boundary)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
vte_output.push_str(&format!(
|
|
||||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
|
||||||
self.geom.y + 1, // +1 because goto is 1 indexed
|
|
||||||
self.geom.x + 1, // +1 because goto is 1 indexed
|
|
||||||
color_string(&title_text, self.color),
|
|
||||||
)); // goto row/col + boundary character
|
|
||||||
}
|
}
|
||||||
pub fn render(&self) -> String {
|
pub fn render(&self) -> String {
|
||||||
let mut vte_output = String::new();
|
let mut vte_output = String::new();
|
||||||
|
|
|
||||||
165
zellij-server/src/ui/pane_contents_and_ui.rs
Normal file
165
zellij-server/src/ui/pane_contents_and_ui.rs
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
use crate::panes::PaneId;
|
||||||
|
use crate::tab::{Output, Pane};
|
||||||
|
use crate::ui::boundaries::Boundaries;
|
||||||
|
use crate::ui::pane_boundaries_frame::client_id_to_colors;
|
||||||
|
use crate::ui::pane_boundaries_frame::FrameParams;
|
||||||
|
use crate::ClientId;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use zellij_tile::data::{InputMode, Palette, PaletteColor};
|
||||||
|
|
||||||
|
pub struct PaneContentsAndUi<'a> {
|
||||||
|
pane: &'a mut Box<dyn Pane>,
|
||||||
|
output: &'a mut Output,
|
||||||
|
colors: Palette,
|
||||||
|
focused_clients: Vec<ClientId>,
|
||||||
|
multiple_users_exist_in_session: bool,
|
||||||
|
mode: InputMode, // TODO: per client
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PaneContentsAndUi<'a> {
|
||||||
|
pub fn new(
|
||||||
|
pane: &'a mut Box<dyn Pane>,
|
||||||
|
output: &'a mut Output,
|
||||||
|
colors: Palette,
|
||||||
|
active_panes: &HashMap<ClientId, PaneId>,
|
||||||
|
mode: InputMode,
|
||||||
|
) -> Self {
|
||||||
|
let focused_clients: Vec<ClientId> = active_panes
|
||||||
|
.iter()
|
||||||
|
.filter(|(_c_id, p_id)| **p_id == pane.pid())
|
||||||
|
.map(|(c_id, _p_id)| *c_id)
|
||||||
|
.collect();
|
||||||
|
let multiple_users_exist_in_session = active_panes.len() > 1;
|
||||||
|
PaneContentsAndUi {
|
||||||
|
pane,
|
||||||
|
output,
|
||||||
|
colors,
|
||||||
|
focused_clients,
|
||||||
|
multiple_users_exist_in_session,
|
||||||
|
mode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn render_pane_contents_for_all_clients(&mut self) {
|
||||||
|
if let Some(vte_output) = self.pane.render() {
|
||||||
|
// FIXME: Use Termion for cursor and style clearing?
|
||||||
|
self.output.push_str_to_all_clients(&format!(
|
||||||
|
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||||
|
self.pane.y() + 1,
|
||||||
|
self.pane.x() + 1,
|
||||||
|
vte_output
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn render_fake_cursor_if_needed(&mut self, client_id: ClientId) {
|
||||||
|
let pane_focused_for_client_id = self.focused_clients.contains(&client_id);
|
||||||
|
let pane_focused_for_different_client = self
|
||||||
|
.focused_clients
|
||||||
|
.iter()
|
||||||
|
.filter(|c_id| **c_id != client_id)
|
||||||
|
.count()
|
||||||
|
> 0;
|
||||||
|
if pane_focused_for_different_client && !pane_focused_for_client_id {
|
||||||
|
let fake_cursor_client_id = self
|
||||||
|
.focused_clients
|
||||||
|
.iter()
|
||||||
|
.find(|c_id| **c_id != client_id)
|
||||||
|
.unwrap();
|
||||||
|
if let Some(colors) = client_id_to_colors(*fake_cursor_client_id, self.colors) {
|
||||||
|
if let Some(vte_output) = self.pane.render_fake_cursor(colors.0, colors.1) {
|
||||||
|
self.output.push_to_client(
|
||||||
|
client_id,
|
||||||
|
&format!(
|
||||||
|
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||||
|
self.pane.y() + 1,
|
||||||
|
self.pane.x() + 1,
|
||||||
|
vte_output
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn render_pane_frame(&mut self, client_id: ClientId, session_is_mirrored: bool) {
|
||||||
|
let pane_focused_for_client_id = self.focused_clients.contains(&client_id);
|
||||||
|
let other_focused_clients: Vec<ClientId> = self
|
||||||
|
.focused_clients
|
||||||
|
.iter()
|
||||||
|
.filter(|c_id| **c_id != client_id)
|
||||||
|
.copied()
|
||||||
|
.collect();
|
||||||
|
let pane_focused_for_differet_client = !other_focused_clients.is_empty();
|
||||||
|
|
||||||
|
let frame_color = self.frame_color(client_id, self.mode, session_is_mirrored);
|
||||||
|
let focused_client = if pane_focused_for_client_id {
|
||||||
|
Some(client_id)
|
||||||
|
} else if pane_focused_for_differet_client {
|
||||||
|
Some(*other_focused_clients.first().unwrap())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let frame_params = if session_is_mirrored {
|
||||||
|
FrameParams {
|
||||||
|
focused_client,
|
||||||
|
is_main_client: pane_focused_for_client_id,
|
||||||
|
other_focused_clients: vec![],
|
||||||
|
colors: self.colors,
|
||||||
|
color: frame_color,
|
||||||
|
other_cursors_exist_in_session: false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FrameParams {
|
||||||
|
focused_client,
|
||||||
|
is_main_client: pane_focused_for_client_id,
|
||||||
|
other_focused_clients,
|
||||||
|
colors: self.colors,
|
||||||
|
color: frame_color,
|
||||||
|
other_cursors_exist_in_session: self.multiple_users_exist_in_session,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(vte_output) = self.pane.render_frame(client_id, frame_params) {
|
||||||
|
// FIXME: Use Termion for cursor and style clearing?
|
||||||
|
self.output.push_to_client(
|
||||||
|
client_id,
|
||||||
|
&format!(
|
||||||
|
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||||
|
self.pane.y() + 1,
|
||||||
|
self.pane.x() + 1,
|
||||||
|
vte_output
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn render_pane_boundaries(
|
||||||
|
&self,
|
||||||
|
client_id: ClientId,
|
||||||
|
boundaries: &mut Boundaries,
|
||||||
|
session_is_mirrored: bool,
|
||||||
|
) {
|
||||||
|
let color = self.frame_color(client_id, self.mode, session_is_mirrored);
|
||||||
|
boundaries.add_rect(self.pane.as_ref(), color);
|
||||||
|
}
|
||||||
|
fn frame_color(
|
||||||
|
&self,
|
||||||
|
client_id: ClientId,
|
||||||
|
mode: InputMode,
|
||||||
|
session_is_mirrored: bool,
|
||||||
|
) -> Option<PaletteColor> {
|
||||||
|
let pane_focused_for_client_id = self.focused_clients.contains(&client_id);
|
||||||
|
if pane_focused_for_client_id {
|
||||||
|
match mode {
|
||||||
|
InputMode::Normal | InputMode::Locked => {
|
||||||
|
if session_is_mirrored {
|
||||||
|
let colors = client_id_to_colors(1, self.colors); // mirrored sessions only have one focused color
|
||||||
|
colors.map(|colors| colors.0)
|
||||||
|
} else {
|
||||||
|
let colors = client_id_to_colors(client_id, self.colors);
|
||||||
|
colors.map(|colors| colors.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Some(self.colors.orange),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -163,6 +163,7 @@ pub struct Palette {
|
||||||
pub cyan: PaletteColor,
|
pub cyan: PaletteColor,
|
||||||
pub white: PaletteColor,
|
pub white: PaletteColor,
|
||||||
pub orange: PaletteColor,
|
pub orange: PaletteColor,
|
||||||
|
pub gray: PaletteColor,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the contents of the help message that is printed in the status bar,
|
/// Represents the contents of the help message that is printed in the status bar,
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,10 @@ pub mod colors {
|
||||||
pub const RED: u8 = 88;
|
pub const RED: u8 = 88;
|
||||||
pub const ORANGE: u8 = 166;
|
pub const ORANGE: u8 = 166;
|
||||||
pub const BLACK: u8 = 16;
|
pub const BLACK: u8 = 16;
|
||||||
|
pub const MAGENTA: u8 = 201;
|
||||||
|
pub const CYAN: u8 = 51;
|
||||||
|
pub const YELLOW: u8 = 226;
|
||||||
|
pub const BLUE: u8 = 45;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _hex_to_rgb(hex: &str) -> (u8, u8, u8) {
|
pub fn _hex_to_rgb(hex: &str) -> (u8, u8, u8) {
|
||||||
|
|
@ -66,12 +70,13 @@ pub fn default_palette() -> Palette {
|
||||||
black: PaletteColor::EightBit(colors::BLACK),
|
black: PaletteColor::EightBit(colors::BLACK),
|
||||||
red: PaletteColor::EightBit(colors::RED),
|
red: PaletteColor::EightBit(colors::RED),
|
||||||
green: PaletteColor::EightBit(colors::GREEN),
|
green: PaletteColor::EightBit(colors::GREEN),
|
||||||
yellow: PaletteColor::EightBit(colors::GRAY),
|
yellow: PaletteColor::EightBit(colors::YELLOW),
|
||||||
blue: PaletteColor::EightBit(colors::GRAY),
|
blue: PaletteColor::EightBit(colors::BLUE),
|
||||||
magenta: PaletteColor::EightBit(colors::GRAY),
|
magenta: PaletteColor::EightBit(colors::MAGENTA),
|
||||||
cyan: PaletteColor::EightBit(colors::GRAY),
|
cyan: PaletteColor::EightBit(colors::CYAN),
|
||||||
white: PaletteColor::EightBit(colors::WHITE),
|
white: PaletteColor::EightBit(colors::WHITE),
|
||||||
orange: PaletteColor::EightBit(colors::ORANGE),
|
orange: PaletteColor::EightBit(colors::ORANGE),
|
||||||
|
gray: PaletteColor::EightBit(colors::GRAY),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue