//! Handles cli and configuration options use crate::cli::Command; use crate::data::{InputMode, WebSharing}; use clap::{Args, ValueEnum}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use std::str::FromStr; use std::net::IpAddr; #[derive(Copy, Clone, Debug, PartialEq, Deserialize, Serialize, ValueEnum)] pub enum OnForceClose { #[serde(alias = "quit")] Quit, #[serde(alias = "detach")] Detach, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ValueEnum)] pub enum MobileLayoutConfiguration { #[serde(alias = "web")] Web, #[serde(alias = "always")] Always, #[serde(alias = "never")] Never, } impl Default for MobileLayoutConfiguration { fn default() -> Self { Self::Web } } impl FromStr for MobileLayoutConfiguration { type Err = String; fn from_str(s: &str) -> Result { match s { "Web" | "web" => Ok(Self::Web), "Always" | "always" => Ok(Self::Always), "Never" | "never" => Ok(Self::Never), _ => Err(format!("No such mobile_layout: {}", s)), } } } impl MobileLayoutConfiguration { pub fn should_route_to_mobile( self, is_web_client: bool, viewport_cols: usize, viewport_rows: usize, threshold_cols: u16, threshold_rows: u16, ) -> bool { let cols_reported = viewport_cols > 0; let rows_reported = viewport_rows > 0; let cols_match = cols_reported && (threshold_cols == 0 || viewport_cols <= threshold_cols as usize); let rows_match = rows_reported && (threshold_rows == 0 || viewport_rows <= threshold_rows as usize); let size_match = cols_match || rows_match; match self { MobileLayoutConfiguration::Always => size_match, MobileLayoutConfiguration::Never => false, MobileLayoutConfiguration::Web => is_web_client && size_match, } } pub fn may_route_web_client_to_mobile(self) -> bool { matches!( self, MobileLayoutConfiguration::Web | MobileLayoutConfiguration::Always ) } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ValueEnum)] pub enum NestedSessionHandling { #[serde(alias = "ask")] Ask, #[serde(alias = "fullscreen")] Fullscreen, #[serde(alias = "descend")] Descend, #[serde(alias = "never")] Never, } impl Default for NestedSessionHandling { fn default() -> Self { Self::Ask } } impl FromStr for NestedSessionHandling { type Err = String; fn from_str(s: &str) -> Result { match s { "Ask" | "ask" => Ok(Self::Ask), "Fullscreen" | "fullscreen" => Ok(Self::Fullscreen), "Descend" | "descend" => Ok(Self::Descend), "Never" | "never" => Ok(Self::Never), _ => Err(format!("No such nested_session_handling: {}", s)), } } } impl Default for OnForceClose { fn default() -> Self { Self::Detach } } impl FromStr for OnForceClose { type Err = Box; fn from_str(s: &str) -> Result { match s { "quit" => Ok(Self::Quit), "detach" => Ok(Self::Detach), e => Err(e.to_string().into()), } } } #[derive(ValueEnum, Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum PaneFrameStyle { Full, Titles, None, } impl Default for PaneFrameStyle { fn default() -> Self { PaneFrameStyle::Titles } } impl PaneFrameStyle { pub fn draws_full_frames(&self) -> bool { matches!(self, PaneFrameStyle::Full) } pub fn draws_titles(&self) -> bool { matches!(self, PaneFrameStyle::Titles) } pub fn from_options(options: &Options) -> Self { if options.pane_frames == Some(false) { return PaneFrameStyle::None; } match options.pane_frame_style { Some(PaneFrameStyle::Full) => PaneFrameStyle::Full, _ => PaneFrameStyle::Titles, } } } impl FromStr for PaneFrameStyle { type Err = Box; fn from_str(s: &str) -> Result { match s.trim().to_lowercase().as_str() { "full" => Ok(PaneFrameStyle::Full), "titles" => Ok(PaneFrameStyle::Titles), "none" => Ok(PaneFrameStyle::None), e => Err(format!( "Unknown pane frame style: '{}' (expected 'full', 'titles' or 'none')", e ) .into()), } } } #[derive(Clone, Default, Debug, PartialEq, Deserialize, Serialize, Args)] /// Options that can be set either through the config file, /// or cli flags - cli flags should take precedence over the config file /// TODO: In order to correctly parse boolean flags, this is currently split /// into Options and CliOptions, this could be a good canditate for a macro pub struct Options { /// Allow plugins to use a more simplified layout /// that is compatible with more fonts (true or false) #[clap(long, value_parser)] #[serde(default)] pub simplified_ui: Option, /// Set the default theme #[clap(long, value_parser)] pub theme: Option, /// Theme name to apply when the host terminal reports a dark color palette /// (CSI 2031 / DSR 997). Requires `theme_light` to also be set; if either /// is missing the static `theme` remains authoritative. #[clap(long, value_parser)] pub theme_dark: Option, /// Theme name to apply when the host terminal reports a light color palette /// (CSI 2031 / DSR 997). Requires `theme_dark` to also be set; if either /// is missing the static `theme` remains authoritative. #[clap(long, value_parser)] pub theme_light: Option, /// Set the default mode #[clap(long, value_enum, hide_possible_values = true, value_parser)] pub default_mode: Option, /// Set the default shell #[clap(long, value_parser)] pub default_shell: Option, /// Set the default cwd #[clap(long, value_parser)] pub default_cwd: Option, /// Set the default layout #[clap(long, value_parser)] pub default_layout: Option, /// Set the layout_dir, defaults to /// subdirectory of config dir #[clap(long, value_parser)] pub layout_dir: Option, /// Set the theme_dir, defaults to /// subdirectory of config dir #[clap(long, value_parser)] pub theme_dir: Option, #[clap(long, value_parser)] #[serde(default)] /// Set the handling of mouse events (true or false) /// Can be temporarily bypassed by the [SHIFT] key pub mouse_mode: Option, #[clap(long, value_parser)] #[serde(default)] /// Set display of the pane frames (true or false) pub pane_frames: Option, #[clap(long, value_enum, hide_possible_values = true, value_parser)] #[serde(default)] pub pane_frame_style: Option, #[clap(long, value_parser)] #[serde(default)] /// Mirror session when multiple users are connected (true or false) pub mirror_session: Option, /// Set behaviour on force close (quit or detach) #[clap(long, value_enum, hide_possible_values = true, value_parser)] pub on_force_close: Option, #[clap(long, value_parser)] pub scroll_buffer_size: Option, /// Switch to using a user supplied command for clipboard instead of OSC52 #[clap(long, value_parser)] #[serde(default)] pub copy_command: Option, /// OSC52 destination clipboard #[clap( long, value_enum, ignore_case = true, conflicts_with = "copy_command", value_parser )] #[serde(default)] pub copy_clipboard: Option, /// Automatically copy when selecting text (true or false) #[clap(long, value_parser)] #[serde(default)] pub copy_on_select: Option, /// Enable OSC8 hyperlink output (true or false) #[clap(long, value_parser)] #[serde(default)] pub osc8_hyperlinks: Option, /// Explicit full path to open the scrollback editor (default is $EDITOR or $VISUAL) #[clap(long, value_parser)] pub scrollback_editor: Option, /// The name of the session to create when starting Zellij #[clap(long, value_parser)] #[serde(default)] pub session_name: Option, /// Whether to attach to a session specified in "session-name" if it exists #[clap(long, value_parser)] #[serde(default)] pub attach_to_session: Option, /// Whether to lay out panes in a predefined set of layouts whenever possible #[clap(long, value_parser)] #[serde(default)] pub auto_layout: Option, /// Whether sessions should be serialized to the HD so that they can be later resurrected, /// default is true #[clap(long, value_parser)] #[serde(default)] pub session_serialization: Option, /// Whether pane viewports are serialized along with the session, default is false #[clap(long, value_parser)] #[serde(default)] pub serialize_pane_viewport: Option, /// Scrollback lines to serialize along with the pane viewport when serializing sessions, 0 /// defaults to the scrollback size. If this number is higher than the scrollback size, it will /// also default to the scrollback size #[clap(long, value_parser)] #[serde(default)] pub scrollback_lines_to_serialize: Option, /// Whether to use ANSI styled underlines #[clap(long, value_parser)] #[serde(default)] pub styled_underlines: Option, /// The interval at which to serialize sessions for resurrection (in seconds) #[clap(long, value_parser)] pub serialization_interval: Option, /// If true, will disable writing session metadata to disk #[clap(long, value_parser)] pub disable_session_metadata: Option, /// Whether to enable support for the Kitty keyboard protocol (must also be supported by the /// host terminal), defaults to true if the terminal supports it #[clap(long, value_parser)] #[serde(default)] pub support_kitty_keyboard_protocol: Option, /// Whether to enable support for the Kitty graphics (image) protocol (must also be supported /// by the host terminal), defaults to true if the terminal supports it #[clap(long, value_parser)] #[serde(default)] pub support_kitty_graphics_protocol: Option, /// Whether to make sure a local web server is running when a new Zellij session starts. /// This web server will allow creating new sessions and attaching to existing ones that have /// opted in to being shared in the browser. /// /// Note: a local web server can still be manually started from within a Zellij session or from the CLI. /// If this is not desired, one can use a version of Zellij compiled without /// web_server_capability /// /// Possible values: /// - true /// - false /// Default: false #[clap(long, value_parser)] #[serde(default)] pub web_server: Option, /// Whether to allow new sessions to be shared through a local web server, assuming one is /// running (see the `web_server` option for more details). /// /// Note: if Zellij was compiled without web_server_capability, this option will be locked to /// "disabled" /// /// Possible values: /// - "on" (new sessions will allow web sharing through the local web server if it /// is online) /// - "off" (new sessions will not allow web sharing unless they explicitly opt-in to it) /// - "disabled" (new sessions will not allow web sharing and will not be able to opt-in to it) /// Default: "off" #[clap(long, value_parser)] #[serde(default)] pub web_sharing: Option, /// Whether to stack panes when resizing beyond a certain size /// default is true #[clap(long, value_parser)] #[serde(default)] pub stacked_resize: Option, #[clap(long, value_parser)] #[serde(default)] pub stacked_pane_list: Option, /// Whether to show startup tips when starting a new session /// default is true #[clap(long, value_parser)] #[serde(default)] pub show_startup_tips: Option, /// Whether to show release notes on first run of a new version /// default is true #[clap(long, value_parser)] #[serde(default)] pub show_release_notes: Option, /// Whether to enable mouse hover effects and pane grouping functionality /// default is true #[clap(long, value_parser)] #[serde(default)] pub advanced_mouse_actions: Option, /// Whether Ctrl+ScrollWheel resizes panes /// default is true #[clap(long, value_parser)] #[serde(default)] pub mouse_scroll_resize: Option, /// Whether to enable mouse hover visual effects (frame highlight and help text) /// default is true #[clap(long, value_parser)] #[serde(default)] pub mouse_hover_effects: Option, /// Whether to show visual bell indicators (pane/tab frame flash and [!] suffix) /// default is true #[clap(long, value_parser)] #[serde(default)] pub visual_bell: Option, /// Whether to focus panes on mouse hover (true or false) /// default is false #[clap(long, value_parser)] #[serde(default)] pub focus_follows_mouse: Option, /// Whether clicking a pane to focus it also sends the click into the pane (true or false) /// default is false #[clap(long, value_parser)] #[serde(default)] pub mouse_click_through: Option, // these are intentionally excluded from the CLI options as they must be specified in the // configuration file pub web_server_ip: Option, pub web_server_port: Option, pub web_server_cert: Option, pub web_server_key: Option, pub enforce_https_for_localhost: Option, /// A command to run after the discovery of running commands when serializing, for the purpose /// of manipulating the command (eg. with a regex) before it gets serialized #[clap(long, value_parser)] pub post_command_discovery_hook: Option, /// Number of async worker tasks to spawn per active client. /// /// Allocating few tasks may result in resource contention and lags. Small values (around 4) /// should typically work best. Set to 0 to use the number of (physical) CPU cores. /// NOTE: This only applies to web clients at the moment. #[clap(long)] pub client_async_worker_tasks: Option, /// When a newly-attaching client should land in the mobile UI plugin (web, always, never) #[clap(long, value_enum, hide_possible_values = true, value_parser)] #[serde(default)] pub mobile_layout: Option, /// Column breakpoint for mobile_layout (0 to always match) #[clap(long, value_parser)] #[serde(default)] pub mobile_threshold_cols: Option, /// Row breakpoint for mobile_layout (0 to always match) #[clap(long, value_parser)] #[serde(default)] pub mobile_threshold_rows: Option, /// How to handle a nested Zellij session detected inside a pane /// (ask, fullscreen, descend, never) #[clap(long, value_enum, hide_possible_values = true, value_parser)] #[serde(default)] pub nested_session_handling: Option, } #[derive(ValueEnum, Deserialize, Serialize, Debug, Clone, Copy, PartialEq)] pub enum Clipboard { #[serde(alias = "system")] System, #[serde(alias = "primary")] Primary, } impl Default for Clipboard { fn default() -> Self { Self::System } } impl FromStr for Clipboard { type Err = String; fn from_str(s: &str) -> Result { match s { "System" | "system" => Ok(Self::System), "Primary" | "primary" => Ok(Self::Primary), _ => Err(format!("No such clipboard: {}", s)), } } } impl Options { pub fn from_yaml(from_yaml: Option) -> Options { if let Some(opts) = from_yaml { opts } else { Options::default() } } /// Merges two [`Options`] structs, a `Some` in `other` /// will supersede a `Some` in `self` // TODO: Maybe a good candidate for a macro? pub fn merge(&self, other: Options) -> Options { let mouse_mode = other.mouse_mode.or(self.mouse_mode); let pane_frames = other.pane_frames.or(self.pane_frames); let pane_frame_style = other.pane_frame_style.or(self.pane_frame_style); let auto_layout = other.auto_layout.or(self.auto_layout); let mirror_session = other.mirror_session.or(self.mirror_session); let simplified_ui = other.simplified_ui.or(self.simplified_ui); let default_mode = other.default_mode.or(self.default_mode); let default_shell = other.default_shell.or_else(|| self.default_shell.clone()); let default_cwd = other.default_cwd.or_else(|| self.default_cwd.clone()); let default_layout = other.default_layout.or_else(|| self.default_layout.clone()); let layout_dir = other.layout_dir.or_else(|| self.layout_dir.clone()); let theme_dir = other.theme_dir.or_else(|| self.theme_dir.clone()); let theme = other.theme.or_else(|| self.theme.clone()); let theme_dark = other.theme_dark.or_else(|| self.theme_dark.clone()); let theme_light = other.theme_light.or_else(|| self.theme_light.clone()); let on_force_close = other.on_force_close.or(self.on_force_close); let scroll_buffer_size = other.scroll_buffer_size.or(self.scroll_buffer_size); let copy_command = other.copy_command.or_else(|| self.copy_command.clone()); let copy_clipboard = other.copy_clipboard.or(self.copy_clipboard); let copy_on_select = other.copy_on_select.or(self.copy_on_select); let osc8_hyperlinks = other.osc8_hyperlinks.or(self.osc8_hyperlinks); let scrollback_editor = other .scrollback_editor .or_else(|| self.scrollback_editor.clone()); let session_name = other.session_name.or_else(|| self.session_name.clone()); let attach_to_session = other .attach_to_session .or_else(|| self.attach_to_session.clone()); let session_serialization = other.session_serialization.or(self.session_serialization); let serialize_pane_viewport = other .serialize_pane_viewport .or(self.serialize_pane_viewport); let scrollback_lines_to_serialize = other .scrollback_lines_to_serialize .or(self.scrollback_lines_to_serialize); let styled_underlines = other.styled_underlines.or(self.styled_underlines); let serialization_interval = other.serialization_interval.or(self.serialization_interval); let disable_session_metadata = other .disable_session_metadata .or(self.disable_session_metadata); let support_kitty_keyboard_protocol = other .support_kitty_keyboard_protocol .or(self.support_kitty_keyboard_protocol); let support_kitty_graphics_protocol = other .support_kitty_graphics_protocol .or(self.support_kitty_graphics_protocol); let web_server = other.web_server.or(self.web_server); let web_sharing = other.web_sharing.or(self.web_sharing); let stacked_resize = other.stacked_resize.or(self.stacked_resize); let stacked_pane_list = other.stacked_pane_list.or(self.stacked_pane_list); let show_startup_tips = other.show_startup_tips.or(self.show_startup_tips); let show_release_notes = other.show_release_notes.or(self.show_release_notes); let advanced_mouse_actions = other.advanced_mouse_actions.or(self.advanced_mouse_actions); let mouse_scroll_resize = other.mouse_scroll_resize.or(self.mouse_scroll_resize); let mouse_hover_effects = other.mouse_hover_effects.or(self.mouse_hover_effects); let visual_bell = other.visual_bell.or(self.visual_bell); let focus_follows_mouse = other.focus_follows_mouse.or(self.focus_follows_mouse); let mouse_click_through = other.mouse_click_through.or(self.mouse_click_through); let web_server_ip = other.web_server_ip.or(self.web_server_ip); let web_server_port = other.web_server_port.or(self.web_server_port); let web_server_cert = other .web_server_cert .or_else(|| self.web_server_cert.clone()); let web_server_key = other.web_server_key.or_else(|| self.web_server_key.clone()); let enforce_https_for_localhost = other .enforce_https_for_localhost .or(self.enforce_https_for_localhost); let post_command_discovery_hook = other .post_command_discovery_hook .or(self.post_command_discovery_hook.clone()); let client_async_worker_tasks = other .client_async_worker_tasks .or(self.client_async_worker_tasks); let mobile_layout = other.mobile_layout.or(self.mobile_layout); let mobile_threshold_cols = other.mobile_threshold_cols.or(self.mobile_threshold_cols); let mobile_threshold_rows = other.mobile_threshold_rows.or(self.mobile_threshold_rows); let nested_session_handling = other .nested_session_handling .or(self.nested_session_handling); Options { simplified_ui, theme, theme_dark, theme_light, default_mode, default_shell, default_cwd, default_layout, layout_dir, theme_dir, mouse_mode, pane_frames, pane_frame_style, mirror_session, on_force_close, scroll_buffer_size, copy_command, copy_clipboard, copy_on_select, osc8_hyperlinks, scrollback_editor, session_name, attach_to_session, auto_layout, session_serialization, serialize_pane_viewport, scrollback_lines_to_serialize, styled_underlines, serialization_interval, disable_session_metadata, support_kitty_keyboard_protocol, support_kitty_graphics_protocol, web_server, web_sharing, stacked_resize, stacked_pane_list, show_startup_tips, show_release_notes, advanced_mouse_actions, mouse_scroll_resize, mouse_hover_effects, visual_bell, focus_follows_mouse, mouse_click_through, web_server_ip, web_server_port, web_server_cert, web_server_key, enforce_https_for_localhost, post_command_discovery_hook, client_async_worker_tasks, mobile_layout, mobile_threshold_cols, mobile_threshold_rows, nested_session_handling, } } /// Merges two [`Options`] structs, /// - `Some` in `other` will supersede a `Some` in `self` /// - `Some(bool)` in `other` will toggle a `Some(bool)` in `self` // TODO: Maybe a good candidate for a macro? pub fn merge_from_cli(&self, other: Options) -> Options { let merge_bool = |opt_other: Option, opt_self: Option| { if opt_other.is_some() ^ opt_self.is_some() { opt_other.or(opt_self) } else if opt_other.is_some() && opt_self.is_some() { Some(opt_other.unwrap() ^ opt_self.unwrap()) } else { None } }; let simplified_ui = merge_bool(other.simplified_ui, self.simplified_ui); let mouse_mode = merge_bool(other.mouse_mode, self.mouse_mode); let pane_frames = merge_bool(other.pane_frames, self.pane_frames); let pane_frame_style = other.pane_frame_style.or(self.pane_frame_style); let auto_layout = merge_bool(other.auto_layout, self.auto_layout); let mirror_session = merge_bool(other.mirror_session, self.mirror_session); let session_serialization = merge_bool(other.session_serialization, self.session_serialization); let serialize_pane_viewport = merge_bool(other.serialize_pane_viewport, self.serialize_pane_viewport); let default_mode = other.default_mode.or(self.default_mode); let default_shell = other.default_shell.or_else(|| self.default_shell.clone()); let default_cwd = other.default_cwd.or_else(|| self.default_cwd.clone()); let default_layout = other.default_layout.or_else(|| self.default_layout.clone()); let layout_dir = other.layout_dir.or_else(|| self.layout_dir.clone()); let theme_dir = other.theme_dir.or_else(|| self.theme_dir.clone()); let theme = other.theme.or_else(|| self.theme.clone()); let theme_dark = other.theme_dark.or_else(|| self.theme_dark.clone()); let theme_light = other.theme_light.or_else(|| self.theme_light.clone()); let on_force_close = other.on_force_close.or(self.on_force_close); let scroll_buffer_size = other.scroll_buffer_size.or(self.scroll_buffer_size); let copy_command = other.copy_command.or_else(|| self.copy_command.clone()); let copy_clipboard = other.copy_clipboard.or(self.copy_clipboard); let copy_on_select = other.copy_on_select.or(self.copy_on_select); let osc8_hyperlinks = other.osc8_hyperlinks.or(self.osc8_hyperlinks); let scrollback_editor = other .scrollback_editor .or_else(|| self.scrollback_editor.clone()); let session_name = other.session_name.or_else(|| self.session_name.clone()); let attach_to_session = other .attach_to_session .or_else(|| self.attach_to_session.clone()); let scrollback_lines_to_serialize = other .scrollback_lines_to_serialize .or_else(|| self.scrollback_lines_to_serialize.clone()); let styled_underlines = other.styled_underlines.or(self.styled_underlines); let serialization_interval = other.serialization_interval.or(self.serialization_interval); let disable_session_metadata = other .disable_session_metadata .or(self.disable_session_metadata); let support_kitty_keyboard_protocol = other .support_kitty_keyboard_protocol .or(self.support_kitty_keyboard_protocol); let support_kitty_graphics_protocol = other .support_kitty_graphics_protocol .or(self.support_kitty_graphics_protocol); let web_server = other.web_server.or(self.web_server); let web_sharing = other.web_sharing.or(self.web_sharing); let stacked_resize = other.stacked_resize.or(self.stacked_resize); let stacked_pane_list = other.stacked_pane_list.or(self.stacked_pane_list); let show_startup_tips = other.show_startup_tips.or(self.show_startup_tips); let show_release_notes = other.show_release_notes.or(self.show_release_notes); let advanced_mouse_actions = other.advanced_mouse_actions.or(self.advanced_mouse_actions); let mouse_scroll_resize = other.mouse_scroll_resize.or(self.mouse_scroll_resize); let mouse_hover_effects = other.mouse_hover_effects.or(self.mouse_hover_effects); let visual_bell = other.visual_bell.or(self.visual_bell); let focus_follows_mouse = merge_bool(other.focus_follows_mouse, self.focus_follows_mouse); let mouse_click_through = merge_bool(other.mouse_click_through, self.mouse_click_through); let web_server_ip = other.web_server_ip.or(self.web_server_ip); let web_server_port = other.web_server_port.or(self.web_server_port); let web_server_cert = other .web_server_cert .or_else(|| self.web_server_cert.clone()); let web_server_key = other.web_server_key.or_else(|| self.web_server_key.clone()); let enforce_https_for_localhost = other .enforce_https_for_localhost .or(self.enforce_https_for_localhost); let post_command_discovery_hook = other .post_command_discovery_hook .or_else(|| self.post_command_discovery_hook.clone()); let client_async_worker_tasks = other .client_async_worker_tasks .or(self.client_async_worker_tasks); let mobile_layout = other.mobile_layout.or(self.mobile_layout); let mobile_threshold_cols = other.mobile_threshold_cols.or(self.mobile_threshold_cols); let mobile_threshold_rows = other.mobile_threshold_rows.or(self.mobile_threshold_rows); let nested_session_handling = other .nested_session_handling .or(self.nested_session_handling); Options { simplified_ui, theme, theme_dark, theme_light, default_mode, default_shell, default_cwd, default_layout, layout_dir, theme_dir, mouse_mode, pane_frames, pane_frame_style, mirror_session, on_force_close, scroll_buffer_size, copy_command, copy_clipboard, copy_on_select, osc8_hyperlinks, scrollback_editor, session_name, attach_to_session, auto_layout, session_serialization, serialize_pane_viewport, scrollback_lines_to_serialize, styled_underlines, serialization_interval, disable_session_metadata, support_kitty_keyboard_protocol, support_kitty_graphics_protocol, web_server, web_sharing, stacked_resize, stacked_pane_list, show_startup_tips, show_release_notes, advanced_mouse_actions, mouse_scroll_resize, mouse_hover_effects, visual_bell, focus_follows_mouse, mouse_click_through, web_server_ip, web_server_port, web_server_cert, web_server_key, enforce_https_for_localhost, post_command_discovery_hook, client_async_worker_tasks, mobile_layout, mobile_threshold_cols, mobile_threshold_rows, nested_session_handling, } } pub fn from_cli(&self, other: Option) -> Options { if let Some(Command::Options(options)) = other { Options::merge_from_cli(self, options.into()) } else { self.to_owned() } } } #[cfg(test)] mod tests { use super::*; const SMALL: (usize, usize) = (40, 20); const WIDE_SHORT: (usize, usize) = (200, 20); const TALL_NARROW: (usize, usize) = (40, 200); const LARGE: (usize, usize) = (200, 200); #[test] fn pane_frame_style_from_str_accepts_all_variants() { assert_eq!( "full".parse::().unwrap(), PaneFrameStyle::Full ); assert_eq!( "titles".parse::().unwrap(), PaneFrameStyle::Titles ); assert_eq!( "none".parse::().unwrap(), PaneFrameStyle::None ); assert_eq!( "NONE".parse::().unwrap(), PaneFrameStyle::None ); assert!("bogus".parse::().is_err()); } fn route( layout: MobileLayoutConfiguration, is_web: bool, viewport: (usize, usize), thresholds: (u16, u16), ) -> bool { layout.should_route_to_mobile(is_web, viewport.0, viewport.1, thresholds.0, thresholds.1) } #[test] fn never_never_routes_to_mobile() { for &is_web in &[true, false] { for &viewport in &[SMALL, WIDE_SHORT, TALL_NARROW, LARGE] { for &thresholds in &[(60, 30), (0, 0), (0, 30), (60, 0)] { assert!( !route(MobileLayoutConfiguration::Never, is_web, viewport, thresholds), "Never must never route (is_web={is_web} viewport={viewport:?} thresholds={thresholds:?})", ); } } } } #[test] fn web_requires_web_client() { assert!(route(MobileLayoutConfiguration::Web, true, SMALL, (60, 30))); assert!(!route( MobileLayoutConfiguration::Web, false, SMALL, (60, 30) )); } #[test] fn web_respects_size_in_either_dimension() { assert!(route( MobileLayoutConfiguration::Web, true, TALL_NARROW, (60, 30) )); assert!(route( MobileLayoutConfiguration::Web, true, WIDE_SHORT, (60, 30) )); assert!(!route( MobileLayoutConfiguration::Web, true, LARGE, (60, 30) )); } #[test] fn always_routes_any_client_on_size_match() { assert!(route( MobileLayoutConfiguration::Always, true, SMALL, (60, 30) )); assert!(route( MobileLayoutConfiguration::Always, false, SMALL, (60, 30) )); assert!(!route( MobileLayoutConfiguration::Always, true, LARGE, (60, 30) )); assert!(!route( MobileLayoutConfiguration::Always, false, LARGE, (60, 30) )); } #[test] fn zero_threshold_makes_dimension_unconditional() { assert!(route( MobileLayoutConfiguration::Always, false, LARGE, (0, 30) )); assert!(route( MobileLayoutConfiguration::Always, false, LARGE, (60, 0) )); assert!(route( MobileLayoutConfiguration::Always, false, LARGE, (0, 0) )); assert!(route(MobileLayoutConfiguration::Web, true, LARGE, (0, 0))); assert!(!route(MobileLayoutConfiguration::Web, false, LARGE, (0, 0))); } #[test] fn zero_viewport_is_treated_as_unknown() { assert!(!route( MobileLayoutConfiguration::Always, true, (0, 0), (60, 30) )); assert!(!route( MobileLayoutConfiguration::Web, true, (0, 0), (60, 30) )); assert!(!route( MobileLayoutConfiguration::Always, true, (0, 0), (0, 0) )); assert!(route( MobileLayoutConfiguration::Always, true, (40, 0), (60, 30) )); assert!(route( MobileLayoutConfiguration::Always, true, (0, 20), (60, 30) )); } #[test] fn boundary_inclusive_at_threshold() { assert!(route( MobileLayoutConfiguration::Always, false, (60, 200), (60, 30) )); assert!(route( MobileLayoutConfiguration::Always, false, (200, 30), (60, 30) )); assert!(!route( MobileLayoutConfiguration::Always, false, (61, 31), (60, 30) )); } #[test] fn from_str_accepts_canonical_and_lowercase() { assert_eq!( "web".parse::(), Ok(MobileLayoutConfiguration::Web) ); assert_eq!( "Web".parse::(), Ok(MobileLayoutConfiguration::Web) ); assert_eq!( "always".parse::(), Ok(MobileLayoutConfiguration::Always) ); assert_eq!( "never".parse::(), Ok(MobileLayoutConfiguration::Never) ); assert!("auto".parse::().is_err()); } #[test] fn default_is_web() { assert_eq!( MobileLayoutConfiguration::default(), MobileLayoutConfiguration::Web ); } }