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