Skip to main content

zellij_utils/input/
options.rs

1//! Handles cli and configuration options
2use crate::cli::Command;
3use crate::data::{InputMode, WebSharing};
4use clap::{ArgEnum, Args};
5use serde::{Deserialize, Serialize};
6use std::path::PathBuf;
7use std::str::FromStr;
8
9use std::net::IpAddr;
10
11#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Serialize, ArgEnum)]
12pub enum OnForceClose {
13    #[serde(alias = "quit")]
14    Quit,
15    #[serde(alias = "detach")]
16    Detach,
17}
18
19impl Default for OnForceClose {
20    fn default() -> Self {
21        Self::Detach
22    }
23}
24
25impl FromStr for OnForceClose {
26    type Err = Box<dyn std::error::Error>;
27
28    fn from_str(s: &str) -> Result<Self, Self::Err> {
29        match s {
30            "quit" => Ok(Self::Quit),
31            "detach" => Ok(Self::Detach),
32            e => Err(e.to_string().into()),
33        }
34    }
35}
36
37#[derive(Clone, Default, Debug, PartialEq, Deserialize, Serialize, Args)]
38/// Options that can be set either through the config file,
39/// or cli flags - cli flags should take precedence over the config file
40/// TODO: In order to correctly parse boolean flags, this is currently split
41/// into Options and CliOptions, this could be a good canditate for a macro
42pub struct Options {
43    /// Allow plugins to use a more simplified layout
44    /// that is compatible with more fonts (true or false)
45    #[clap(long, value_parser)]
46    #[serde(default)]
47    pub simplified_ui: Option<bool>,
48    /// Set the default theme
49    #[clap(long, value_parser)]
50    pub theme: Option<String>,
51    /// Theme name to apply when the host terminal reports a dark color palette
52    /// (CSI 2031 / DSR 997). Requires `theme_light` to also be set; if either
53    /// is missing the static `theme` remains authoritative.
54    #[clap(long, value_parser)]
55    pub theme_dark: Option<String>,
56    /// Theme name to apply when the host terminal reports a light color palette
57    /// (CSI 2031 / DSR 997). Requires `theme_dark` to also be set; if either
58    /// is missing the static `theme` remains authoritative.
59    #[clap(long, value_parser)]
60    pub theme_light: Option<String>,
61    /// Set the default mode
62    #[clap(long, arg_enum, hide_possible_values = true, value_parser)]
63    pub default_mode: Option<InputMode>,
64    /// Set the default shell
65    #[clap(long, value_parser)]
66    pub default_shell: Option<PathBuf>,
67    /// Set the default cwd
68    #[clap(long, value_parser)]
69    pub default_cwd: Option<PathBuf>,
70    /// Set the default layout
71    #[clap(long, value_parser)]
72    pub default_layout: Option<PathBuf>,
73    /// Set the layout_dir, defaults to
74    /// subdirectory of config dir
75    #[clap(long, value_parser)]
76    pub layout_dir: Option<PathBuf>,
77    /// Set the theme_dir, defaults to
78    /// subdirectory of config dir
79    #[clap(long, value_parser)]
80    pub theme_dir: Option<PathBuf>,
81    #[clap(long, value_parser)]
82    #[serde(default)]
83    /// Set the handling of mouse events (true or false)
84    /// Can be temporarily bypassed by the [SHIFT] key
85    pub mouse_mode: Option<bool>,
86    #[clap(long, value_parser)]
87    #[serde(default)]
88    /// Set display of the pane frames (true or false)
89    pub pane_frames: Option<bool>,
90    #[clap(long, value_parser)]
91    #[serde(default)]
92    /// Mirror session when multiple users are connected (true or false)
93    pub mirror_session: Option<bool>,
94    /// Set behaviour on force close (quit or detach)
95    #[clap(long, arg_enum, hide_possible_values = true, value_parser)]
96    pub on_force_close: Option<OnForceClose>,
97    #[clap(long, value_parser)]
98    pub scroll_buffer_size: Option<usize>,
99
100    /// Switch to using a user supplied command for clipboard instead of OSC52
101    #[clap(long, value_parser)]
102    #[serde(default)]
103    pub copy_command: Option<String>,
104
105    /// OSC52 destination clipboard
106    #[clap(
107        long,
108        arg_enum,
109        ignore_case = true,
110        conflicts_with = "copy-command",
111        value_parser
112    )]
113    #[serde(default)]
114    pub copy_clipboard: Option<Clipboard>,
115
116    /// Automatically copy when selecting text (true or false)
117    #[clap(long, value_parser)]
118    #[serde(default)]
119    pub copy_on_select: Option<bool>,
120
121    /// Enable OSC8 hyperlink output (true or false)
122    #[clap(long, value_parser)]
123    #[serde(default)]
124    pub osc8_hyperlinks: Option<bool>,
125
126    /// Explicit full path to open the scrollback editor (default is $EDITOR or $VISUAL)
127    #[clap(long, value_parser)]
128    pub scrollback_editor: Option<PathBuf>,
129
130    /// The name of the session to create when starting Zellij
131    #[clap(long, value_parser)]
132    #[serde(default)]
133    pub session_name: Option<String>,
134
135    /// Whether to attach to a session specified in "session-name" if it exists
136    #[clap(long, value_parser)]
137    #[serde(default)]
138    pub attach_to_session: Option<bool>,
139
140    /// Whether to lay out panes in a predefined set of layouts whenever possible
141    #[clap(long, value_parser)]
142    #[serde(default)]
143    pub auto_layout: Option<bool>,
144
145    /// Whether sessions should be serialized to the HD so that they can be later resurrected,
146    /// default is true
147    #[clap(long, value_parser)]
148    #[serde(default)]
149    pub session_serialization: Option<bool>,
150
151    /// Whether pane viewports are serialized along with the session, default is false
152    #[clap(long, value_parser)]
153    #[serde(default)]
154    pub serialize_pane_viewport: Option<bool>,
155
156    /// Scrollback lines to serialize along with the pane viewport when serializing sessions, 0
157    /// defaults to the scrollback size. If this number is higher than the scrollback size, it will
158    /// also default to the scrollback size
159    #[clap(long, value_parser)]
160    #[serde(default)]
161    pub scrollback_lines_to_serialize: Option<usize>,
162
163    /// Whether to use ANSI styled underlines
164    #[clap(long, value_parser)]
165    #[serde(default)]
166    pub styled_underlines: Option<bool>,
167
168    /// The interval at which to serialize sessions for resurrection (in seconds)
169    #[clap(long, value_parser)]
170    pub serialization_interval: Option<u64>,
171
172    /// If true, will disable writing session metadata to disk
173    #[clap(long, value_parser)]
174    pub disable_session_metadata: Option<bool>,
175
176    /// Whether to enable support for the Kitty keyboard protocol (must also be supported by the
177    /// host terminal), defaults to true if the terminal supports it
178    #[clap(long, value_parser)]
179    #[serde(default)]
180    pub support_kitty_keyboard_protocol: Option<bool>,
181
182    /// Whether to make sure a local web server is running when a new Zellij session starts.
183    /// This web server will allow creating new sessions and attaching to existing ones that have
184    /// opted in to being shared in the browser.
185    ///
186    /// Note: a local web server can still be manually started from within a Zellij session or from the CLI.
187    /// If this is not desired, one can use a version of Zellij compiled without
188    /// web_server_capability
189    ///
190    /// Possible values:
191    /// - true
192    /// - false
193    /// Default: false
194    #[clap(long, value_parser)]
195    #[serde(default)]
196    pub web_server: Option<bool>,
197
198    /// Whether to allow new sessions to be shared through a local web server, assuming one is
199    /// running (see the `web_server` option for more details).
200    ///
201    /// Note: if Zellij was compiled without web_server_capability, this option will be locked to
202    /// "disabled"
203    ///
204    /// Possible values:
205    /// - "on" (new sessions will allow web sharing through the local web server if it
206    /// is online)
207    /// - "off" (new sessions will not allow web sharing unless they explicitly opt-in to it)
208    /// - "disabled" (new sessions will not allow web sharing and will not be able to opt-in to it)
209    /// Default: "off"
210    #[clap(long, value_parser)]
211    #[serde(default)]
212    pub web_sharing: Option<WebSharing>,
213
214    /// Whether to stack panes when resizing beyond a certain size
215    /// default is true
216    #[clap(long, value_parser)]
217    #[serde(default)]
218    pub stacked_resize: Option<bool>,
219
220    /// Whether to show startup tips when starting a new session
221    /// default is true
222    #[clap(long, value_parser)]
223    #[serde(default)]
224    pub show_startup_tips: Option<bool>,
225
226    /// Whether to show release notes on first run of a new version
227    /// default is true
228    #[clap(long, value_parser)]
229    #[serde(default)]
230    pub show_release_notes: Option<bool>,
231
232    /// Whether to enable mouse hover effects and pane grouping functionality
233    /// default is true
234    #[clap(long, value_parser)]
235    #[serde(default)]
236    pub advanced_mouse_actions: Option<bool>,
237
238    /// Whether to enable mouse hover visual effects (frame highlight and help text)
239    /// default is true
240    #[clap(long, value_parser)]
241    #[serde(default)]
242    pub mouse_hover_effects: Option<bool>,
243
244    /// Whether to show visual bell indicators (pane/tab frame flash and [!] suffix)
245    /// default is true
246    #[clap(long, value_parser)]
247    #[serde(default)]
248    pub visual_bell: Option<bool>,
249
250    /// Whether to focus panes on mouse hover (true or false)
251    /// default is false
252    #[clap(long, value_parser)]
253    #[serde(default)]
254    pub focus_follows_mouse: Option<bool>,
255
256    /// Whether clicking a pane to focus it also sends the click into the pane (true or false)
257    /// default is false
258    #[clap(long, value_parser)]
259    #[serde(default)]
260    pub mouse_click_through: Option<bool>,
261
262    // these are intentionally excluded from the CLI options as they must be specified in the
263    // configuration file
264    pub web_server_ip: Option<IpAddr>,
265    pub web_server_port: Option<u16>,
266    pub web_server_cert: Option<PathBuf>,
267    pub web_server_key: Option<PathBuf>,
268    pub enforce_https_for_localhost: Option<bool>,
269    /// A command to run after the discovery of running commands when serializing, for the purpose
270    /// of manipulating the command (eg. with a regex) before it gets serialized
271    #[clap(long, value_parser)]
272    pub post_command_discovery_hook: Option<String>,
273
274    /// Number of async worker tasks to spawn per active client.
275    ///
276    /// Allocating few tasks may result in resource contention and lags. Small values (around 4)
277    /// should typically work best. Set to 0 to use the number of (physical) CPU cores.
278    /// NOTE: This only applies to web clients at the moment.
279    #[clap(long)]
280    pub client_async_worker_tasks: Option<usize>,
281}
282
283#[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, Copy, PartialEq)]
284pub enum Clipboard {
285    #[serde(alias = "system")]
286    System,
287    #[serde(alias = "primary")]
288    Primary,
289}
290
291impl Default for Clipboard {
292    fn default() -> Self {
293        Self::System
294    }
295}
296
297impl FromStr for Clipboard {
298    type Err = String;
299    fn from_str(s: &str) -> Result<Self, Self::Err> {
300        match s {
301            "System" | "system" => Ok(Self::System),
302            "Primary" | "primary" => Ok(Self::Primary),
303            _ => Err(format!("No such clipboard: {}", s)),
304        }
305    }
306}
307
308impl Options {
309    pub fn from_yaml(from_yaml: Option<Options>) -> Options {
310        if let Some(opts) = from_yaml {
311            opts
312        } else {
313            Options::default()
314        }
315    }
316    /// Merges two [`Options`] structs, a `Some` in `other`
317    /// will supersede a `Some` in `self`
318    // TODO: Maybe a good candidate for a macro?
319    pub fn merge(&self, other: Options) -> Options {
320        let mouse_mode = other.mouse_mode.or(self.mouse_mode);
321        let pane_frames = other.pane_frames.or(self.pane_frames);
322        let auto_layout = other.auto_layout.or(self.auto_layout);
323        let mirror_session = other.mirror_session.or(self.mirror_session);
324        let simplified_ui = other.simplified_ui.or(self.simplified_ui);
325        let default_mode = other.default_mode.or(self.default_mode);
326        let default_shell = other.default_shell.or_else(|| self.default_shell.clone());
327        let default_cwd = other.default_cwd.or_else(|| self.default_cwd.clone());
328        let default_layout = other.default_layout.or_else(|| self.default_layout.clone());
329        let layout_dir = other.layout_dir.or_else(|| self.layout_dir.clone());
330        let theme_dir = other.theme_dir.or_else(|| self.theme_dir.clone());
331        let theme = other.theme.or_else(|| self.theme.clone());
332        let theme_dark = other.theme_dark.or_else(|| self.theme_dark.clone());
333        let theme_light = other.theme_light.or_else(|| self.theme_light.clone());
334        let on_force_close = other.on_force_close.or(self.on_force_close);
335        let scroll_buffer_size = other.scroll_buffer_size.or(self.scroll_buffer_size);
336        let copy_command = other.copy_command.or_else(|| self.copy_command.clone());
337        let copy_clipboard = other.copy_clipboard.or(self.copy_clipboard);
338        let copy_on_select = other.copy_on_select.or(self.copy_on_select);
339        let osc8_hyperlinks = other.osc8_hyperlinks.or(self.osc8_hyperlinks);
340        let scrollback_editor = other
341            .scrollback_editor
342            .or_else(|| self.scrollback_editor.clone());
343        let session_name = other.session_name.or_else(|| self.session_name.clone());
344        let attach_to_session = other
345            .attach_to_session
346            .or_else(|| self.attach_to_session.clone());
347        let session_serialization = other.session_serialization.or(self.session_serialization);
348        let serialize_pane_viewport = other
349            .serialize_pane_viewport
350            .or(self.serialize_pane_viewport);
351        let scrollback_lines_to_serialize = other
352            .scrollback_lines_to_serialize
353            .or(self.scrollback_lines_to_serialize);
354        let styled_underlines = other.styled_underlines.or(self.styled_underlines);
355        let serialization_interval = other.serialization_interval.or(self.serialization_interval);
356        let disable_session_metadata = other
357            .disable_session_metadata
358            .or(self.disable_session_metadata);
359        let support_kitty_keyboard_protocol = other
360            .support_kitty_keyboard_protocol
361            .or(self.support_kitty_keyboard_protocol);
362        let web_server = other.web_server.or(self.web_server);
363        let web_sharing = other.web_sharing.or(self.web_sharing);
364        let stacked_resize = other.stacked_resize.or(self.stacked_resize);
365        let show_startup_tips = other.show_startup_tips.or(self.show_startup_tips);
366        let show_release_notes = other.show_release_notes.or(self.show_release_notes);
367        let advanced_mouse_actions = other.advanced_mouse_actions.or(self.advanced_mouse_actions);
368        let mouse_hover_effects = other.mouse_hover_effects.or(self.mouse_hover_effects);
369        let visual_bell = other.visual_bell.or(self.visual_bell);
370        let focus_follows_mouse = other.focus_follows_mouse.or(self.focus_follows_mouse);
371        let mouse_click_through = other.mouse_click_through.or(self.mouse_click_through);
372        let web_server_ip = other.web_server_ip.or(self.web_server_ip);
373        let web_server_port = other.web_server_port.or(self.web_server_port);
374        let web_server_cert = other
375            .web_server_cert
376            .or_else(|| self.web_server_cert.clone());
377        let web_server_key = other.web_server_key.or_else(|| self.web_server_key.clone());
378        let enforce_https_for_localhost = other
379            .enforce_https_for_localhost
380            .or(self.enforce_https_for_localhost);
381        let post_command_discovery_hook = other
382            .post_command_discovery_hook
383            .or(self.post_command_discovery_hook.clone());
384        let client_async_worker_tasks = other
385            .client_async_worker_tasks
386            .or(self.client_async_worker_tasks);
387
388        Options {
389            simplified_ui,
390            theme,
391            theme_dark,
392            theme_light,
393            default_mode,
394            default_shell,
395            default_cwd,
396            default_layout,
397            layout_dir,
398            theme_dir,
399            mouse_mode,
400            pane_frames,
401            mirror_session,
402            on_force_close,
403            scroll_buffer_size,
404            copy_command,
405            copy_clipboard,
406            copy_on_select,
407            osc8_hyperlinks,
408            scrollback_editor,
409            session_name,
410            attach_to_session,
411            auto_layout,
412            session_serialization,
413            serialize_pane_viewport,
414            scrollback_lines_to_serialize,
415            styled_underlines,
416            serialization_interval,
417            disable_session_metadata,
418            support_kitty_keyboard_protocol,
419            web_server,
420            web_sharing,
421            stacked_resize,
422            show_startup_tips,
423            show_release_notes,
424            advanced_mouse_actions,
425            mouse_hover_effects,
426            visual_bell,
427            focus_follows_mouse,
428            mouse_click_through,
429            web_server_ip,
430            web_server_port,
431            web_server_cert,
432            web_server_key,
433            enforce_https_for_localhost,
434            post_command_discovery_hook,
435            client_async_worker_tasks,
436        }
437    }
438
439    /// Merges two [`Options`] structs,
440    /// - `Some` in `other` will supersede a `Some` in `self`
441    /// - `Some(bool)` in `other` will toggle a `Some(bool)` in `self`
442    // TODO: Maybe a good candidate for a macro?
443    pub fn merge_from_cli(&self, other: Options) -> Options {
444        let merge_bool = |opt_other: Option<bool>, opt_self: Option<bool>| {
445            if opt_other.is_some() ^ opt_self.is_some() {
446                opt_other.or(opt_self)
447            } else if opt_other.is_some() && opt_self.is_some() {
448                Some(opt_other.unwrap() ^ opt_self.unwrap())
449            } else {
450                None
451            }
452        };
453
454        let simplified_ui = merge_bool(other.simplified_ui, self.simplified_ui);
455        let mouse_mode = merge_bool(other.mouse_mode, self.mouse_mode);
456        let pane_frames = merge_bool(other.pane_frames, self.pane_frames);
457        let auto_layout = merge_bool(other.auto_layout, self.auto_layout);
458        let mirror_session = merge_bool(other.mirror_session, self.mirror_session);
459        let session_serialization =
460            merge_bool(other.session_serialization, self.session_serialization);
461        let serialize_pane_viewport =
462            merge_bool(other.serialize_pane_viewport, self.serialize_pane_viewport);
463
464        let default_mode = other.default_mode.or(self.default_mode);
465        let default_shell = other.default_shell.or_else(|| self.default_shell.clone());
466        let default_cwd = other.default_cwd.or_else(|| self.default_cwd.clone());
467        let default_layout = other.default_layout.or_else(|| self.default_layout.clone());
468        let layout_dir = other.layout_dir.or_else(|| self.layout_dir.clone());
469        let theme_dir = other.theme_dir.or_else(|| self.theme_dir.clone());
470        let theme = other.theme.or_else(|| self.theme.clone());
471        let theme_dark = other.theme_dark.or_else(|| self.theme_dark.clone());
472        let theme_light = other.theme_light.or_else(|| self.theme_light.clone());
473        let on_force_close = other.on_force_close.or(self.on_force_close);
474        let scroll_buffer_size = other.scroll_buffer_size.or(self.scroll_buffer_size);
475        let copy_command = other.copy_command.or_else(|| self.copy_command.clone());
476        let copy_clipboard = other.copy_clipboard.or(self.copy_clipboard);
477        let copy_on_select = other.copy_on_select.or(self.copy_on_select);
478        let osc8_hyperlinks = other.osc8_hyperlinks.or(self.osc8_hyperlinks);
479        let scrollback_editor = other
480            .scrollback_editor
481            .or_else(|| self.scrollback_editor.clone());
482        let session_name = other.session_name.or_else(|| self.session_name.clone());
483        let attach_to_session = other
484            .attach_to_session
485            .or_else(|| self.attach_to_session.clone());
486        let scrollback_lines_to_serialize = other
487            .scrollback_lines_to_serialize
488            .or_else(|| self.scrollback_lines_to_serialize.clone());
489        let styled_underlines = other.styled_underlines.or(self.styled_underlines);
490        let serialization_interval = other.serialization_interval.or(self.serialization_interval);
491        let disable_session_metadata = other
492            .disable_session_metadata
493            .or(self.disable_session_metadata);
494        let support_kitty_keyboard_protocol = other
495            .support_kitty_keyboard_protocol
496            .or(self.support_kitty_keyboard_protocol);
497        let web_server = other.web_server.or(self.web_server);
498        let web_sharing = other.web_sharing.or(self.web_sharing);
499        let stacked_resize = other.stacked_resize.or(self.stacked_resize);
500        let show_startup_tips = other.show_startup_tips.or(self.show_startup_tips);
501        let show_release_notes = other.show_release_notes.or(self.show_release_notes);
502        let advanced_mouse_actions = other.advanced_mouse_actions.or(self.advanced_mouse_actions);
503        let mouse_hover_effects = other.mouse_hover_effects.or(self.mouse_hover_effects);
504        let visual_bell = other.visual_bell.or(self.visual_bell);
505        let focus_follows_mouse = merge_bool(other.focus_follows_mouse, self.focus_follows_mouse);
506        let mouse_click_through = merge_bool(other.mouse_click_through, self.mouse_click_through);
507        let web_server_ip = other.web_server_ip.or(self.web_server_ip);
508        let web_server_port = other.web_server_port.or(self.web_server_port);
509        let web_server_cert = other
510            .web_server_cert
511            .or_else(|| self.web_server_cert.clone());
512        let web_server_key = other.web_server_key.or_else(|| self.web_server_key.clone());
513        let enforce_https_for_localhost = other
514            .enforce_https_for_localhost
515            .or(self.enforce_https_for_localhost);
516        let post_command_discovery_hook = other
517            .post_command_discovery_hook
518            .or_else(|| self.post_command_discovery_hook.clone());
519        let client_async_worker_tasks = other
520            .client_async_worker_tasks
521            .or(self.client_async_worker_tasks);
522
523        Options {
524            simplified_ui,
525            theme,
526            theme_dark,
527            theme_light,
528            default_mode,
529            default_shell,
530            default_cwd,
531            default_layout,
532            layout_dir,
533            theme_dir,
534            mouse_mode,
535            pane_frames,
536            mirror_session,
537            on_force_close,
538            scroll_buffer_size,
539            copy_command,
540            copy_clipboard,
541            copy_on_select,
542            osc8_hyperlinks,
543            scrollback_editor,
544            session_name,
545            attach_to_session,
546            auto_layout,
547            session_serialization,
548            serialize_pane_viewport,
549            scrollback_lines_to_serialize,
550            styled_underlines,
551            serialization_interval,
552            disable_session_metadata,
553            support_kitty_keyboard_protocol,
554            web_server,
555            web_sharing,
556            stacked_resize,
557            show_startup_tips,
558            show_release_notes,
559            advanced_mouse_actions,
560            mouse_hover_effects,
561            visual_bell,
562            focus_follows_mouse,
563            mouse_click_through,
564            web_server_ip,
565            web_server_port,
566            web_server_cert,
567            web_server_key,
568            enforce_https_for_localhost,
569            post_command_discovery_hook,
570            client_async_worker_tasks,
571        }
572    }
573
574    pub fn from_cli(&self, other: Option<Command>) -> Options {
575        if let Some(Command::Options(options)) = other {
576            Options::merge_from_cli(self, options.into())
577        } else {
578            self.to_owned()
579        }
580    }
581}