diff options
| author | Aram Drevekenin <aram@poor.dev> | 2026-07-27 18:24:37 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-27 18:24:37 +0200 |
| commit | ea07e2d5b6bbc9ea6f9c765b0838b3e91156a58d (patch) | |
| tree | 4a8fac8a9b27f2a8b32b3af61a3b5419b9937c81 /default-plugins/status-bar/src | |
| parent | Switch from the unmaintained daemonize crate to a maintained fork (#4512) (diff) | |
| download | sl0ppij-ea07e2d5b6bbc9ea6f9c765b0838b3e91156a58d.tar.gz sl0ppij-ea07e2d5b6bbc9ea6f9c765b0838b3e91156a58d.tar.xz sl0ppij-ea07e2d5b6bbc9ea6f9c765b0838b3e91156a58d.zip | |
Nested session handling (#5417)
* fix: theme query and updated pixel dimensions
* add no-ui-fullscreen key/action/capability
* protocol core
* integration test harness
* fix integration tests
* fix flaky tests
* nested ui (dimming) and directional focus initial implementation
* integration tests for nested ui dimming and directional focus
* initial full screen nesteed implementation
* fix: keep hidden floating panes hidden across nested no-ui fullscreen
* fix integration tests
* no ui fullscreen integration tests
* hardening behavior/tests
* fix(nested): re-derive descend/dim on host tab switch and new-tab so clicking dimmed chrome ascends
* initial modal implementation
* initial ui improvements
* fix integration tests
* improve nested selection ui
* nested session modal refactoring and tests
* improve integration test speed somewhat
* some ui adjustments
* simplify full/auto nested session handling
* hints in compact-bar
* guest stdin leak fix
* allow floating panes to be fullscreen
* make nested session handling configurable
* protocol reannounce
* refactor: naming confusion
* improve ux somewhat
* rustfmt
* merge fixes
* improve integration test rate limiting (fix ci)
* add pr
Diffstat (limited to 'default-plugins/status-bar/src')
| -rw-r--r-- | default-plugins/status-bar/src/first_line.rs | 4 | ||||
| -rw-r--r-- | default-plugins/status-bar/src/main.rs | 50 | ||||
| -rw-r--r-- | default-plugins/status-bar/src/one_line_ui.rs | 131 | ||||
| -rw-r--r-- | default-plugins/status-bar/src/second_line.rs | 54 |
4 files changed, 206 insertions, 33 deletions
diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index b5c688ad..c2f5b477 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -613,7 +613,7 @@ pub fn first_line( separator: &str, ) -> LinePart { let supports_arrow_fonts = !help.capabilities.arrow_fonts; - let colored_elements = color_elements(help.style.colors, !supports_arrow_fonts); + let colored_elements = color_elements(help.style.colors, !supports_arrow_fonts, false); let binds = &help.get_mode_keybinds(); // Unselect all by default let mut default_keys = vec![ @@ -755,7 +755,7 @@ mod tests { fn colored_elements() -> ColoredElements { let palette = Styling::default(); - color_elements(palette, false) + color_elements(palette, false, false) } // Strip style information from `LinePart` and return a raw String instead diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 673d7c8c..f12da1cc 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -18,9 +18,9 @@ use zellij_tile_utils::{palette_match, style}; use first_line::first_line; use one_line_ui::one_line_ui; use second_line::{ - floating_panes_are_visible, fullscreen_panes_to_hide, keybinds, - locked_floating_panes_are_visible, locked_fullscreen_panes_to_hide, system_clipboard_error, - text_copied_hint, + ascended_to_host_session_hint, descended_into_nested_session_hint, floating_panes_are_visible, + fullscreen_panes_to_hide, keybinds, locked_floating_panes_are_visible, + locked_fullscreen_panes_to_hide, system_clipboard_error, text_copied_hint, }; use tip::utils::get_cached_tip_name; @@ -94,7 +94,33 @@ pub struct SegmentStyle { // we need different colors from palette for the default theme // plus here we can add new sources in the future, like Theme // that can be defined in the config perhaps -fn color_elements(palette: Styling, different_color_alternates: bool) -> ColoredElements { +fn color_elements( + palette: Styling, + different_color_alternates: bool, + dimmed: bool, +) -> ColoredElements { + if dimmed { + let background = palette.text_unselected.background; + let foreground = palette.text_unselected.base; + let ribbon_background = palette.ribbon_unselected.background; + let italic_on_ribbon = style!(palette.ribbon_unselected.base, ribbon_background).italic(); + let dim_segment = SegmentStyle { + prefix_separator: style!(background, ribbon_background), + char_left_separator: italic_on_ribbon, + char_shortcut: italic_on_ribbon, + char_right_separator: italic_on_ribbon, + styled_text: italic_on_ribbon, + suffix_separator: style!(ribbon_background, background), + }; + return ColoredElements { + selected: dim_segment, + unselected: dim_segment, + unselected_alternate: dim_segment, + disabled: dim_segment, + superkey_prefix: style!(foreground, background).italic(), + superkey_suffix_separator: style!(background, background), + }; + } let background = palette.text_unselected.background; let foreground = palette.text_unselected.base; let alternate_background_color = if different_color_alternates { @@ -344,6 +370,8 @@ impl ZellijPlugin for State { let active_tab = self.tabs.iter().find(|t| t.active); let first_line = first_line(&self.mode_info, active_tab, cols, separator); let second_line = self.second_line(cols); + let show_nested_session_hint = self.mode_info.session_dimmed.unwrap_or(false) + || self.mode_info.session_ascended.unwrap_or(false); // [48;5;238m is white 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 @@ -352,7 +380,9 @@ impl ZellijPlugin for State { if rows > 1 { println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", first_line, r, g, b); } else { - if self.mode_info.mode == InputMode::Normal { + if show_nested_session_hint { + print!("\u{1b}[m{}\u{1b}[0K", second_line); + } else if self.mode_info.mode == InputMode::Normal { print!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", first_line, r, g, b); } else { print!("\u{1b}[m{}\u{1b}[0K", second_line); @@ -363,7 +393,9 @@ impl ZellijPlugin for State { if rows > 1 { println!("{}\u{1b}[48;5;{}m\u{1b}[0K", first_line, color); } else { - if self.mode_info.mode == InputMode::Normal { + if show_nested_session_hint { + print!("\u{1b}[m{}\u{1b}[0K", second_line); + } else if self.mode_info.mode == InputMode::Normal { print!("{}\u{1b}[48;5;{}m\u{1b}[0K", first_line, color); } else { print!("\u{1b}[m{}\u{1b}[0K", second_line); @@ -382,6 +414,12 @@ impl State { fn second_line(&self, cols: usize) -> LinePart { let active_tab = self.tabs.iter().find(|t| t.active); + if self.mode_info.session_dimmed.unwrap_or(false) { + return descended_into_nested_session_hint(&self.mode_info, cols); + } + if self.mode_info.session_ascended.unwrap_or(false) { + return ascended_to_host_session_hint(&self.mode_info, cols); + } if let Some(copy_destination) = self.text_copy_destination { text_copied_hint(copy_destination) } else if self.display_system_clipboard_failure { diff --git a/default-plugins/status-bar/src/one_line_ui.rs b/default-plugins/status-bar/src/one_line_ui.rs index ae57a223..62992af6 100644 --- a/default-plugins/status-bar/src/one_line_ui.rs +++ b/default-plugins/status-bar/src/one_line_ui.rs @@ -9,7 +9,10 @@ use zellij_tile::prelude::*; use zellij_tile_utils::{palette_match, style}; use crate::first_line::{to_char, KeyAction, KeyMode, KeyShortcut}; -use crate::second_line::{system_clipboard_error, text_copied_hint}; +use crate::second_line::{ + ascended_to_host_session_hint, descended_into_nested_session_hint, system_clipboard_error, + text_copied_hint, +}; use crate::{action_key, action_key_group, color_elements, MORE_MSG, TO_NORMAL}; use crate::{ColoredElements, LinePart}; use unicode_width::UnicodeWidthStr; @@ -25,6 +28,16 @@ pub fn one_line_ui( new_pane_hovered: bool, floating_hovered: bool, ) -> (LinePart, Option<(usize, usize)>, Option<(usize, usize)>) { + if help.session_dimmed.unwrap_or(false) { + return ( + descended_into_nested_session_hint(help, max_len), + None, + None, + ); + } + if help.session_ascended.unwrap_or(false) { + return (ascended_to_host_session_hint(help, max_len), None, None); + } if let Some(text_copied_to_clipboard_destination) = text_copied_to_clipboard_destination { return ( text_copied_hint(text_copied_to_clipboard_destination), @@ -528,7 +541,11 @@ fn render_mode_key_indicators( ) -> Option<LinePart> { let mut line_part_to_render = LinePart::default(); let supports_arrow_fonts = !help.capabilities.arrow_fonts; - let colored_elements = color_elements(help.style.colors, !supports_arrow_fonts); + let colored_elements = color_elements( + help.style.colors, + !supports_arrow_fonts, + help.session_dimmed.unwrap_or(false), + ); let default_keys = if base_mode_is_locked { base_mode_locked_mode_indicators(help) } else { @@ -742,13 +759,14 @@ fn render_common_modifiers( ) }; + let prefix = if mode_info.session_dimmed.unwrap_or(false) { + serialize_text(&Text::new(&prefix_text).disabled().opaque()) + } else { + serialize_text(&Text::new(&prefix_text).opaque()) + }; let suffix_separator = palette.superkey_suffix_separator.paint(separator); - line_part_to_render.part = format!( - "{}{}{}", - line_part_to_render.part, - serialize_text(&Text::new(&prefix_text).opaque()), - suffix_separator - ); + line_part_to_render.part = + format!("{}{}{}", line_part_to_render.part, prefix, suffix_separator); line_part_to_render.len += prefix_text.chars().count() + separator.chars().count(); } @@ -761,7 +779,11 @@ fn render_secondary_info( ) -> Option<(LinePart, Option<(usize, usize)>, Option<(usize, usize)>)> { let mut secondary_info = LinePart::default(); let supports_arrow_fonts = !help.capabilities.arrow_fonts; - let colored_elements = color_elements(help.style.colors, !supports_arrow_fonts); + let colored_elements = color_elements( + help.style.colors, + !supports_arrow_fonts, + help.session_dimmed.unwrap_or(false), + ); let (secondary_keybinds, new_pane_range, floating_range) = secondary_keybinds(&help, tab_info, max_len, new_pane_hovered, floating_hovered); secondary_info.append(&secondary_keybinds); @@ -1011,6 +1033,7 @@ fn secondary_keybinds( .join("-") ), 0, + help.session_dimmed.unwrap_or(false), ); secondary_info.append(&modifier_str); let new_pane_key_to_display: Vec<KeyWithModifier> = new_pane_key_to_display @@ -1145,6 +1168,7 @@ fn secondary_keybinds( .join("-") ), 0, + help.session_dimmed.unwrap_or(false), ); short_line.append(&modifier_str); let new_pane_key_to_display: Vec<KeyWithModifier> = new_pane_key_to_display @@ -1213,11 +1237,12 @@ fn secondary_keybinds( if short_line.len <= max_len { (short_line, new_pane_range, floating_range) } else if max_len >= 3 { - let part = serialize_text( - &Text::new(format!("{:>width$}", "...", width = max_len)) - .color_range(0, ..) - .opaque(), - ); + let overflow_text = Text::new(format!("{:>width$}", "...", width = max_len)); + let part = if help.session_dimmed.unwrap_or(false) { + serialize_text(&overflow_text.disabled().opaque()) + } else { + serialize_text(&overflow_text.color_range(0, ..).opaque()) + }; let len = max_len; (LinePart { part, len }, None, None) } else { @@ -1232,8 +1257,12 @@ fn secondary_keybinds( } } -fn text_as_line_part_with_emphasis(text: String, emphases_index: usize) -> LinePart { - let part = serialize_text(&Text::new(&text).color_range(emphases_index, ..).opaque()); +fn text_as_line_part_with_emphasis(text: String, emphases_index: usize, dimmed: bool) -> LinePart { + let part = if dimmed { + serialize_text(&Text::new(&text).disabled().opaque()) + } else { + serialize_text(&Text::new(&text).color_range(emphases_index, ..).opaque()) + }; LinePart { part, len: text.width(), @@ -1279,11 +1308,14 @@ fn add_shortcut_hovered( keys: &Vec<KeyWithModifier>, key_color_index: Option<usize>, ) -> LinePart { + if help.session_dimmed.unwrap_or(false) { + return add_shortcut(help, text, keys, false, key_color_index); + } let mut ret = LinePart::default(); if keys.is_empty() { return ret; } - ret.append(&style_key_with_modifier(&keys, key_color_index)); + ret.append(&style_key_with_modifier(&keys, key_color_index, false)); let palette = help.style.colors; let supports_arrow_fonts = !help.capabilities.arrow_fonts; let ribbon_bg = palette.ribbon_unselected.emphasis_1; @@ -1306,6 +1338,9 @@ fn add_shortcut_with_inline_key_hovered( text: &str, key: Vec<KeyWithModifier>, ) -> LinePart { + if help.session_dimmed.unwrap_or(false) { + return add_shortcut_with_inline_key(help, text, key, false); + } let mut ret = LinePart::default(); if key.is_empty() { return ret; @@ -1360,14 +1395,17 @@ fn add_shortcut( selected: bool, key_color_index: Option<usize>, ) -> LinePart { + let dimmed = help.session_dimmed.unwrap_or(false); let mut ret = LinePart::default(); if keys.is_empty() { return ret; } - ret.append(&style_key_with_modifier(&keys, key_color_index)); // TODO: alternate - // - let ribbon = if selected { + ret.append(&style_key_with_modifier(&keys, key_color_index, dimmed)); // TODO: alternate + // + let ribbon = if dimmed { + serialize_ribbon(&Text::new(format!("{}", text)).disabled()) + } else if selected { serialize_ribbon(&Text::new(format!("{}", text)).selected()) } else { serialize_ribbon(&Text::new(format!("{}", text))) @@ -1420,7 +1458,9 @@ fn add_shortcut_with_inline_key( .join(key_separator) ); - let ribbon = if is_selected { + let ribbon = if help.session_dimmed.unwrap_or(false) { + serialize_ribbon(&Text::new(format!("<{}> {}", key_string, text)).disabled()) + } else if is_selected { serialize_ribbon( &Text::new(format!("<{}> {}", key_string, text)) .color_range(0, 1..key_string.width() + 1) @@ -1461,7 +1501,9 @@ fn add_shortcut_with_key_only( .join("-") ); - let ribbon = if is_selected { + let ribbon = if help.session_dimmed.unwrap_or(false) { + serialize_ribbon(&Text::new(format!("{}", key_string)).disabled()) + } else if is_selected { serialize_ribbon( &Text::new(format!("{}", key_string)) .color_range(0, ..) @@ -1489,6 +1531,37 @@ fn add_keygroup_separator(help: &ModeInfo, max_len: usize) -> Option<LinePart> { }; let palette = help.style.colors; + if help.session_dimmed.unwrap_or(false) { + let mut ret = LinePart::default(); + let dim_style = Style::new() + .fg(palette_match!(palette.text_unselected.base)) + .on(palette_match!(palette.text_unselected.background)) + .italic(); + let mut bits: Vec<ANSIString> = vec![]; + let mode_help_text = match help.mode { + InputMode::RenamePane => Some("RENAMING PANE"), + InputMode::RenameTab => Some("RENAMING TAB"), + InputMode::EnterSearch => Some("ENTERING SEARCH TERM"), + InputMode::Search => Some("SEARCHING"), + _ => None, + }; + if let Some(mode_help_text) = mode_help_text { + bits.push(dim_style.paint(format!(" {} ", mode_help_text))); + ret.len += mode_help_text.width() + 2; + } + bits.push(dim_style.paint(format!("{}", separator))); + bits.push(dim_style.paint(format!(" "))); + bits.push(dim_style.paint(format!("{}", separator))); + ret.part = format!("{}{}", ret.part, ANSIStrings(&bits)); + ret.len += 3; + + if ret.len <= max_len { + return Some(ret); + } else { + return None; + } + } + let mut ret = LinePart::default(); let separator_color = palette_match!(palette.text_unselected.emphasis_0); @@ -1916,7 +1989,11 @@ fn configuration_key(keymap: &[(KeyWithModifier, Vec<Action>)]) -> Vec<KeyWithMo } } -fn style_key_with_modifier(keyvec: &[KeyWithModifier], color_index: Option<usize>) -> LinePart { +fn style_key_with_modifier( + keyvec: &[KeyWithModifier], + color_index: Option<usize>, + dimmed: bool, +) -> LinePart { if keyvec.is_empty() { return LinePart::default(); } @@ -1956,7 +2033,9 @@ fn style_key_with_modifier(keyvec: &[KeyWithModifier], color_index: Option<usize if no_common_modifier || key.len() == 1 { let key_string_text = format!(" {} ", key.join(key_separator)); - let text = if let Some(color_index) = color_index { + let text = if dimmed { + Text::new(&key_string_text).disabled().opaque() + } else if let Some(color_index) = color_index { Text::new(&key_string_text) .color_range(color_index, ..) .opaque() @@ -1970,7 +2049,9 @@ fn style_key_with_modifier(keyvec: &[KeyWithModifier], color_index: Option<usize } else { let key_string_without_modifier = format!("{}", key.join(key_separator)); let key_string_text = format!(" {} <{}> ", modifier_str, key_string_without_modifier); - let text = if let Some(color_index) = color_index { + let text = if dimmed { + Text::new(&key_string_text).disabled().opaque() + } else if let Some(color_index) = color_index { Text::new(&key_string_text) .color_range(color_index, ..modifier_str.width() + 1) .color_range( diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 921f3c75..63f845a6 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -368,6 +368,60 @@ pub fn keybinds(help: &ModeInfo, tip_name: &str, max_width: usize) -> LinePart { best_effort_shortcut_list(help, tip_body.short, max_width) } +pub fn descended_into_nested_session_hint(help: &ModeInfo, max_len: usize) -> LinePart { + nested_session_status_hint(help, "Ascend: ", &help.nested_ascend_keys, max_len) +} + +pub fn ascended_to_host_session_hint(help: &ModeInfo, max_len: usize) -> LinePart { + nested_session_status_hint(help, "Descend: ", &help.nested_descend_keys, max_len) +} + +fn nested_session_status_hint( + help: &ModeInfo, + label: &str, + keys: &[KeyWithModifier], + max_len: usize, +) -> LinePart { + let palette = help.style.colors; + let text_color = palette_match!(palette.text_unselected.base); + let styled_label = Style::new().dimmed().italic().paint(format!(" {}", label)); + let mut key_bits: Vec<ANSIString> = vec![]; + if keys.is_empty() { + key_bits.push(Style::new().dimmed().italic().paint("<unbound>")); + } else { + for (i, key) in keys.iter().enumerate() { + if i > 0 { + key_bits.push(Style::new().fg(text_color).paint(" + ")); + } + key_bits.extend(style_key_with_modifier( + std::slice::from_ref(key), + &palette, + None, + )); + } + } + let mut bits: Vec<ANSIString> = vec![styled_label]; + bits.extend(key_bits.clone()); + let part = ANSIStrings(&bits); + let len = unstyled_len(&part); + if len <= max_len { + return LinePart { + part: part.to_string(), + len, + }; + } + let part = ANSIStrings(&key_bits); + let len = unstyled_len(&part); + if len <= max_len { + LinePart { + part: part.to_string(), + len, + } + } else { + LinePart::default() + } +} + pub fn text_copied_hint(copy_destination: CopyDestination) -> LinePart { let hint = match copy_destination { CopyDestination::Command => "Text piped to external command", |
