diff options
| author | Aram Drevekenin <aram@poor.dev> | 2025-08-28 17:48:34 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-08-28 17:48:34 +0200 |
| commit | 3a26df6314cf5966ffb3065b97d4183b480d1441 (patch) | |
| tree | 0afc4bc62272f22c397ccab260a46bbfed4ac24f /zellij-utils/src/input | |
| parent | fix(web): avoid sending single meta keypresses, cleanup (#4376) (diff) | |
| download | sl0ppij-3a26df6314cf5966ffb3065b97d4183b480d1441.tar.gz sl0ppij-3a26df6314cf5966ffb3065b97d4183b480d1441.tar.xz sl0ppij-3a26df6314cf5966ffb3065b97d4183b480d1441.zip | |
refactor(client-server-contract): config + layout (#4383)
* commented out ConfigWrittenToDisk instructions
* commented out WriteConfigToDisk instructions
* commented out FailedToWriteConfigToDisk and WriteConfigToDisk instructions
* pass cli assets from client to server instead of parsed config - kind of working, lots of stuff still broken
* move ClientAttributes to the server side
* move ClientAttributes to the server side in Attach instruction as well
* move CliArgs and runtime options from NewClient -> move them to the server
* moved plugin_aliases to the server side
* moved initial config writing to the server side
* moved layout parsing to the server
* some cleanups
* move config file watching and live reload to the server
* move config file writing to the server side
* use saved config when attaching
* get web server to work with the new config roles
* some cleanups
* moar cleanups
* even moar cleanups
* yet more cleanups
* fix tests
* style(fmt): rustfmt
* style(fmt): rustfmt
* fix e2e tests
* docs(changelog): add PR
Diffstat (limited to 'zellij-utils/src/input')
| -rw-r--r-- | zellij-utils/src/input/cli_assets.rs | 86 | ||||
| -rw-r--r-- | zellij-utils/src/input/config.rs | 142 | ||||
| -rw-r--r-- | zellij-utils/src/input/layout.rs | 58 | ||||
| -rw-r--r-- | zellij-utils/src/input/mod.rs | 1 | ||||
| -rw-r--r-- | zellij-utils/src/input/options.rs | 70 |
5 files changed, 215 insertions, 142 deletions
diff --git a/zellij-utils/src/input/cli_assets.rs b/zellij-utils/src/input/cli_assets.rs new file mode 100644 index 00000000..0db1395b --- /dev/null +++ b/zellij-utils/src/input/cli_assets.rs @@ -0,0 +1,86 @@ +use crate::data::LayoutInfo; +use crate::input::options::Options; +use crate::pane_size::Size; +use crate::{ + home::find_default_config_dir, + input::{config::Config, layout::Layout, theme::Themes}, + setup::{get_default_themes, get_theme_dir}, +}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct CliAssets { + pub config_file_path: Option<PathBuf>, + pub config_dir: Option<PathBuf>, + pub should_ignore_config: bool, + pub configuration_options: Option<Options>, // merged from everywhere: there are the source of truth + pub layout: Option<LayoutInfo>, + pub terminal_window_size: Size, + pub data_dir: Option<PathBuf>, + pub is_debug: bool, + pub max_panes: Option<usize>, + pub force_run_layout_commands: bool, + pub cwd: Option<PathBuf>, +} + +impl CliAssets { + pub fn load_config_and_layout(&self) -> (Config, Layout) { + let config = { + if self.should_ignore_config { + Config::from_default_assets().unwrap_or_else(|_| Default::default()) + } else if let Some(ref path) = self.config_file_path { + let default_config = + Config::from_default_assets().unwrap_or_else(|_| Default::default()); + Config::from_path(path, Some(default_config.clone())) + .unwrap_or_else(|_| default_config) + } else { + Config::from_default_assets().unwrap_or_else(|_| Default::default()) + } + }; + + let (mut layout, mut config_with_merged_layout_opts) = { + let layout_dir = self + .configuration_options + .as_ref() + .and_then(|e| e.layout_dir.clone()) + .or_else(|| config.options.layout_dir.clone()) + .or_else(|| { + self.config_dir + .clone() + .or_else(find_default_config_dir) + .map(|dir| dir.join("layouts")) + }); + self.layout.as_ref().and_then(|layout_info| { + Layout::from_layout_info_with_config(&layout_dir, layout_info, Some(config.clone())) + .ok() + }) + } + .map(|(layout, config)| (layout, config)) + .unwrap_or_else(|| (Layout::default_layout_asset(), config)); + + if self.force_run_layout_commands { + layout.recursively_add_start_suspended(Some(false)); + } + + config_with_merged_layout_opts.themes = config_with_merged_layout_opts + .themes + .merge(get_default_themes()); + + let user_theme_dir = self + .configuration_options + .as_ref() + .and_then(|o| o.theme_dir.clone()) + .or_else(|| { + get_theme_dir(config_with_merged_layout_opts.options.theme_dir.clone()) + .or_else(find_default_config_dir) + .filter(|dir| dir.exists()) + }); + if let Some(themes) = user_theme_dir.and_then(|u| Themes::from_dir(u).ok()) { + config_with_merged_layout_opts.themes = + config_with_merged_layout_opts.themes.merge(themes); + } + + (config_with_merged_layout_opts, layout) + } +} diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs index 287d0e5f..1f407621 100644 --- a/zellij-utils/src/input/config.rs +++ b/zellij-utils/src/input/config.rs @@ -19,7 +19,7 @@ use crate::cli::{CliArgs, Command}; use crate::envs::EnvironmentVariables; use crate::{home, setup}; -const DEFAULT_CONFIG_FILE_NAME: &str = "config.kdl"; +pub const DEFAULT_CONFIG_FILE_NAME: &str = "config.kdl"; type ConfigResult = Result<Config, ConfigError>; @@ -243,26 +243,29 @@ impl Config { opts.config.clone().or_else(|| { opts.config_dir .clone() - .or_else(home::find_default_config_dir) + .or_else(|| { + home::try_create_home_config_dir(); + home::find_default_config_dir() + }) .map(|config_dir| config_dir.join(DEFAULT_CONFIG_FILE_NAME)) }) } pub fn default_config_file_path() -> Option<PathBuf> { home::find_default_config_dir().map(|config_dir| config_dir.join(DEFAULT_CONFIG_FILE_NAME)) } - pub fn write_config_to_disk(config: String, opts: &CliArgs) -> Result<Config, Option<PathBuf>> { + pub fn write_config_to_disk( + config: String, + config_file_path: &PathBuf, + ) -> Result<Config, Option<PathBuf>> { // if we fail, try to return the PathBuf of the file we were not able to write to + let config_file_path = config_file_path.clone(); Config::from_kdl(&config, None) .map_err(|e| { log::error!("Failed to parse config: {}", e); None }) .and_then(|parsed_config| { - let backed_up_file_name = Config::backup_current_config(&opts)?; - let config_file_path = Config::config_file_path(&opts).ok_or_else(|| { - log::error!("Config file path not found"); - None - })?; + let backed_up_file_name = Config::backup_current_config(&config_file_path)?; let config = match backed_up_file_name { Some(backed_up_file_name) => { format!( @@ -295,31 +298,28 @@ impl Config { }) } // returns true if the config was not previouly written to disk and we successfully wrote it - pub fn write_config_to_disk_if_it_does_not_exist(config: String, opts: &CliArgs) -> bool { - if opts.config.is_none() { - // if a config file path wasn't explicitly specified, we try to create the default - // config folder - home::try_create_home_config_dir(); - } - match Config::config_file_path(opts) { - Some(config_file_path) => { - if config_file_path.exists() { + pub fn write_config_to_disk_if_it_does_not_exist( + config: String, + config_file_path: &Option<PathBuf>, + ) -> bool { + let Some(config_file_path) = config_file_path.clone() else { + log::error!("Could not find file path to write config"); + return false; + }; + if config_file_path.exists() { + false + } else { + if let Err(e) = std::fs::write(&config_file_path, config.as_bytes()) { + log::error!("Failed to write config to disk: {}", e); + return false; + } + match std::fs::read_to_string(&config_file_path) { + Ok(written_config) => written_config == config, + Err(e) => { + log::error!("Failed to read written config: {}", e); false - } else { - if let Err(e) = std::fs::write(&config_file_path, config.as_bytes()) { - log::error!("Failed to write config to disk: {}", e); - return false; - } - match std::fs::read_to_string(&config_file_path) { - Ok(written_config) => written_config == config, - Err(e) => { - log::error!("Failed to read written config: {}", e); - false - }, - } - } - }, - None => false, + }, + } } } fn find_free_backup_file_name(config_file_path: &PathBuf) -> Option<PathBuf> { @@ -361,47 +361,45 @@ impl Config { }, } } - fn backup_current_config(opts: &CliArgs) -> Result<Option<PathBuf>, Option<PathBuf>> { + fn backup_current_config( + config_file_path: &PathBuf, + ) -> Result<Option<PathBuf>, Option<PathBuf>> { // if we fail, try to return the PathBuf of the file we were not able to write to - if let Some(config_file_path) = Config::config_file_path(&opts) { - match std::fs::read_to_string(&config_file_path) { - Ok(current_config) => { - let Some(backup_config_path) = - Config::find_free_backup_file_name(&config_file_path) - else { - log::error!("Failed to find a file name to back up the configuration to, ran out of files."); - return Err(None); - }; - if Config::backup_config_with_written_content_confirmation( - ¤t_config, - &config_file_path, - &backup_config_path, - ) { - Ok(Some(backup_config_path)) - } else { - log::error!( - "Failed to back up config file: {}", - backup_config_path.display() - ); - Err(Some(backup_config_path)) - } - }, - Err(e) => { - if e.kind() == std::io::ErrorKind::NotFound { - Ok(None) - } else { - log::error!( - "Failed to read current config {}: {}", - config_file_path.display(), - e - ); - Err(Some(config_file_path)) - } - }, - } - } else { - log::error!("No config file path found?"); - Err(None) + // if let Some(config_file_path) = Config::config_file_path(&opts) { + match std::fs::read_to_string(&config_file_path) { + Ok(current_config) => { + let Some(backup_config_path) = + Config::find_free_backup_file_name(&config_file_path) + else { + log::error!("Failed to find a file name to back up the configuration to, ran out of files."); + return Err(None); + }; + if Config::backup_config_with_written_content_confirmation( + ¤t_config, + &config_file_path, + &backup_config_path, + ) { + Ok(Some(backup_config_path)) + } else { + log::error!( + "Failed to back up config file: {}", + backup_config_path.display() + ); + Err(Some(backup_config_path)) + } + }, + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + Ok(None) + } else { + log::error!( + "Failed to read current config {}: {}", + config_file_path.display(), + e + ); + Err(Some(config_file_path.clone())) + } + }, } } fn autogen_config_message(backed_up_file_name: PathBuf) -> String { diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index e29ad94d..83704439 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -1248,6 +1248,50 @@ impl Layout { } layout } + pub fn from_layout_info_with_config( + layout_dir: &Option<PathBuf>, + layout_info: &LayoutInfo, + config: Option<Config>, + ) -> Result<(Layout, Config), ConfigError> { + let mut should_start_layout_commands_suspended = false; + let (path_to_raw_layout, raw_layout, raw_swap_layouts) = match layout_info { + LayoutInfo::File(layout_name_without_extension) => { + let layout_dir = layout_dir.clone().or_else(|| default_layout_dir()); + let (path_to_layout, stringified_layout, swap_layouts) = + Self::stringified_from_dir( + &PathBuf::from(layout_name_without_extension), + layout_dir.as_ref(), + )?; + (Some(path_to_layout), stringified_layout, swap_layouts) + }, + LayoutInfo::BuiltIn(layout_name) => { + let (path_to_layout, stringified_layout, swap_layouts) = + Self::stringified_from_default_assets(&PathBuf::from(layout_name))?; + (Some(path_to_layout), stringified_layout, swap_layouts) + }, + LayoutInfo::Url(url) => { + should_start_layout_commands_suspended = true; + (Some(url.clone()), Self::stringified_from_url(&url)?, None) + }, + LayoutInfo::Stringified(stringified_layout) => (None, stringified_layout.clone(), None), + }; + let mut layout = Layout::from_kdl( + &raw_layout, + path_to_raw_layout, + raw_swap_layouts + .as_ref() + .map(|(r, f)| (r.as_str(), f.as_str())), + None, + ); + if should_start_layout_commands_suspended { + layout + .iter_mut() + .next() + .map(|l| l.recursively_add_start_suspended_including_template(Some(true))); + } + let config = Config::from_kdl(&raw_layout, config)?; // this merges the two config, with + layout.map(|l| (l, config)) + } pub fn stringified_from_path_or_default( layout_path: Option<&PathBuf>, layout_dir: Option<PathBuf>, @@ -1369,6 +1413,20 @@ impl Layout { let config = Config::from_kdl(&raw_layout, Some(config))?; // this merges the two config, with Ok((layout, config)) } + pub fn default_layout_asset() -> Layout { + Layout::from_kdl( + &Self::stringified_default_from_assets().unwrap(), + None, + Some(( + "", + Self::stringified_default_swap_from_assets() + .unwrap() + .as_str(), + )), + None, + ) + .unwrap() + } pub fn from_str( raw: &str, path_to_raw_layout: String, diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs index 2ff8c206..69520fe2 100644 --- a/zellij-utils/src/input/mod.rs +++ b/zellij-utils/src/input/mod.rs @@ -1,4 +1,5 @@ pub mod actions; +pub mod cli_assets; pub mod command; pub mod config; pub mod keybinds; diff --git a/zellij-utils/src/input/options.rs b/zellij-utils/src/input/options.rs index 72b4e63b..e9035dc1 100644 --- a/zellij-utils/src/input/options.rs +++ b/zellij-utils/src/input/options.rs @@ -496,73 +496,3 @@ impl Options { } } } - -#[derive(Clone, Default, Debug, PartialEq, Args, Serialize, Deserialize)] -/// Options that can be set through cli flags -/// boolean flags end up toggling boolean options in `Options` -pub struct CliOptions { - /// Disable handling of mouse events - #[clap(long, conflicts_with("mouse-mode"), value_parser)] - pub disable_mouse_mode: bool, - /// Disable display of pane frames - #[clap(long, conflicts_with("pane-frames"), value_parser)] - pub no_pane_frames: bool, - #[clap(flatten)] - pub options: Options, -} - -impl From<CliOptions> for Options { - fn from(cli_options: CliOptions) -> Self { - let mut opts = cli_options.options; - - // TODO: what? - if cli_options.no_pane_frames { - opts.pane_frames = Some(false); - } - if cli_options.disable_mouse_mode { - opts.mouse_mode = Some(false); - } - - Self { - simplified_ui: opts.simplified_ui, - theme: opts.theme, - default_mode: opts.default_mode, - default_shell: opts.default_shell, - default_cwd: opts.default_cwd, - default_layout: opts.default_layout, - layout_dir: opts.layout_dir, - theme_dir: opts.theme_dir, - mouse_mode: opts.mouse_mode, - pane_frames: opts.pane_frames, - mirror_session: opts.mirror_session, - on_force_close: opts.on_force_close, - scroll_buffer_size: opts.scroll_buffer_size, - copy_command: opts.copy_command, - copy_clipboard: opts.copy_clipboard, - copy_on_select: opts.copy_on_select, - scrollback_editor: opts.scrollback_editor, - session_name: opts.session_name, - attach_to_session: opts.attach_to_session, - auto_layout: opts.auto_layout, - session_serialization: opts.session_serialization, - serialize_pane_viewport: opts.serialize_pane_viewport, - scrollback_lines_to_serialize: opts.scrollback_lines_to_serialize, - styled_underlines: opts.styled_underlines, - serialization_interval: opts.serialization_interval, - support_kitty_keyboard_protocol: opts.support_kitty_keyboard_protocol, - web_server: opts.web_server, - web_sharing: opts.web_sharing, - stacked_resize: opts.stacked_resize, - show_startup_tips: opts.show_startup_tips, - show_release_notes: opts.show_release_notes, - advanced_mouse_actions: opts.advanced_mouse_actions, - web_server_ip: opts.web_server_ip, - web_server_port: opts.web_server_port, - web_server_cert: opts.web_server_cert, - web_server_key: opts.web_server_key, - enforce_https_for_localhost: opts.enforce_https_for_localhost, - post_command_discovery_hook: opts.post_command_discovery_hook, - ..Default::default() - } - } -} |
