Skip to main content

zellij_utils/input/
cli_assets.rs

1use crate::data::LayoutInfo;
2use crate::input::options::Options;
3use crate::pane_size::Size;
4use crate::{
5    home::{find_default_config_dir, get_theme_dir},
6    input::{config::Config, layout::Layout, theme::Themes},
7    setup::get_default_themes,
8};
9use serde::{Deserialize, Serialize};
10use std::collections::BTreeMap;
11use std::path::PathBuf;
12
13pub const HOST_TERMINAL_ENV_VARS: [&str; 7] = [
14    "TERM",
15    "TERM_PROGRAM",
16    "TERM_PROGRAM_VERSION",
17    "KITTY_WINDOW_ID",
18    "WEZTERM_PANE",
19    "ITERM_SESSION_ID",
20    "GHOSTTY_RESOURCES_DIR",
21];
22
23pub fn host_terminal_env() -> BTreeMap<String, String> {
24    host_terminal_env_from(|name| std::env::var(name).ok())
25}
26
27pub fn host_terminal_env_from<F: Fn(&str) -> Option<String>>(
28    lookup: F,
29) -> BTreeMap<String, String> {
30    HOST_TERMINAL_ENV_VARS
31        .iter()
32        .filter_map(|name| lookup(name).map(|value| (name.to_string(), value)))
33        .collect()
34}
35
36#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
37pub struct CliAssets {
38    pub config_file_path: Option<PathBuf>,
39    pub config_dir: Option<PathBuf>,
40    pub should_ignore_config: bool,
41    pub configuration_options: Option<Options>, // merged from everywhere: there are the source of truth
42    pub layout: Option<LayoutInfo>,
43    pub terminal_window_size: Size,
44    pub data_dir: Option<PathBuf>,
45    pub is_debug: bool,
46    pub max_panes: Option<usize>,
47    pub force_run_layout_commands: bool,
48    pub cwd: Option<PathBuf>,
49    pub host_terminal_env: BTreeMap<String, String>,
50}
51
52impl CliAssets {
53    pub fn load_config_and_layout(&self) -> (Config, Layout) {
54        let config = {
55            if self.should_ignore_config {
56                Config::from_default_assets().unwrap_or_else(|_| Default::default())
57            } else if let Some(ref path) = self.config_file_path {
58                let default_config =
59                    Config::from_default_assets().unwrap_or_else(|_| Default::default());
60                Config::from_path(path, Some(default_config.clone()))
61                    .unwrap_or_else(|_| default_config)
62            } else {
63                Config::from_default_assets().unwrap_or_else(|_| Default::default())
64            }
65        };
66
67        let (mut layout, mut config_with_merged_layout_opts) = {
68            let layout_dir = self
69                .configuration_options
70                .as_ref()
71                .and_then(|e| e.layout_dir.clone())
72                .or_else(|| config.options.layout_dir.clone())
73                .or_else(|| {
74                    self.config_dir
75                        .clone()
76                        .or_else(find_default_config_dir)
77                        .map(|dir| dir.join("layouts"))
78                });
79            self.layout.as_ref().and_then(|layout_info| {
80                Layout::from_layout_info_with_config(&layout_dir, layout_info, Some(config.clone()))
81                    .ok()
82            })
83        }
84        .map(|(layout, config)| (layout, config))
85        .unwrap_or_else(|| (Layout::default_layout_asset(), config));
86
87        if self.force_run_layout_commands {
88            layout.recursively_add_start_suspended(Some(false));
89        }
90
91        config_with_merged_layout_opts.themes = config_with_merged_layout_opts
92            .themes
93            .merge(get_default_themes());
94
95        let user_theme_dir = self
96            .configuration_options
97            .as_ref()
98            .and_then(|o| o.theme_dir.clone())
99            .or_else(|| {
100                config_with_merged_layout_opts
101                    .options
102                    .theme_dir
103                    .clone()
104                    .or_else(|| {
105                        get_theme_dir(self.config_dir.clone().or_else(find_default_config_dir))
106                    })
107                    .filter(|dir| dir.exists())
108            });
109        if let Some(themes) = user_theme_dir.and_then(|u| Themes::from_dir(u).ok()) {
110            config_with_merged_layout_opts.themes =
111                config_with_merged_layout_opts.themes.merge(themes);
112        }
113
114        (config_with_merged_layout_opts, layout)
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn only_whitelisted_host_terminal_variables_are_captured() {
124        let env = host_terminal_env_from(|name| match name {
125            "TERM" => Some("xterm-kitty".to_owned()),
126            "KITTY_WINDOW_ID" => Some("3".to_owned()),
127            "SECRET_TOKEN" => Some("hunter2".to_owned()),
128            _ => None,
129        });
130        assert_eq!(
131            env,
132            [
133                ("TERM".to_owned(), "xterm-kitty".to_owned()),
134                ("KITTY_WINDOW_ID".to_owned(), "3".to_owned())
135            ]
136            .into_iter()
137            .collect::<BTreeMap<String, String>>(),
138            "unset and non-whitelisted variables are left out"
139        );
140    }
141
142    #[test]
143    fn a_host_without_any_of_the_known_variables_yields_an_empty_env() {
144        assert!(host_terminal_env_from(|_| None).is_empty());
145    }
146}