use std::collections::BTreeMap; use zellij_tile::shim::new_tab; use crate::{config::ZellijState, render::FormattedPart, widgets::widget::Widget}; /// sl0p fork: a clickable "new tab" button for the bar. /// /// The tab strip is already a mouse surface -- clicking a tab switches to it, /// double-clicking renames it -- but the one thing you cannot do with the mouse /// is make a new one, which is the operation a tab strip in every other /// application offers. `{new_tab}` fills that hole. /// /// Configured like every other widget: /// /// ```kdl /// new_tab "#[bg=$bg,fg=$dim] + " /// ``` /// /// Rendering and hit-testing both come from this one string: the button covers /// exactly the columns it draws, because `process_widget_click` measures the /// same rendered output it placed. There is nothing here that can drift out of /// alignment with what you see -- unlike a pane frame's button, whose geometry /// is computed twice. pub struct NewTabWidget { format: FormattedPart, } impl NewTabWidget { pub fn new(config: &BTreeMap) -> Self { let format = match config.get("new_tab") { Some(format) => FormattedPart::from_format_string(format, config), // A default that is visible without any configuration: the widget // is only rendered if the user puts {new_tab} in a format string, // so an empty button would just be a confusing dead zone. None => FormattedPart::from_format_string(" + ", config), }; Self { format } } } impl Widget for NewTabWidget { fn process(&self, _name: &str, _state: &ZellijState) -> String { self.format.format_string(&self.format.content) } fn process_click(&self, _name: &str, _state: &ZellijState, _pos: usize, _is_double_click: bool) { // No name and no cwd: the same tab the `n` keybinding makes, so the // button is the mouse spelling of a thing that already exists rather // than a second, subtly different way to make a tab. The new tab takes // focus, which is what makes this a button and not a counter. new_tab(None::, None::); } } #[cfg(test)] mod test { use super::*; #[test] fn renders_the_configured_button() { let mut config = BTreeMap::new(); config.insert("new_tab".to_owned(), "#[fg=#ffffff] + ".to_owned()); let widget = NewTabWidget::new(&config); let output = widget.process("new_tab", &ZellijState::default()); assert!( output.contains(" + "), "expected the button text in {output:?}" ); } #[test] fn renders_something_clickable_without_configuration() { // an unconfigured {new_tab} must not be a zero-width click target let widget = NewTabWidget::new(&BTreeMap::new()); let output = widget.process("new_tab", &ZellijState::default()); assert!( console::measure_text_width(&output) > 0, "the default button has no width: {output:?}" ); } }