aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-utils/src/input
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2025-08-28 17:48:34 +0200
committerGitHub <noreply@github.com>2025-08-28 17:48:34 +0200
commit3a26df6314cf5966ffb3065b97d4183b480d1441 (patch)
tree0afc4bc62272f22c397ccab260a46bbfed4ac24f /zellij-utils/src/input
parentfix(web): avoid sending single meta keypresses, cleanup (#4376) (diff)
downloadsl0ppij-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.rs86
-rw-r--r--zellij-utils/src/input/config.rs142
-rw-r--r--zellij-utils/src/input/layout.rs58
-rw-r--r--zellij-utils/src/input/mod.rs1
-rw-r--r--zellij-utils/src/input/options.rs70
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(
- &current_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(
+ &current_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()
- }
- }
-}