diff --git a/Cargo.lock b/Cargo.lock index fb8c14207d..05d6fbe6a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -506,6 +506,12 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dfdf9179d546b55ff3f88c9d93ecfaa3e9760163da5a1080af5243230dbbb70" +[[package]] +name = "common-path" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101" + [[package]] name = "compact-bar" version = "0.1.0" @@ -892,6 +898,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" +[[package]] +name = "dissimilar" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "210ec60ae7d710bed8683e333e9d2855a8a56a3e9892b38bad3bb0d4d29b0d5e" + [[package]] name = "dynasm" version = "1.2.3" @@ -1007,6 +1019,16 @@ version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" +[[package]] +name = "expect-test" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d9eafeadd538e68fb28016364c9732d78e420b9ff8853fa5e4058861e9f8d3" +dependencies = [ + "dissimilar", + "once_cell", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -4638,8 +4660,11 @@ dependencies = [ "clap_complete", "colored", "colorsys", + "common-path", "crossbeam", "directories", + "expect-test", + "humantime", "include_dir", "insta", "interprocess", diff --git a/Cargo.toml b/Cargo.toml index 69ebc3bded..18d87f89e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,10 +77,10 @@ pkg-fmt = "tgz" [features] # See remarks in zellij_utils/Cargo.toml -default = [ "zellij-utils/plugins_from_target" ] -disable_automatic_asset_installation = [ "zellij-utils/disable_automatic_asset_installation" ] -unstable = [ "zellij-client/unstable", "zellij-utils/unstable" ] -singlepass = [ "zellij-server/singlepass" ] +default = ["zellij-utils/plugins_from_target"] +disable_automatic_asset_installation = ["zellij-utils/disable_automatic_asset_installation"] +unstable = ["zellij-client/unstable", "zellij-utils/unstable"] +singlepass = ["zellij-server/singlepass"] # uncomment this when developing plugins in the Zellij UI to make plugin compilation faster # [profile.dev.package."*"] diff --git a/docs/MANPAGE.md b/docs/MANPAGE.md index ad06d7ba7a..50c37d8061 100644 --- a/docs/MANPAGE.md +++ b/docs/MANPAGE.md @@ -155,6 +155,7 @@ ACTIONS Right, Up, Down). * __Clear__ - clears current screen. * __DumpScreen: __ - dumps the screen in the specified file. +* __DumpLayout: __ - dumps the screen in the specified or default file. * __EditScrollback__ - replaces the current pane with the scrollback buffer. * __ScrollUp__ - scrolls up 1 line in the focused pane. * __ScrollDown__ - scrolls down 1 line in the focused pane. diff --git a/src/commands.rs b/src/commands.rs index 96f49c315a..3bc280d347 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,10 +1,12 @@ use dialoguer::Confirm; -use std::{fs::File, io::prelude::*, path::PathBuf, process}; +use std::{fs::File, io::prelude::*, path::PathBuf, process, time::Duration}; use crate::sessions::{ - assert_session, assert_session_ne, get_active_session, get_name_generator, get_sessions, + assert_dead_session, assert_session, assert_session_ne, delete_session as delete_session_impl, + get_active_session, get_name_generator, get_resurrectable_sessions, get_sessions, get_sessions_sorted_by_mtime, kill_session as kill_session_impl, match_session_name, - print_sessions, print_sessions_with_index, session_exists, ActiveSession, SessionNameMatch, + print_sessions, print_sessions_with_index, resurrection_layout, session_exists, ActiveSession, + SessionNameMatch, }; use zellij_client::{ old_config_converter::{ @@ -49,7 +51,7 @@ pub(crate) fn kill_all_sessions(yes: bool) { } } for session in &sessions { - kill_session_impl(session); + kill_session_impl(&session.0); } process::exit(0); }, @@ -60,6 +62,39 @@ pub(crate) fn kill_all_sessions(yes: bool) { } } +pub(crate) fn delete_all_sessions(yes: bool, force: bool) { + let active_sessions: Vec = get_sessions() + .unwrap_or_default() + .iter() + .map(|s| s.0.clone()) + .collect(); + let resurrectable_sessions = get_resurrectable_sessions(); + let dead_sessions: Vec<_> = if force { + resurrectable_sessions + } else { + resurrectable_sessions + .iter() + .filter(|(name, _, _)| !active_sessions.contains(name)) + .cloned() + .collect() + }; + if !yes { + println!("WARNING: this action will delete all resurrectable sessions."); + if !Confirm::new() + .with_prompt("Do you want to continue?") + .interact() + .unwrap() + { + println!("Abort."); + process::exit(1); + } + } + for session in &dead_sessions { + delete_session_impl(&session.0, force); + } + process::exit(0); +} + pub(crate) fn kill_session(target_session: &Option) { match target_session { Some(target_session) => { @@ -74,6 +109,20 @@ pub(crate) fn kill_session(target_session: &Option) { } } +pub(crate) fn delete_session(target_session: &Option, force: bool) { + match target_session { + Some(target_session) => { + assert_dead_session(target_session, force); + delete_session_impl(target_session, force); + process::exit(0); + }, + None => { + println!("Please specify the session name to delete."); + process::exit(1); + }, + } +} + fn get_os_input( fn_get_os_input: fn() -> Result, ) -> OsInputOutput { @@ -117,6 +166,9 @@ fn find_indexed_session( } } +/// Client entrypoint for all [`zellij_utils::cli::CliAction`] +/// +/// Checks session to send the action to and attaches with client pub(crate) fn send_action_to_session( cli_action: zellij_utils::cli::CliAction, requested_session_name: Option, @@ -141,7 +193,11 @@ pub(crate) fn send_action_to_session( attach_with_cli_client(cli_action, &session_name, config); }, ActiveSession::Many => { - let existing_sessions = get_sessions().unwrap(); + let existing_sessions: Vec = get_sessions() + .unwrap_or_default() + .iter() + .map(|s| s.0.clone()) + .collect(); if let Some(session_name) = requested_session_name { if existing_sessions.contains(&session_name) { attach_with_cli_client(cli_action, &session_name, config); @@ -150,14 +206,14 @@ pub(crate) fn send_action_to_session( "Session '{}' not found. The following sessions are active:", session_name ); - print_sessions(existing_sessions); + list_sessions(false); std::process::exit(1); } } else if let Ok(session_name) = envs::get_session_name() { attach_with_cli_client(cli_action, &session_name, config); } else { eprintln!("Please specify the session name to send actions to. The following sessions are active:"); - print_sessions(existing_sessions); + list_sessions(false); std::process::exit(1); } }, @@ -293,7 +349,13 @@ fn attach_with_session_name( "Ambiguous selection: multiple sessions names start with '{}':", prefix ); - print_sessions(sessions); + print_sessions( + sessions + .iter() + .map(|s| (s.clone(), Duration::default(), false)) + .collect(), + false, + ); process::exit(1); }, SessionNameMatch::None => { @@ -310,7 +372,7 @@ fn attach_with_session_name( ActiveSession::One(session_name) => ClientInfo::Attach(session_name, config_options), ActiveSession::Many => { println!("Please specify the session to attach to, either by using the full name or a unique prefix.\nThe following sessions are active:"); - print_sessions(get_sessions().unwrap()); + list_sessions(false); process::exit(1); }, }, @@ -351,6 +413,7 @@ pub(crate) fn start_client(opts: CliArgs) { opts.command = Some(Command::Sessions(Sessions::Attach { session_name: reconnect_to_session.name.clone(), create: true, + force_run_commands: false, index: None, options: None, })); @@ -369,6 +432,7 @@ pub(crate) fn start_client(opts: CliArgs) { if let Some(Command::Sessions(Sessions::Attach { session_name, create, + force_run_commands, index, options, })) = opts.command.clone() @@ -387,10 +451,20 @@ pub(crate) fn start_client(opts: CliArgs) { .as_ref() .and_then(|s| session_exists(&s).ok()) .unwrap_or(false); - if create && !session_exists { + let resurrection_layout = + session_name.as_ref().and_then(|s| resurrection_layout(&s)); + if create && !session_exists && resurrection_layout.is_none() { session_name.clone().map(start_client_plan); } - attach_with_session_name(session_name, config_options.clone(), create) + match (session_name.as_ref(), resurrection_layout) { + (Some(session_name), Some(mut resurrection_layout)) if !session_exists => { + if force_run_commands { + resurrection_layout.recursively_add_start_suspended(Some(false)); + } + ClientInfo::Resurrect(session_name.clone(), resurrection_layout) + }, + _ => attach_with_session_name(session_name, config_options.clone(), create), + } }; if let Ok(val) = std::env::var(envs::SESSION_NAME_ENV_KEY) { @@ -399,9 +473,12 @@ pub(crate) fn start_client(opts: CliArgs) { } } - let attach_layout = match client { + let attach_layout = match &client { ClientInfo::Attach(_, _) => None, ClientInfo::New(_) => Some(layout), + ClientInfo::Resurrect(_session_name, layout_to_resurrect) => { + Some(layout_to_resurrect.clone()) + }, }; let tab_position_to_focus = reconnect_to_session @@ -457,9 +534,12 @@ pub(crate) fn start_client(opts: CliArgs) { config_options.clone(), true, ); - let attach_layout = match client { + let attach_layout = match &client { ClientInfo::Attach(_, _) => None, ClientInfo::New(_) => Some(layout), + ClientInfo::Resurrect(_, resurrection_layout) => { + Some(resurrection_layout.clone()) + }, }; reconnect_to_session = start_client_impl( Box::new(os_input), @@ -518,7 +598,16 @@ pub(crate) fn start_client(opts: CliArgs) { } fn generate_unique_session_name() -> String { - let sessions = get_sessions(); + let sessions = get_sessions().map(|sessions| { + sessions + .iter() + .map(|s| s.0.clone()) + .collect::>() + }); + let dead_sessions: Vec = get_resurrectable_sessions() + .iter() + .map(|(s, _, _)| s.clone()) + .collect(); let Ok(sessions) = sessions else { eprintln!("Failed to list existing sessions: {:?}", sessions); process::exit(1); @@ -526,7 +615,7 @@ fn generate_unique_session_name() -> String { let name = get_name_generator() .take(1000) - .find(|name| !sessions.contains(name)); + .find(|name| !sessions.contains(name) && !dead_sessions.contains(name)); if let Some(name) = name { return name; diff --git a/src/main.rs b/src/main.rs index da059b7fb5..a009ba3c69 100644 --- a/src/main.rs +++ b/src/main.rs @@ -87,14 +87,23 @@ fn main() { } } - if let Some(Command::Sessions(Sessions::ListSessions)) = opts.command { - commands::list_sessions(); + if let Some(Command::Sessions(Sessions::ListSessions { no_formatting })) = opts.command { + commands::list_sessions(no_formatting); } else if let Some(Command::Sessions(Sessions::KillAllSessions { yes })) = opts.command { commands::kill_all_sessions(yes); } else if let Some(Command::Sessions(Sessions::KillSession { ref target_session })) = opts.command { commands::kill_session(target_session); + } else if let Some(Command::Sessions(Sessions::DeleteAllSessions { yes, force })) = opts.command + { + commands::delete_all_sessions(yes, force); + } else if let Some(Command::Sessions(Sessions::DeleteSession { + ref target_session, + force, + })) = opts.command + { + commands::delete_session(target_session, force); } else if let Some(path) = opts.server { commands::start_server(path, opts.debug); } else { diff --git a/src/sessions.rs b/src/sessions.rs index 910bd333b4..d7b3263766 100644 --- a/src/sessions.rs +++ b/src/sessions.rs @@ -1,24 +1,36 @@ +use std::collections::HashMap; use std::os::unix::fs::FileTypeExt; -use std::time::SystemTime; +use std::time::{Duration, SystemTime}; use std::{fs, io, process}; use suggest::Suggest; use zellij_utils::{ anyhow, - consts::ZELLIJ_SOCK_DIR, + consts::{ + session_info_folder_for_session, session_layout_cache_file_name, + ZELLIJ_SESSION_INFO_CACHE_DIR, ZELLIJ_SOCK_DIR, + }, envs, + humantime::format_duration, + input::layout::Layout, interprocess::local_socket::LocalSocketStream, ipc::{ClientToServerMsg, IpcReceiverWithContext, IpcSenderWithContext, ServerToClientMsg}, }; -pub(crate) fn get_sessions() -> Result, io::ErrorKind> { +pub(crate) fn get_sessions() -> Result, io::ErrorKind> { match fs::read_dir(&*ZELLIJ_SOCK_DIR) { Ok(files) => { let mut sessions = Vec::new(); files.for_each(|file| { let file = file.unwrap(); let file_name = file.file_name().into_string().unwrap(); + let ctime = std::fs::metadata(&file.path()) + .ok() + .and_then(|f| f.created().ok()) + .and_then(|d| d.elapsed().ok()) + .unwrap_or_default(); + let duration = Duration::from_secs(ctime.as_secs()); if file.file_type().unwrap().is_socket() && assert_socket(&file_name) { - sessions.push(file_name); + sessions.push((file_name, duration)); } }); Ok(sessions) @@ -28,6 +40,70 @@ pub(crate) fn get_sessions() -> Result, io::ErrorKind> { } } +pub(crate) fn get_resurrectable_sessions() -> Vec<(String, Duration, Layout)> { + match fs::read_dir(&*ZELLIJ_SESSION_INFO_CACHE_DIR) { + Ok(files_in_session_info_folder) => { + let files_that_are_folders = files_in_session_info_folder + .filter_map(|f| f.ok().map(|f| f.path())) + .filter(|f| f.is_dir()); + files_that_are_folders + .filter_map(|folder_name| { + let layout_file_name = + session_layout_cache_file_name(&folder_name.display().to_string()); + let raw_layout = match std::fs::read_to_string(&layout_file_name) { + Ok(raw_layout) => raw_layout, + Err(e) => { + log::error!("Failed to read resurrection layout file: {:?}", e); + return None; + }, + }; + let ctime = match std::fs::metadata(&layout_file_name) + .and_then(|metadata| metadata.created()) + { + Ok(created) => Some(created), + Err(e) => { + log::error!( + "Failed to read created stamp of resurrection file: {:?}", + e + ); + None + }, + }; + let layout = match Layout::from_kdl( + &raw_layout, + layout_file_name.display().to_string(), + None, + None, + ) { + Ok(layout) => layout, + Err(e) => { + log::error!("Failed to parse resurrection layout file: {}", e); + return None; + }, + }; + let elapsed_duration = ctime + .map(|ctime| { + Duration::from_secs(ctime.elapsed().ok().unwrap_or_default().as_secs()) + }) + .unwrap_or_default(); + let session_name = folder_name + .file_name() + .map(|f| std::path::PathBuf::from(f).display().to_string())?; + Some((session_name, elapsed_duration, layout)) + }) + .collect() + }, + Err(e) => { + log::error!( + "Failed to read session_info cache folder: \"{:?}\": {:?}", + &*ZELLIJ_SESSION_INFO_CACHE_DIR, + e + ); + vec![] + }, + } +} + pub(crate) fn get_sessions_sorted_by_mtime() -> anyhow::Result> { match fs::read_dir(&*ZELLIJ_SOCK_DIR) { Ok(files) => { @@ -70,16 +146,39 @@ fn assert_socket(name: &str) -> bool { } } -pub(crate) fn print_sessions(sessions: Vec) { +pub(crate) fn print_sessions(mut sessions: Vec<(String, Duration, bool)>, no_formatting: bool) { + // (session_name, timestamp, is_dead) let curr_session = envs::get_session_name().unwrap_or_else(|_| "".into()); - sessions.iter().for_each(|session| { - let suffix = if curr_session == *session { - " (current)" - } else { - "" - }; - println!("{}{}", session, suffix); - }) + sessions.sort_by(|a, b| a.1.cmp(&b.1)); + sessions + .iter() + .for_each(|(session_name, timestamp, is_dead)| { + if no_formatting { + let suffix = if curr_session == *session_name { + format!("(current)") + } else if *is_dead { + format!("(EXITED - attach to resurrect)") + } else { + String::new() + }; + let timestamp = format!("[Created {} ago]", format_duration(*timestamp)); + println!("{} {} {}", session_name, timestamp, suffix); + } else { + let formatted_session_name = format!("\u{1b}[32;1m{}\u{1b}[m", session_name); + let suffix = if curr_session == *session_name { + format!("(current)") + } else if *is_dead { + format!("(\u{1b}[31;1mEXITED\u{1b}[m - attach to resurrect)") + } else { + String::new() + }; + let timestamp = format!( + "[Created \u{1b}[35;1m{}\u{1b}[m ago]", + format_duration(*timestamp) + ); + println!("{} {} {}", formatted_session_name, timestamp, suffix); + } + }) } pub(crate) fn print_sessions_with_index(sessions: Vec) { @@ -103,7 +202,7 @@ pub(crate) enum ActiveSession { pub(crate) fn get_active_session() -> ActiveSession { match get_sessions() { Ok(sessions) if sessions.is_empty() => ActiveSession::None, - Ok(mut sessions) if sessions.len() == 1 => ActiveSession::One(sessions.pop().unwrap()), + Ok(mut sessions) if sessions.len() == 1 => ActiveSession::One(sessions.pop().unwrap().0), Ok(_) => ActiveSession::Many, Err(e) => { eprintln!("Error occurred: {:?}", e); @@ -125,15 +224,53 @@ pub(crate) fn kill_session(name: &str) { }; } -pub(crate) fn list_sessions() { +pub(crate) fn delete_session(name: &str, force: bool) { + if force { + let path = &*ZELLIJ_SOCK_DIR.join(name); + let _ = LocalSocketStream::connect(path).map(|stream| { + IpcSenderWithContext::new(stream) + .send(ClientToServerMsg::KillSession) + .ok(); + }); + } + if let Err(e) = std::fs::remove_dir_all(session_info_folder_for_session(name)) { + if e.kind() == std::io::ErrorKind::NotFound { + eprintln!("Session: {:?} not found.", name); + process::exit(2); + } else { + log::error!("Failed to remove session {:?}: {:?}", name, e); + } + } else { + println!("Session: {:?} successfully deleted.", name); + } +} + +pub(crate) fn list_sessions(no_formatting: bool) { let exit_code = match get_sessions() { - Ok(sessions) if !sessions.is_empty() => { - print_sessions(sessions); - 0 - }, - Ok(_) => { - eprintln!("No active zellij sessions found."); - 1 + Ok(running_sessions) => { + let resurrectable_sessions = get_resurrectable_sessions(); + let mut all_sessions: HashMap = resurrectable_sessions + .iter() + .map(|(name, timestamp, _layout)| (name.clone(), (timestamp.clone(), true))) + .collect(); + for (session_name, duration) in running_sessions { + all_sessions.insert(session_name.clone(), (duration, false)); + } + if all_sessions.is_empty() { + eprintln!("No active zellij sessions found."); + 1 + } else { + print_sessions( + all_sessions + .iter() + .map(|(name, (timestamp, is_dead))| { + (name.clone(), timestamp.clone(), *is_dead) + }) + .collect(), + no_formatting, + ); + 0 + } }, Err(e) => { eprintln!("Error occurred: {:?}", e); @@ -154,19 +291,22 @@ pub enum SessionNameMatch { pub(crate) fn match_session_name(prefix: &str) -> Result { let sessions = get_sessions()?; - let filtered_sessions: Vec<_> = sessions.iter().filter(|s| s.starts_with(prefix)).collect(); + let filtered_sessions: Vec<_> = sessions + .iter() + .filter(|s| s.0.starts_with(prefix)) + .collect(); - if filtered_sessions.iter().any(|s| *s == prefix) { + if filtered_sessions.iter().any(|s| s.0 == prefix) { return Ok(SessionNameMatch::Exact(prefix.to_string())); } Ok({ match &filtered_sessions[..] { [] => SessionNameMatch::None, - [s] => SessionNameMatch::UniquePrefix(s.to_string()), - _ => { - SessionNameMatch::AmbiguousPrefix(filtered_sessions.into_iter().cloned().collect()) - }, + [s] => SessionNameMatch::UniquePrefix(s.0.to_string()), + _ => SessionNameMatch::AmbiguousPrefix( + filtered_sessions.into_iter().map(|s| s.0.clone()).collect(), + ), } }) } @@ -179,6 +319,20 @@ pub(crate) fn session_exists(name: &str) -> Result { } } +// if the session is resurrecable, the returned layout is the one to be used to resurrect it +pub(crate) fn resurrection_layout(session_name_to_resurrect: &str) -> Option { + let resurrectable_sessions = get_resurrectable_sessions(); + resurrectable_sessions + .iter() + .find_map(|(name, _timestamp, layout)| { + if name == session_name_to_resurrect { + Some(layout.clone()) + } else { + None + } + }) +} + pub(crate) fn assert_session(name: &str) { match session_exists(name) { Ok(result) => { @@ -186,7 +340,13 @@ pub(crate) fn assert_session(name: &str) { return; } else { println!("No session named {:?} found.", name); - if let Some(sugg) = get_sessions().unwrap().suggest(name) { + if let Some(sugg) = get_sessions() + .unwrap() + .iter() + .map(|s| s.0.clone()) + .collect::>() + .suggest(name) + { println!(" help: Did you mean `{}`?", sugg); } } @@ -198,6 +358,28 @@ pub(crate) fn assert_session(name: &str) { process::exit(1); } +pub(crate) fn assert_dead_session(name: &str, force: bool) { + match session_exists(name) { + Ok(exists) => { + if exists && !force { + println!( + "A session by the name {:?} exists and is active, use --force to delete it.", + name + ) + } else if exists && force { + println!("A session by the name {:?} exists and is active, but will be force killed and deleted.", name); + return; + } else { + return; + } + }, + Err(e) => { + eprintln!("Error occurred: {:?}", e); + }, + }; + process::exit(1); +} + pub(crate) fn assert_session_ne(name: &str) { if name.trim().is_empty() { eprintln!("Session name cannot be empty. Please provide a specific session name."); @@ -213,7 +395,14 @@ pub(crate) fn assert_session_ne(name: &str) { } match session_exists(name) { - Ok(result) if !result => return, + Ok(result) if !result => { + let resurrectable_sessions = get_resurrectable_sessions(); + if resurrectable_sessions.iter().find(|(s, _, _)| s == name).is_some() { + println!("Session with name {:?} already exists, but is dead. Use the attach command to resurrect it or, the delete-session command to kill it or specify a different name.", name); + } else { + return + } + } Ok(_) => println!("Session with name {:?} already exists. Use attach command to connect to it or specify a different name.", name), Err(e) => eprintln!("Error occurred: {:?}", e), }; diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs index b34138b959..c5110b6913 100644 --- a/src/tests/e2e/cases.rs +++ b/src/tests/e2e/cases.rs @@ -990,6 +990,125 @@ pub fn detach_and_attach_session() { assert_snapshot!(last_snapshot); } +#[test] +#[ignore] +pub fn quit_and_resurrect_session() { + let fake_win_size = Size { + cols: 120, + rows: 24, + }; + let mut test_attempts = 10; + let layout_name = "layout_for_resurrection.kdl"; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + let mut runner = RemoteRunner::new_mirrored_session_with_layout(fake_win_size, layout_name) + .add_step(Step { + name: "Wait for session to be serialized", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.snapshot_contains("Waiting to run: top") { + std::thread::sleep(std::time::Duration::from_millis(5000)); // wait for + // serialization + remote_terminal.send_key(&QUIT); + step_is_complete = true; + } + step_is_complete + }, + }) + .add_step(Step { + name: "Resurrect session by attaching", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.snapshot_contains("Bye from Zellij!") { + remote_terminal.attach_to_original_session(); + step_is_complete = true; + } + step_is_complete + }, + }); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { + name: "Wait for session to be resurrected", + instruction: |remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.snapshot_contains("(FLOATING PANES VISIBLE)") { + step_is_complete = true; + } + step_is_complete + }, + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; + let last_snapshot = account_for_races_in_snapshot(last_snapshot); + assert_snapshot!(last_snapshot); +} + +#[test] +#[ignore] +pub fn quit_and_resurrect_session_with_viewport_serialization() { + let fake_win_size = Size { + cols: 120, + rows: 24, + }; + let mut test_attempts = 10; + let layout_name = "layout_for_resurrection.kdl"; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + let mut runner = RemoteRunner::new_mirrored_session_with_layout_and_viewport_serialization( + fake_win_size, + layout_name, + ) + .add_step(Step { + name: "Wait for session to be serialized", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.snapshot_contains("Waiting to run: top") { + std::thread::sleep(std::time::Duration::from_millis(5000)); // wait for + // serialization + remote_terminal.send_key(&QUIT); + step_is_complete = true; + } + step_is_complete + }, + }) + .add_step(Step { + name: "Resurrect session by attaching", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.snapshot_contains("Bye from Zellij!") { + remote_terminal.attach_to_original_session(); + step_is_complete = true; + } + step_is_complete + }, + }); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { + name: "Wait for session to be resurrected", + instruction: |remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.snapshot_contains("(FLOATING PANES VISIBLE)") { + step_is_complete = true; + } + step_is_complete + }, + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; + let last_snapshot = account_for_races_in_snapshot(last_snapshot); + assert_snapshot!(last_snapshot); +} + #[test] #[ignore] pub fn status_bar_loads_custom_keybindings() { diff --git a/src/tests/e2e/remote_runner.rs b/src/tests/e2e/remote_runner.rs index 2b6d82298b..0da5bce7d5 100644 --- a/src/tests/e2e/remote_runner.rs +++ b/src/tests/e2e/remote_runner.rs @@ -66,6 +66,10 @@ fn stop_zellij(channel: &mut ssh2::Channel) { channel.write_all(b"killall -KILL zellij\n").unwrap(); channel.write_all(b"rm -rf /tmp/*\n").unwrap(); // remove temporary artifacts from previous // tests + channel.write_all(b"rm -rf /tmp/*\n").unwrap(); // remove temporary artifacts from previous + channel + .write_all(b"rm -rf ~/.cache/zellij/*/session_info\n") + .unwrap(); } fn start_zellij(channel: &mut ssh2::Channel) { @@ -98,6 +102,47 @@ fn start_zellij_mirrored_session(channel: &mut ssh2::Channel) { std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN } +fn start_zellij_mirrored_session_with_layout(channel: &mut ssh2::Channel, layout_file_name: &str) { + stop_zellij(channel); + channel + .write_all( + format!( + "{} {} --session {} --data-dir {} --layout {} options --mirror-session true\n", + SET_ENV_VARIABLES, + ZELLIJ_EXECUTABLE_LOCATION, + SESSION_NAME, + ZELLIJ_DATA_DIR, + format!("{}/{}", ZELLIJ_FIXTURE_PATH, layout_file_name) + ) + .as_bytes(), + ) + .unwrap(); + channel.flush().unwrap(); + std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN +} + +fn start_zellij_mirrored_session_with_layout_and_viewport_serialization( + channel: &mut ssh2::Channel, + layout_file_name: &str, +) { + stop_zellij(channel); + channel + .write_all( + format!( + "{} {} --session {} --data-dir {} --layout {} options --mirror-session true --serialize-pane-viewport true\n", + SET_ENV_VARIABLES, + ZELLIJ_EXECUTABLE_LOCATION, + SESSION_NAME, + ZELLIJ_DATA_DIR, + format!("{}/{}", ZELLIJ_FIXTURE_PATH, layout_file_name) + ) + .as_bytes(), + ) + .unwrap(); + channel.flush().unwrap(); + std::thread::sleep(std::time::Duration::from_secs(1)); // wait until Zellij stops parsing startup ANSI codes from the terminal STDIN +} + fn start_zellij_in_session(channel: &mut ssh2::Channel, session_name: &str, mirrored: bool) { stop_zellij(channel); channel @@ -464,6 +509,84 @@ impl RemoteRunner { reader_thread, } } + pub fn new_mirrored_session_with_layout(win_size: Size, layout_file_name: &str) -> Self { + let sess = ssh_connect(); + let mut channel = sess.channel_session().unwrap(); + let mut rows = Dimension::fixed(win_size.rows); + let mut cols = Dimension::fixed(win_size.cols); + rows.set_inner(win_size.rows); + cols.set_inner(win_size.cols); + let pane_geom = PaneGeom { + x: 0, + y: 0, + rows, + cols, + is_stacked: false, + }; + setup_remote_environment(&mut channel, win_size); + start_zellij_mirrored_session_with_layout(&mut channel, layout_file_name); + let channel = Arc::new(Mutex::new(channel)); + let last_snapshot = Arc::new(Mutex::new(String::new())); + let cursor_coordinates = Arc::new(Mutex::new((0, 0))); + sess.set_blocking(false); + let reader_thread = + read_from_channel(&channel, &last_snapshot, &cursor_coordinates, &pane_geom); + RemoteRunner { + steps: vec![], + channel, + currently_running_step: None, + current_step_index: 0, + retries_left: RETRIES, + retry_pause_ms: 100, + test_timed_out: false, + panic_on_no_retries_left: true, + last_snapshot, + cursor_coordinates, + reader_thread, + } + } + pub fn new_mirrored_session_with_layout_and_viewport_serialization( + win_size: Size, + layout_file_name: &str, + ) -> Self { + let sess = ssh_connect(); + let mut channel = sess.channel_session().unwrap(); + let mut rows = Dimension::fixed(win_size.rows); + let mut cols = Dimension::fixed(win_size.cols); + rows.set_inner(win_size.rows); + cols.set_inner(win_size.cols); + let pane_geom = PaneGeom { + x: 0, + y: 0, + rows, + cols, + is_stacked: false, + }; + setup_remote_environment(&mut channel, win_size); + start_zellij_mirrored_session_with_layout_and_viewport_serialization( + &mut channel, + layout_file_name, + ); + let channel = Arc::new(Mutex::new(channel)); + let last_snapshot = Arc::new(Mutex::new(String::new())); + let cursor_coordinates = Arc::new(Mutex::new((0, 0))); + sess.set_blocking(false); + let reader_thread = + read_from_channel(&channel, &last_snapshot, &cursor_coordinates, &pane_geom); + RemoteRunner { + steps: vec![], + channel, + currently_running_step: None, + current_step_index: 0, + retries_left: RETRIES, + retry_pause_ms: 100, + test_timed_out: false, + panic_on_no_retries_left: true, + last_snapshot, + cursor_coordinates, + reader_thread, + } + } pub fn kill_running_sessions(win_size: Size) { let sess = ssh_connect(); let mut channel = sess.channel_session().unwrap(); diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session.snap new file mode 100644 index 0000000000..a7c58010ec --- /dev/null +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session.snap @@ -0,0 +1,29 @@ +--- +source: src/tests/e2e/cases.rs +assertion_line: 1048 +expression: last_snapshot +--- + Zellij (e2e-test)  Tab #1  Tab #2  Tab #3  Tab #4  +┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐ +│$ ││$ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ┌ Pane #4 ───────────────────────────────────────────────┐ │ +│ │$ │ │ +│ │ ┌ top ───────────────────────────────────────────────────┐ │ +│ │ │ │ │ +│ │ │ │─┐ │ +│ │ │ │ │────────────────────────────┘ +│ │ │ Waiting to run: top │ │────────────────────────────┐ +│ │ │ │ │ │ +│ │ │ to run, to exit │ │ │ +│ └─│ │ │ │ +│ │ │ │ │ +│ └─ to run, to exit ─────────────────────┘ │ │ +│ │ │ │ +│ └────────────────────────────────────────────────────────┘ │ +│ ││ │ +└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  + (FLOATING PANES VISIBLE): Press Ctrl+p, to hide. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session_with_viewport_serialization.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session_with_viewport_serialization.snap new file mode 100644 index 0000000000..510313378c --- /dev/null +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session_with_viewport_serialization.snap @@ -0,0 +1,29 @@ +--- +source: src/tests/e2e/cases.rs +assertion_line: 1109 +expression: last_snapshot +--- + Zellij (e2e-test)  Tab #1  Tab #2  Tab #3  Tab #4  +┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐ +│ ││ │ +│$ ││$ │ +│$ ││$ │ +│ ││ │ +│ ┌ Pane #4 ───────────────────────────────────────────────┐ │ +│ │ │ │ +│ │$┌ top ───────────────────────────────────────────────────┐ │ +│ │$│ │ │ +│ │ │ │─┐ │ +│ │ │ │ │────────────────────────────┘ +│ │ │ Waiting to run: top │ │────────────────────────────┐ +│ │ │ │ │ │ +│ │ │ to run, to exit │ │ │ +│ └─│ │ │ │ +│ │ │ │ │ +│ └─ to run, to exit ─────────────────────┘ │ │ +│ │ │ │ +│ └────────────────────────────────────────────────────────┘ │ +│ ││ │ +└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  + (FLOATING PANES VISIBLE): Press Ctrl+p, to hide. diff --git a/src/tests/fixtures/layout_for_resurrection.kdl b/src/tests/fixtures/layout_for_resurrection.kdl new file mode 100644 index 0000000000..3f6d10a50f --- /dev/null +++ b/src/tests/fixtures/layout_for_resurrection.kdl @@ -0,0 +1,373 @@ +layout { + cwd "/tmp" + tab name="Tab #1" focus=true { + pane size=1 borderless=true { + plugin location="zellij:tab-bar" + } + pane split_direction="vertical" { + pane cwd="/tmp" size="50%" + pane size="50%" { + pane cwd="/tmp" size="50%" + pane cwd="/tmp" size="50%" + } + } + pane size=2 borderless=true { + plugin location="zellij:status-bar" + } + floating_panes { + pane cwd="/tmp" { + height 10 + width 58 + x 29 + y 6 + } + pane command="top" cwd="/tmp" { + focus true + start_suspended true + height 10 + width 58 + x 31 + y 8 + } + pane cwd="/tmp" { + height 10 + width 58 + x 33 + y 10 + } + } + } + tab name="Tab #2" hide_floating_panes=true { + pane size=1 borderless=true { + plugin location="zellij:tab-bar" + } + pane cwd="/tmp" focus=true + pane size=2 borderless=true { + plugin location="zellij:status-bar" + } + } + tab name="Tab #3" { + pane size=1 borderless=true { + plugin location="zellij:tab-bar" + } + pane cwd="/tmp" + pane size=2 borderless=true { + plugin location="zellij:status-bar" + } + floating_panes { + pane cwd="/tmp" { + height 10 + width 53 + x 2 + y 1 + } + pane cwd="/tmp" { + height 10 + width 53 + x 59 + y 1 + } + pane command="vim" cwd="/tmp" { + start_suspended true + height 9 + width 53 + x 29 + y 11 + } + } + } + tab name="Tab #4" hide_floating_panes=true { + pane size=1 borderless=true { + plugin location="zellij:tab-bar" + } + pane cwd="/tmp" focus=true + pane size=2 borderless=true { + plugin location="zellij:status-bar" + } + } + new_tab_template { + pane size=1 borderless=true { + plugin location="zellij:tab-bar" + } + pane cwd="/tmp" + pane size=2 borderless=true { + plugin location="zellij:status-bar" + } + } + swap_tiled_layout name="vertical" { + tab max_panes=5 { + pane size=1 borderless=true { + plugin location="zellij:tab-bar" + } + pane { + pane split_direction="vertical" { + pane + pane { + children + } + } + } + pane size=2 borderless=true { + plugin location="zellij:status-bar" + } + } + tab max_panes=8 { + pane size=1 borderless=true { + plugin location="zellij:tab-bar" + } + pane { + pane split_direction="vertical" { + pane { + children + } + pane { + pane + pane + pane + pane + } + } + } + pane size=2 borderless=true { + plugin location="zellij:status-bar" + } + } + tab max_panes=12 { + pane size=1 borderless=true { + plugin location="zellij:tab-bar" + } + pane { + pane split_direction="vertical" { + pane { + children + } + pane { + pane + pane + pane + pane + } + pane { + pane + pane + pane + pane + } + } + } + pane size=2 borderless=true { + plugin location="zellij:status-bar" + } + } + } + swap_tiled_layout name="horizontal" { + tab max_panes=5 { + pane size=1 borderless=true { + plugin location="zellij:tab-bar" + } + pane { + pane + pane + } + pane size=2 borderless=true { + plugin location="zellij:status-bar" + } + } + tab max_panes=8 { + pane size=1 borderless=true { + plugin location="zellij:tab-bar" + } + pane { + pane { + pane split_direction="vertical" { + children + } + pane split_direction="vertical" { + pane + pane + pane + pane + } + } + } + pane size=2 borderless=true { + plugin location="zellij:status-bar" + } + } + tab max_panes=12 { + pane size=1 borderless=true { + plugin location="zellij:tab-bar" + } + pane { + pane { + pane split_direction="vertical" { + children + } + pane split_direction="vertical" { + pane + pane + pane + pane + } + pane split_direction="vertical" { + pane + pane + pane + pane + } + } + } + pane size=2 borderless=true { + plugin location="zellij:status-bar" + } + } + } + swap_tiled_layout name="stacked" { + tab min_panes=5 { + pane size=1 borderless=true { + plugin location="zellij:tab-bar" + } + pane { + pane split_direction="vertical" { + pane + pane stacked=true { + children + } + } + } + pane size=2 borderless=true { + plugin location="zellij:status-bar" + } + } + } + swap_floating_layout name="staggered" { + floating_panes + } + swap_floating_layout name="enlarged" { + floating_panes max_panes=10 { + pane cwd="/tmp" { + height "90%" + width "90%" + x "5%" + y 1 + } + pane cwd="/tmp" { + height "90%" + width "90%" + x "5%" + y 2 + } + pane cwd="/tmp" { + height "90%" + width "90%" + x "5%" + y 3 + } + pane cwd="/tmp" { + height "90%" + width "90%" + x "5%" + y 4 + } + pane cwd="/tmp" { + height "90%" + width "90%" + x "5%" + y 5 + } + pane cwd="/tmp" { + height "90%" + width "90%" + x "5%" + y 6 + } + pane cwd="/tmp" { + height "90%" + width "90%" + x "5%" + y 7 + } + pane cwd="/tmp" { + height "90%" + width "90%" + x "5%" + y 8 + } + pane cwd="/tmp" { + height "90%" + width "90%" + x "5%" + y 9 + } + pane cwd="/tmp" focus=true { + height "90%" + width "90%" + x 10 + y 10 + } + } + } + swap_floating_layout name="spread" { + floating_panes max_panes=1 { + pane cwd="/tmp" { + x "50%" + y "50%" + } + } + floating_panes max_panes=2 { + pane cwd="/tmp" { + width "45%" + x "1%" + y "25%" + } + pane cwd="/tmp" { + width "45%" + x "50%" + y "25%" + } + } + floating_panes max_panes=3 { + pane cwd="/tmp" focus=true { + height "45%" + width "45%" + y "55%" + } + pane cwd="/tmp" { + width "45%" + x "1%" + y "1%" + } + pane cwd="/tmp" { + width "45%" + x "50%" + y "1%" + } + } + floating_panes max_panes=4 { + pane cwd="/tmp" { + height "45%" + width "45%" + x "1%" + y "55%" + } + pane cwd="/tmp" focus=true { + height "45%" + width "45%" + x "50%" + y "55%" + } + pane cwd="/tmp" { + height "45%" + width "45%" + x "1%" + y "1%" + } + pane cwd="/tmp" { + height "45%" + width "45%" + x "50%" + y "1%" + } + } + } +} diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index a472cd1dc9..716592640f 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -117,6 +117,7 @@ fn spawn_server(socket_path: &Path, debug: bool) -> io::Result<()> { pub enum ClientInfo { Attach(String, Options), New(String), + Resurrect(String, Layout), } impl ClientInfo { @@ -124,6 +125,7 @@ impl ClientInfo { match self { Self::Attach(ref name, _) => name, Self::New(ref name) => name, + Self::Resurrect(ref name, _) => name, } } } @@ -212,7 +214,7 @@ pub fn start_client( ipc_pipe, ) }, - ClientInfo::New(name) => { + ClientInfo::New(name) | ClientInfo::Resurrect(name, _) => { envs::set_session_name(name.clone()); os_input.update_session_name(name); let ipc_pipe = create_ipc_pipe(); diff --git a/zellij-client/src/old_config_converter/convert_old_yaml_files.rs b/zellij-client/src/old_config_converter/convert_old_yaml_files.rs index 346b6eb62a..dde7308914 100644 --- a/zellij-client/src/old_config_converter/convert_old_yaml_files.rs +++ b/zellij-client/src/old_config_converter/convert_old_yaml_files.rs @@ -2,7 +2,7 @@ use super::{config_yaml_to_config_kdl, layout_yaml_to_layout_kdl}; use std::path::PathBuf; use zellij_utils::{ cli::CliArgs, - setup::{find_default_config_dir, get_layout_dir, get_theme_dir}, + home::{find_default_config_dir, get_layout_dir, get_theme_dir}, }; const OLD_CONFIG_NAME: &str = "config.yaml"; diff --git a/zellij-server/src/background_jobs.rs b/zellij-server/src/background_jobs.rs index 86b1735aec..13d693a06d 100644 --- a/zellij-server/src/background_jobs.rs +++ b/zellij-server/src/background_jobs.rs @@ -1,13 +1,15 @@ use zellij_utils::async_std::task; -use zellij_utils::consts::{ZELLIJ_SESSION_INFO_CACHE_DIR, ZELLIJ_SOCK_DIR}; -use zellij_utils::data::{PaneId, SessionInfo}; +use zellij_utils::consts::{ + session_info_cache_file_name, session_info_folder_for_session, session_layout_cache_file_name, + ZELLIJ_SOCK_DIR, +}; +use zellij_utils::data::SessionInfo; use zellij_utils::errors::{prelude::*, BackgroundJobContext, ContextType}; use std::collections::{BTreeMap, HashMap}; use std::fs; use std::io::Write; use std::os::unix::fs::FileTypeExt; -use std::path::PathBuf; use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, Mutex, @@ -20,10 +22,11 @@ use crate::thread_bus::Bus; #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum BackgroundJob { DisplayPaneError(Vec, String), - AnimatePluginLoading(u32), // u32 - plugin_id - StopPluginLoadingAnimation(u32), // u32 - plugin_id - ReadAllSessionInfosOnMachine, // u32 - plugin_id - ReportSessionInfo(String, SessionInfo), // String - session name + AnimatePluginLoading(u32), // u32 - plugin_id + StopPluginLoadingAnimation(u32), // u32 - plugin_id + ReadAllSessionInfosOnMachine, // u32 - plugin_id + ReportSessionInfo(String, SessionInfo), // String - session name + ReportLayoutInfo((String, BTreeMap)), // HashMap Exit, } @@ -39,6 +42,7 @@ impl From<&BackgroundJob> for BackgroundJobContext { BackgroundJobContext::ReadAllSessionInfosOnMachine }, BackgroundJob::ReportSessionInfo(..) => BackgroundJobContext::ReportSessionInfo, + BackgroundJob::ReportLayoutInfo(..) => BackgroundJobContext::ReportLayoutInfo, BackgroundJob::Exit => BackgroundJobContext::Exit, } } @@ -54,6 +58,7 @@ pub(crate) fn background_jobs_main(bus: Bus) -> Result<()> { let mut loading_plugins: HashMap> = HashMap::new(); // u32 - plugin_id let current_session_name = Arc::new(Mutex::new(String::default())); let current_session_info = Arc::new(Mutex::new(SessionInfo::default())); + let current_session_layout = Arc::new(Mutex::new((String::new(), BTreeMap::new()))); loop { let (event, mut err_ctx) = bus.recv().with_context(err_context)?; @@ -111,6 +116,9 @@ pub(crate) fn background_jobs_main(bus: Bus) -> Result<()> { *current_session_name.lock().unwrap() = session_name; *current_session_info.lock().unwrap() = session_info; }, + BackgroundJob::ReportLayoutInfo(session_layout) => { + *current_session_layout.lock().unwrap() = session_layout; + }, BackgroundJob::ReadAllSessionInfosOnMachine => { // this job should only be run once and it keeps track of other sessions (as well // as this one's) infos (metadata mostly) and sends it to the screen which in turn @@ -123,6 +131,7 @@ pub(crate) fn background_jobs_main(bus: Bus) -> Result<()> { let senders = bus.senders.clone(); let current_session_info = current_session_info.clone(); let current_session_name = current_session_name.clone(); + let current_session_layout = current_session_layout.clone(); async move { loop { // write state of current session @@ -130,15 +139,48 @@ pub(crate) fn background_jobs_main(bus: Bus) -> Result<()> { // write it to disk let current_session_name = current_session_name.lock().unwrap().to_string(); - let cache_file_name = + let metadata_cache_file_name = session_info_cache_file_name(¤t_session_name); let current_session_info = current_session_info.lock().unwrap().clone(); - let _wrote_file = - std::fs::create_dir_all(ZELLIJ_SESSION_INFO_CACHE_DIR.as_path()) - .and_then(|_| std::fs::File::create(cache_file_name)) - .and_then(|mut f| { - write!(f, "{}", current_session_info.to_string()) - }); + let (current_session_layout, layout_files_to_write) = + current_session_layout.lock().unwrap().clone(); + let _wrote_metadata_file = std::fs::create_dir_all( + session_info_folder_for_session(¤t_session_name).as_path(), + ) + .and_then(|_| std::fs::File::create(metadata_cache_file_name)) + .and_then(|mut f| write!(f, "{}", current_session_info.to_string())); + + if !current_session_layout.is_empty() { + let layout_cache_file_name = + session_layout_cache_file_name(¤t_session_name); + let _wrote_layout_file = std::fs::create_dir_all( + session_info_folder_for_session(¤t_session_name) + .as_path(), + ) + .and_then(|_| std::fs::File::create(layout_cache_file_name)) + .and_then(|mut f| write!(f, "{}", current_session_layout)) + .and_then(|_| { + let session_info_folder = + session_info_folder_for_session(¤t_session_name); + for (external_file_name, external_file_contents) in + layout_files_to_write + { + std::fs::File::create( + session_info_folder.join(external_file_name), + ) + .and_then(|mut f| write!(f, "{}", external_file_contents)) + .unwrap_or_else( + |e| { + log::error!( + "Failed to write layout metadata file: {:?}", + e + ); + }, + ); + } + Ok(()) + }); + } // start a background job (if not already running) that'll periodically read this and other // sesion infos and report back @@ -160,8 +202,8 @@ pub(crate) fn background_jobs_main(bus: Bus) -> Result<()> { } for session_name in other_session_names { - let session_cache_file_name = ZELLIJ_SESSION_INFO_CACHE_DIR - .join(format!("{}.kdl", session_name)); + let session_cache_file_name = + session_info_cache_file_name(&session_name); if let Ok(raw_session_info) = fs::read_to_string(&session_cache_file_name) { @@ -176,6 +218,7 @@ pub(crate) fn background_jobs_main(bus: Bus) -> Result<()> { let _ = senders.send_to_screen(ScreenInstruction::UpdateSessionInfos( session_infos_on_machine, )); + let _ = senders.send_to_screen(ScreenInstruction::DumpLayoutToHd); task::sleep(std::time::Duration::from_millis(SESSION_READ_DURATION)) .await; } @@ -215,7 +258,3 @@ fn job_already_running( }, } } - -fn session_info_cache_file_name(session_name: &str) -> PathBuf { - ZELLIJ_SESSION_INFO_CACHE_DIR.join(format!("{}.kdl", &session_name)) -} diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 8cb05c8e80..ba4f9452a5 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -10,6 +10,7 @@ mod pty; mod pty_writer; mod route; mod screen; +mod session_layout_metadata; mod terminal_bytes; mod thread_bus; mod ui; @@ -43,6 +44,7 @@ use zellij_utils::{ consts::{DEFAULT_SCROLL_BUFFER_SIZE, SCROLL_BUFFER_SIZE}, data::{ConnectToSession, Event, PluginCapabilities}, errors::{prelude::*, ContextType, ErrorInstruction, FatalError, ServerContext}, + home::get_default_data_dir, input::{ command::{RunCommand, TerminalAction}, get_mode_info, @@ -51,7 +53,6 @@ use zellij_utils::{ plugins::PluginsConfig, }, ipc::{ClientAttributes, ExitReason, ServerToClientMsg}, - setup::get_default_data_dir, }; pub type ClientId = u16; @@ -780,7 +781,7 @@ fn init_session( config_options.scrollback_editor.clone(), ); - move || pty_thread_main(pty, layout).fatal() + move || pty_thread_main(pty, layout.clone()).fatal() }) .unwrap(); @@ -801,6 +802,7 @@ fn init_session( let client_attributes_clone = client_attributes.clone(); let debug = opts.debug; + let layout = layout.clone(); move || { screen_thread_main( screen_bus, @@ -808,6 +810,7 @@ fn init_session( client_attributes_clone, config_options, debug, + layout, ) .fatal(); } diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs index 05feda241b..fe0f3ae435 100644 --- a/zellij-server/src/os_input_output.rs +++ b/zellij-server/src/os_input_output.rs @@ -504,6 +504,14 @@ pub trait ServerOsApi: Send + Sync { fn load_palette(&self) -> Palette; /// Returns the current working directory for a given pid fn get_cwd(&self, pid: Pid) -> Option; + /// Returns the current working directory for multiple pids + fn get_cwds(&self, _pids: Vec) -> HashMap { + HashMap::new() + } + /// Get a list of all running commands by their parent process id + fn get_all_cmds_by_ppid(&self) -> HashMap> { + HashMap::new() + } /// Writes the given buffer to a string fn write_to_file(&mut self, buf: String, file: Option) -> Result<()>; @@ -756,6 +764,50 @@ impl ServerOsApi for ServerOsInputOutput { None } + fn get_cwds(&self, pids: Vec) -> HashMap { + let mut system_info = System::new(); + // Update by minimizing information. + // See https://docs.rs/sysinfo/0.22.5/sysinfo/struct.ProcessRefreshKind.html# + system_info.refresh_processes_specifics(ProcessRefreshKind::default()); + + let mut cwds = HashMap::new(); + for pid in pids { + if let Some(process) = system_info.process(pid.into()) { + let cwd = process.cwd(); + let cwd_is_empty = cwd.iter().next().is_none(); + if !cwd_is_empty { + cwds.insert(pid, process.cwd().to_path_buf()); + } + } + } + cwds + } + fn get_all_cmds_by_ppid(&self) -> HashMap> { + // the key is the stringified ppid + let mut cmds = HashMap::new(); + if let Some(output) = Command::new("ps") + .args(vec!["-ao", "ppid,args"]) + .output() + .ok() + { + let output = String::from_utf8(output.stdout.clone()) + .unwrap_or_else(|_| String::from_utf8_lossy(&output.stdout).to_string()); + for line in output.lines() { + let line_parts: Vec = line + .trim() + .split_ascii_whitespace() + .map(|p| p.to_owned()) + .collect(); + let mut line_parts = line_parts.into_iter(); + let ppid = line_parts.next(); + if let Some(ppid) = ppid { + cmds.insert(ppid.into(), line_parts.collect()); + } + } + } + cmds + } + fn write_to_file(&mut self, buf: String, name: Option) -> Result<()> { let err_context = || "failed to write to file".to_string(); diff --git a/zellij-server/src/output/mod.rs b/zellij-server/src/output/mod.rs index 3750ecc6a2..d75153379d 100644 --- a/zellij-server/src/output/mod.rs +++ b/zellij-server/src/output/mod.rs @@ -79,11 +79,47 @@ fn write_changed_styles( Ok(()) } +fn serialize_chunks_with_newlines( + character_chunks: Vec, + _sixel_chunks: Option<&Vec>, // TODO: fix this sometime + link_handler: Option<&mut Rc>>, +) -> Result { + let err_context = || "failed to serialize input chunks".to_string(); + + let mut vte_output = String::new(); + let link_handler = link_handler.map(|l_h| l_h.borrow()); + for character_chunk in character_chunks { + let chunk_changed_colors = character_chunk.changed_colors(); + let mut character_styles = CharacterStyles::new(); + vte_output.push_str("\n\r"); + let mut chunk_width = character_chunk.x; + for t_character in character_chunk.terminal_characters.iter() { + let current_character_styles = adjust_styles_for_possible_selection( + character_chunk.selection_and_colors(), + t_character.styles, + character_chunk.y, + chunk_width, + ); + write_changed_styles( + &mut character_styles, + current_character_styles, + chunk_changed_colors, + link_handler.as_ref(), + &mut vte_output, + ) + .with_context(err_context)?; + chunk_width += t_character.width; + vte_output.push(t_character.character); + } + character_styles.clear(); + } + Ok(vte_output) +} fn serialize_chunks( character_chunks: Vec, sixel_chunks: Option<&Vec>, link_handler: Option<&mut Rc>>, - sixel_image_store: &mut SixelImageStore, + sixel_image_store: Option<&mut SixelImageStore>, ) -> Result { let err_context = || "failed to serialize input chunks".to_string(); @@ -116,20 +152,22 @@ fn serialize_chunks( } character_styles.clear(); } - if let Some(sixel_chunks) = sixel_chunks { - for sixel_chunk in sixel_chunks { - let serialized_sixel_image = sixel_image_store.serialize_image( - sixel_chunk.sixel_image_id, - sixel_chunk.sixel_image_pixel_x, - sixel_chunk.sixel_image_pixel_y, - sixel_chunk.sixel_image_pixel_width, - sixel_chunk.sixel_image_pixel_height, - ); - if let Some(serialized_sixel_image) = serialized_sixel_image { - let sixel_vte = sixel_vte.get_or_insert_with(String::new); - vte_goto_instruction(sixel_chunk.cell_x, sixel_chunk.cell_y, sixel_vte) - .with_context(err_context)?; - sixel_vte.push_str(&serialized_sixel_image); + if let Some(sixel_image_store) = sixel_image_store { + if let Some(sixel_chunks) = sixel_chunks { + for sixel_chunk in sixel_chunks { + let serialized_sixel_image = sixel_image_store.serialize_image( + sixel_chunk.sixel_image_id, + sixel_chunk.sixel_image_pixel_x, + sixel_chunk.sixel_image_pixel_y, + sixel_chunk.sixel_image_pixel_width, + sixel_chunk.sixel_image_pixel_height, + ); + if let Some(serialized_sixel_image) = serialized_sixel_image { + let sixel_vte = sixel_vte.get_or_insert_with(String::new); + vte_goto_instruction(sixel_chunk.cell_x, sixel_chunk.cell_y, sixel_vte) + .with_context(err_context)?; + sixel_vte.push_str(&serialized_sixel_image); + } } } } @@ -378,7 +416,7 @@ impl Output { client_character_chunks, self.sixel_chunks.get(&client_id), self.link_handler.as_mut(), - &mut self.sixel_image_store.borrow_mut(), + Some(&mut self.sixel_image_store.borrow_mut()), ) .with_context(err_context)?, ); // TODO: less allocations? @@ -865,6 +903,18 @@ impl OutputBuffer { self.changed_lines.clear(); self.should_update_all_lines = false; } + pub fn serialize(&self, viewport: &[Row]) -> Result { + let mut chunks = Vec::new(); + for (line_index, line) in viewport.iter().enumerate() { + let terminal_characters = + self.extract_line_from_viewport(line_index, viewport, line.width()); + + let x = 0; + let y = line_index; + chunks.push(CharacterChunk::new(terminal_characters, x, y)); + } + serialize_chunks_with_newlines(chunks, None, None) + } pub fn changed_chunks_in_viewport( &self, viewport: &[Row], diff --git a/zellij-server/src/panes/floating_panes/mod.rs b/zellij-server/src/panes/floating_panes/mod.rs index 5efa6eb724..6362d5cbbd 100644 --- a/zellij-server/src/panes/floating_panes/mod.rs +++ b/zellij-server/src/panes/floating_panes/mod.rs @@ -157,7 +157,7 @@ impl FloatingPanes { // move clients from the previously active pane to the new pane we just inserted self.move_clients_between_panes(pane_id, with_pane_id); - self.set_pane_frames(); + let _ = self.set_pane_frames(); removed_pane } pub fn remove_pane(&mut self, pane_id: PaneId) -> Option> { @@ -631,7 +631,7 @@ impl FloatingPanes { pub fn move_active_pane( &mut self, search_backwards: bool, - os_api: &mut Box, + _os_api: &mut Box, client_id: ClientId, ) { let active_pane_id = self.get_active_pane_id(client_id).unwrap(); diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs index a13b644524..c902e23a08 100644 --- a/zellij-server/src/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -1034,6 +1034,28 @@ impl Grid { (changed_character_chunks, changed_sixel_image_chunks) } + pub fn serialize(&self, scrollback_lines_to_serialize: Option) -> Option { + match scrollback_lines_to_serialize { + Some(scrollback_lines_to_serialize) => { + let first_index = if scrollback_lines_to_serialize == 0 { + 0 + } else { + self.lines_above + .len() + .saturating_sub(scrollback_lines_to_serialize) + }; + let mut to_serialize = vec![]; + for line in self.lines_above.iter().skip(first_index) { + to_serialize.push(line.clone()); + } + for line in &self.viewport { + to_serialize.push(line.clone()) + } + self.output_buffer.serialize(to_serialize.as_slice()).ok() + }, + None => self.output_buffer.serialize(&self.viewport).ok(), + } + } pub fn render( &mut self, content_x: usize, diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs index 896c0c2612..69b24e3ed4 100644 --- a/zellij-server/src/panes/plugin_pane.rs +++ b/zellij-server/src/panes/plugin_pane.rs @@ -616,6 +616,13 @@ impl Pane for PluginPane { self.pane_name.to_owned() } } + fn custom_title(&self) -> Option { + if self.pane_name.is_empty() { + None + } else { + Some(self.pane_name.clone()) + } + } fn rename(&mut self, buf: Vec) { self.pane_name = String::from_utf8_lossy(&buf).to_string(); self.set_should_render(true); diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index 9e687c81d8..f845d36ad3 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -726,6 +726,13 @@ impl Pane for TerminalPane { self.pane_name.to_owned() } } + fn custom_title(&self) -> Option { + if self.pane_name.is_empty() { + None + } else { + Some(self.pane_name.clone()) + } + } fn exit_status(&self) -> Option { self.is_held .as_ref() @@ -744,6 +751,9 @@ impl Pane for TerminalPane { self.pane_name = String::from_utf8_lossy(&buf).to_string(); self.set_should_render(true); } + fn serialize(&self, scrollback_lines_to_serialize: Option) -> Option { + self.grid.serialize(scrollback_lines_to_serialize) + } } impl TerminalPane { diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs index 37d9a9778f..acf3ae0de3 100644 --- a/zellij-server/src/plugins/mod.rs +++ b/zellij-server/src/plugins/mod.rs @@ -15,6 +15,7 @@ use std::{ use wasmer::Store; use crate::screen::ScreenInstruction; +use crate::session_layout_metadata::SessionLayoutMetadata; use crate::{pty::PtyInstruction, thread_bus::Bus, ClientId, ServerInstruction}; use wasm_bridge::WasmBridge; @@ -94,6 +95,8 @@ pub enum PluginInstruction { PermissionStatus, Option, ), + DumpLayout(SessionLayoutMetadata, ClientId), + LogLayoutToHd(SessionLayoutMetadata), Exit, } @@ -123,6 +126,8 @@ impl From<&PluginInstruction> for PluginContext { PluginInstruction::PermissionRequestResult(..) => { PluginContext::PermissionRequestResult }, + PluginInstruction::DumpLayout(..) => PluginContext::DumpLayout, + PluginInstruction::LogLayoutToHd(..) => PluginContext::LogLayoutToHd, } } } @@ -355,6 +360,20 @@ pub(crate) fn plugin_thread_main( )]; wasm_bridge.update_plugins(updates, shutdown_send.clone())?; }, + PluginInstruction::DumpLayout(mut session_layout_metadata, client_id) => { + populate_session_layout_metadata(&mut session_layout_metadata, &wasm_bridge); + drop(bus.senders.send_to_pty(PtyInstruction::DumpLayout( + session_layout_metadata, + client_id, + ))); + }, + PluginInstruction::LogLayoutToHd(mut session_layout_metadata) => { + populate_session_layout_metadata(&mut session_layout_metadata, &wasm_bridge); + drop( + bus.senders + .send_to_pty(PtyInstruction::LogLayoutToHd(session_layout_metadata)), + ); + }, PluginInstruction::Exit => { break; }, @@ -387,6 +406,24 @@ pub(crate) fn plugin_thread_main( .context("failed to cleanup plugin data directory") } +fn populate_session_layout_metadata( + session_layout_metadata: &mut SessionLayoutMetadata, + wasm_bridge: &WasmBridge, +) { + let plugin_ids = session_layout_metadata.all_plugin_ids(); + let mut plugin_ids_to_cmds: HashMap = HashMap::new(); + for plugin_id in plugin_ids { + let plugin_cmd = wasm_bridge.run_plugin_of_plugin_id(plugin_id); + match plugin_cmd { + Some(plugin_cmd) => { + plugin_ids_to_cmds.insert(plugin_id, plugin_cmd.clone()); + }, + None => log::error!("Plugin with id: {plugin_id} not found"), + } + } + session_layout_metadata.update_plugin_cmds(plugin_ids_to_cmds); +} + const EXIT_TIMEOUT: Duration = Duration::from_secs(3); #[path = "./unit/plugin_tests.rs"] diff --git a/zellij-server/src/plugins/plugin_map.rs b/zellij-server/src/plugins/plugin_map.rs index c5f2ef20f6..91b61c13ed 100644 --- a/zellij-server/src/plugins/plugin_map.rs +++ b/zellij-server/src/plugins/plugin_map.rs @@ -15,7 +15,7 @@ use zellij_utils::{ data::EventType, data::PluginCapabilities, input::command::TerminalAction, - input::layout::{Layout, RunPluginLocation}, + input::layout::{Layout, RunPlugin, RunPluginLocation}, input::plugins::PluginConfig, ipc::ClientAttributes, }; @@ -185,6 +185,28 @@ impl PluginMap { (running_plugin, subscriptions, running_workers), ); } + pub fn run_plugin_of_plugin_id(&self, plugin_id: PluginId) -> Option { + self.plugin_assets + .iter() + .find_map(|((p_id, _), (running_plugin, _, _))| { + if *p_id == plugin_id { + let running_plugin = running_plugin.lock().unwrap(); + let run_plugin_location = running_plugin.plugin_env.plugin.location.clone(); + let run_plugin_configuration = running_plugin + .plugin_env + .plugin + .userspace_configuration + .clone(); + Some(RunPlugin { + _allow_exec_host_cmd: false, + location: run_plugin_location, + configuration: run_plugin_configuration, + }) + } else { + None + } + }) + } } pub type Subscriptions = HashSet; diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_tabs_with_layout_plugin_command-2.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_tabs_with_layout_plugin_command-2.snap index 43571f91f7..54d15e6e5a 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_tabs_with_layout_plugin_command-2.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_tabs_with_layout_plugin_command-2.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/plugins/./unit/plugin_tests.rs -assertion_line: 735 +assertion_line: 1002 expression: "format!(\"{:#?}\", second_new_tab_event)" --- Some( @@ -25,6 +25,8 @@ Some( is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -39,6 +41,8 @@ Some( is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -50,6 +54,8 @@ Some( is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ), [], diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_tabs_with_layout_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_tabs_with_layout_plugin_command.snap index 5e9a17cb31..9c4614e638 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_tabs_with_layout_plugin_command.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_tabs_with_layout_plugin_command.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/plugins/./unit/plugin_tests.rs -assertion_line: 734 +assertion_line: 1001 expression: "format!(\"{:#?}\", first_new_tab_event)" --- Some( @@ -25,6 +25,8 @@ Some( is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -39,6 +41,8 @@ Some( is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -50,6 +54,8 @@ Some( is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ), [], diff --git a/zellij-server/src/plugins/wasm_bridge.rs b/zellij-server/src/plugins/wasm_bridge.rs index 570d1bf7eb..b2ddb99249 100644 --- a/zellij-server/src/plugins/wasm_bridge.rs +++ b/zellij-server/src/plugins/wasm_bridge.rs @@ -520,7 +520,7 @@ impl WasmBridge { let mut applied_plugin_paths = HashSet::new(); for plugin_id in plugin_ids { self.apply_cached_events_and_resizes_for_plugin(plugin_id, shutdown_sender.clone())?; - if let Some(run_plugin) = self.run_plugin_of_plugin_id(plugin_id) { + if let Some(run_plugin) = self.run_plugin_of_loading_plugin_id(plugin_id) { applied_plugin_paths.insert(run_plugin.clone()); } self.loading_plugins @@ -551,12 +551,18 @@ impl WasmBridge { watcher.stop_nonblocking(); } } - fn run_plugin_of_plugin_id(&self, plugin_id: PluginId) -> Option<&RunPlugin> { + pub fn run_plugin_of_loading_plugin_id(&self, plugin_id: PluginId) -> Option<&RunPlugin> { self.loading_plugins .iter() .find(|((p_id, _run_plugin), _)| p_id == &plugin_id) .map(|((_p_id, run_plugin), _)| run_plugin) } + pub fn run_plugin_of_plugin_id(&self, plugin_id: PluginId) -> Option { + self.plugin_map + .lock() + .unwrap() + .run_plugin_of_plugin_id(plugin_id) + } fn apply_cached_events_and_resizes_for_plugin( &mut self, plugin_id: PluginId, diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index 1f732d768e..36394115fc 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -1,7 +1,9 @@ +use crate::background_jobs::BackgroundJob; use crate::terminal_bytes::TerminalBytes; use crate::{ plugins::PluginInstruction, screen::ScreenInstruction, + session_layout_metadata::SessionLayoutMetadata, thread_bus::{Bus, ThreadSenders}, ClientId, ServerInstruction, }; @@ -20,6 +22,7 @@ use zellij_utils::{ TiledPaneLayout, }, }, + session_serialization, }; pub type VteBytes = Vec; @@ -68,6 +71,8 @@ pub enum PtyInstruction { Option, ClientTabIndexOrPaneId, ), // String is an optional pane name + DumpLayout(SessionLayoutMetadata, ClientId), + LogLayoutToHd(SessionLayoutMetadata), Exit, } @@ -85,6 +90,8 @@ impl From<&PtyInstruction> for PtyContext { PtyInstruction::NewTab(..) => PtyContext::NewTab, PtyInstruction::ReRunCommandInPane(..) => PtyContext::ReRunCommandInPane, PtyInstruction::SpawnInPlaceTerminal(..) => PtyContext::SpawnInPlaceTerminal, + PtyInstruction::DumpLayout(..) => PtyContext::DumpLayout, + PtyInstruction::LogLayoutToHd(..) => PtyContext::LogLayoutToHd, PtyInstruction::Exit => PtyContext::Exit, } } @@ -121,12 +128,26 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { ), _ => (false, None, name), }; + let invoked_with = + match &terminal_action { + Some(TerminalAction::RunCommand(run_command)) => { + Some(Run::Command(run_command.clone())) + }, + Some(TerminalAction::OpenFile(file, line_number, cwd)) => Some( + Run::EditFile(file.clone(), line_number.clone(), cwd.clone()), + ), + _ => None, + }; match pty .spawn_terminal(terminal_action, client_or_tab_index) .with_context(err_context) { Ok((pid, starts_held)) => { - let hold_for_command = if starts_held { run_command } else { None }; + let hold_for_command = if starts_held { + run_command.clone() + } else { + None + }; pty.bus .senders .send_to_screen(ScreenInstruction::NewPane( @@ -134,6 +155,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { pane_title, should_float, hold_for_command, + invoked_with, client_or_tab_index, )) .with_context(err_context)?; @@ -149,6 +171,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { pane_title, should_float, hold_for_command, + invoked_with, client_or_tab_index, )) .with_context(err_context)?; @@ -190,6 +213,16 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { ), _ => (false, None, name), }; + let invoked_with = + match &terminal_action { + Some(TerminalAction::RunCommand(run_command)) => { + Some(Run::Command(run_command.clone())) + }, + Some(TerminalAction::OpenFile(file, line_number, cwd)) => Some( + Run::EditFile(file.clone(), line_number.clone(), cwd.clone()), + ), + _ => None, + }; match pty .spawn_terminal(terminal_action, client_id_tab_index_or_pane_id) .with_context(err_context) @@ -202,6 +235,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { PaneId::Terminal(pid), hold_for_command, pane_title, + invoked_with, client_id_tab_index_or_pane_id, )) .with_context(err_context)?; @@ -216,6 +250,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { PaneId::Terminal(*terminal_id), hold_for_command, pane_title, + invoked_with, client_id_tab_index_or_pane_id, )) .with_context(err_context)?; @@ -497,6 +532,27 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { }, } }, + PtyInstruction::DumpLayout(mut session_layout_metadata, client_id) => { + let err_context = || format!("Failed to dump layout"); + pty.populate_session_layout_metadata(&mut session_layout_metadata); + let (kdl_layout, _pane_contents) = + session_serialization::serialize_session_layout(session_layout_metadata.into()); + pty.bus + .senders + .send_to_server(ServerInstruction::Log(vec![kdl_layout], client_id)) + .with_context(err_context) + .non_fatal(); + }, + PtyInstruction::LogLayoutToHd(mut session_layout_metadata) => { + let err_context = || format!("Failed to dump layout"); + pty.populate_session_layout_metadata(&mut session_layout_metadata); + let kdl_layout = + session_serialization::serialize_session_layout(session_layout_metadata.into()); + pty.bus + .senders + .send_to_background_jobs(BackgroundJob::ReportLayoutInfo(kdl_layout)) + .with_context(err_context)?; + }, PtyInstruction::Exit => break, } } @@ -1105,6 +1161,51 @@ impl Pty { _ => Err(anyhow!("cannot respawn plugin panes")).with_context(err_context), } } + pub fn populate_session_layout_metadata( + &self, + session_layout_metadata: &mut SessionLayoutMetadata, + ) { + let terminal_ids = session_layout_metadata.all_terminal_ids(); + let mut terminal_ids_to_commands: HashMap> = HashMap::new(); + let mut terminal_ids_to_cwds: HashMap = HashMap::new(); + + let pids: Vec<_> = terminal_ids + .iter() + .filter_map(|id| self.id_to_child_pid.get(&id)) + .map(|pid| Pid::from_raw(*pid)) + .collect(); + let pids_to_cwds = self + .bus + .os_input + .as_ref() + .map(|os_input| os_input.get_cwds(pids)) + .unwrap_or_default(); + let ppids_to_cmds = self + .bus + .os_input + .as_ref() + .map(|os_input| os_input.get_all_cmds_by_ppid()) + .unwrap_or_default(); + + for terminal_id in terminal_ids { + let process_id = self.id_to_child_pid.get(&terminal_id); + let cwd = process_id + .as_ref() + .and_then(|pid| pids_to_cwds.get(&Pid::from_raw(**pid))); + let cmd = process_id + .as_ref() + .and_then(|pid| ppids_to_cmds.get(&format!("{}", pid))); + if let Some(cmd) = cmd { + terminal_ids_to_commands.insert(terminal_id, cmd.clone()); + } + if let Some(cwd) = cwd { + terminal_ids_to_cwds.insert(terminal_id, cwd.clone()); + } + } + session_layout_metadata.update_default_shell(get_default_shell()); + session_layout_metadata.update_terminal_commands(terminal_ids_to_commands); + session_layout_metadata.update_terminal_cwds(terminal_ids_to_cwds); + } } impl Drop for Pty { diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index d5d28dcdca..a1c658da9a 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -172,6 +172,15 @@ pub(crate) fn route_action( .send_to_screen(ScreenInstruction::DumpScreen(val, client_id, full)) .with_context(err_context)?; }, + Action::DumpLayout => { + let default_shell = match default_shell { + Some(TerminalAction::RunCommand(run_command)) => Some(run_command.command), + _ => None, + }; + senders + .send_to_screen(ScreenInstruction::DumpLayout(default_shell, client_id)) + .with_context(err_context)?; + }, Action::EditScrollback => { senders .send_to_screen(ScreenInstruction::EditScrollback(client_id)) diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index e7fcb5d336..079ddf704f 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -27,6 +27,7 @@ use crate::background_jobs::BackgroundJob; use crate::os_input_output::ResizeCache; use crate::panes::alacritty_functions::xparse_color; use crate::panes::terminal_character::AnsiCode; +use crate::session_layout_metadata::{PaneLayoutMetadata, SessionLayoutMetadata}; use crate::{ output::Output, @@ -144,6 +145,7 @@ pub enum ScreenInstruction { Option, Option, HoldForCommand, + Option, // invoked with ClientTabIndexOrPaneId, ), OpenInPlaceEditor(PaneId, ClientId), @@ -172,6 +174,8 @@ pub enum ScreenInstruction { Exit, ClearScreen(ClientId), DumpScreen(String, ClientId, bool), + DumpLayout(Option, ClientId), // PathBuf is the default configured + // shell EditScrollback(ClientId), ScrollUp(ClientId), ScrollUpAt(Position, ClientId), @@ -306,8 +310,10 @@ pub enum ScreenInstruction { PaneId, HoldForCommand, Option, + Option, ClientTabIndexOrPaneId, ), + DumpLayoutToHd, } impl From<&ScreenInstruction> for ScreenContext { @@ -374,6 +380,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::Exit => ScreenContext::Exit, ScreenInstruction::ClearScreen(..) => ScreenContext::ClearScreen, ScreenInstruction::DumpScreen(..) => ScreenContext::DumpScreen, + ScreenInstruction::DumpLayout(..) => ScreenContext::DumpLayout, ScreenInstruction::EditScrollback(..) => ScreenContext::EditScrollback, ScreenInstruction::ScrollUp(..) => ScreenContext::ScrollUp, ScreenInstruction::ScrollDown(..) => ScreenContext::ScrollDown, @@ -487,6 +494,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::UpdateSessionInfos(..) => ScreenContext::UpdateSessionInfos, ScreenInstruction::ReplacePane(..) => ScreenContext::ReplacePane, ScreenInstruction::NewInPlacePluginPane(..) => ScreenContext::NewInPlacePluginPane, + ScreenInstruction::DumpLayoutToHd => ScreenContext::DumpLayoutToHd, } } } @@ -548,12 +556,17 @@ pub(crate) struct Screen { style: Style, draw_pane_frames: bool, auto_layout: bool, + session_serialization: bool, + serialize_pane_viewport: bool, + scrollback_lines_to_serialize: Option, session_is_mirrored: bool, copy_options: CopyOptions, debug: bool, session_name: String, session_infos_on_machine: BTreeMap, // String is the session name, can - // also be this session + // also be this session + default_layout: Box, + default_shell: Option, } impl Screen { @@ -568,6 +581,11 @@ impl Screen { session_is_mirrored: bool, copy_options: CopyOptions, debug: bool, + default_layout: Box, + default_shell: Option, + session_serialization: bool, + serialize_pane_viewport: bool, + scrollback_lines_to_serialize: Option, ) -> Self { let session_name = mode_info.session_name.clone().unwrap_or_default(); let session_info = SessionInfo::new(session_name.clone()); @@ -597,6 +615,11 @@ impl Screen { debug, session_name, session_infos_on_machine, + default_layout, + default_shell, + session_serialization, + serialize_pane_viewport, + scrollback_lines_to_serialize, } } @@ -1376,10 +1399,20 @@ impl Screen { session_info, )) .with_context(err_context)?; + self.bus .senders .send_to_background_jobs(BackgroundJob::ReadAllSessionInfosOnMachine) .with_context(err_context)?; + Ok(()) + } + fn dump_layout_to_hd(&mut self) -> Result<()> { + let err_context = || format!("Failed to log and report session state"); + let session_layout_metadata = self.get_layout_metadata(self.default_shell.clone()); + self.bus + .senders + .send_to_plugin(PluginInstruction::LogLayoutToHd(session_layout_metadata)) + .with_context(err_context)?; Ok(()) } @@ -1845,15 +1878,15 @@ impl Screen { &mut self, new_pane_id: PaneId, hold_for_command: HoldForCommand, - run_plugin: Option, + run: Option, pane_title: Option, client_id_tab_index_or_pane_id: ClientTabIndexOrPaneId, ) -> Result<()> { let err_context = || format!("failed to replace pane"); let suppress_pane = |tab: &mut Tab, pane_id: PaneId, new_pane_id: PaneId| { - tab.suppress_pane_and_replace_with_pid(pane_id, new_pane_id, run_plugin); + let _ = tab.suppress_pane_and_replace_with_pid(pane_id, new_pane_id, run); if let Some(pane_title) = pane_title { - tab.rename_pane(pane_title.as_bytes().to_vec(), new_pane_id); + let _ = tab.rename_pane(pane_title.as_bytes().to_vec(), new_pane_id); } if let Some(hold_for_command) = hold_for_command { let is_first_run = true; @@ -1874,7 +1907,7 @@ impl Screen { ); }, } - }) + }); }, ClientTabIndexOrPaneId::PaneId(pane_id) => { let tab_index = self @@ -1892,7 +1925,7 @@ impl Screen { }, }; }, - ClientTabIndexOrPaneId::TabIndex(tab_index) => { + ClientTabIndexOrPaneId::TabIndex(_tab_index) => { log::error!("Cannot replace pane with tab index"); }, } @@ -1904,6 +1937,96 @@ impl Screen { .send_to_server(ServerInstruction::UnblockInputThread) .context("failed to unblock input") } + fn get_layout_metadata(&self, default_shell: Option) -> SessionLayoutMetadata { + let mut session_layout_metadata = SessionLayoutMetadata::new(self.default_layout.clone()); + if let Some(default_shell) = default_shell { + session_layout_metadata.update_default_shell(default_shell); + } + let first_client_id = self.get_first_client_id(); + let active_tab_index = + first_client_id.and_then(|client_id| self.active_tab_indices.get(&client_id)); + + for (tab_index, tab) in self.tabs.values().enumerate() { + let tab_is_focused = active_tab_index == Some(&tab_index); + let hide_floating_panes = !tab.are_floating_panes_visible(); + let mut suppressed_panes = HashMap::new(); + for (triggering_pane_id, p) in tab.get_suppressed_panes() { + suppressed_panes.insert(*triggering_pane_id, p); + } + let active_pane_id = + first_client_id.and_then(|client_id| tab.get_active_pane_id(client_id)); + let tiled_panes: Vec = tab + .get_tiled_panes() + .map(|(pane_id, p)| { + // here we look to see if this pane triggers any suppressed pane, + // and if so we take that suppressed pane - we do this because this + // is currently only the case the scrollback editing panes, and + // when dumping the layout we want the "real" pane and not the + // editor pane + match suppressed_panes.remove(pane_id) { + Some((is_scrollback_editor, suppressed_pane)) if *is_scrollback_editor => { + (suppressed_pane.pid(), suppressed_pane) + }, + _ => (*pane_id, p), + } + }) + .map(|(pane_id, p)| { + PaneLayoutMetadata::new( + pane_id, + p.position_and_size(), + p.borderless(), + p.invoked_with().clone(), + p.custom_title(), + active_pane_id == Some(pane_id), + if self.serialize_pane_viewport { + p.serialize(self.scrollback_lines_to_serialize) + } else { + None + }, + ) + }) + .collect(); + let floating_panes: Vec = tab + .get_floating_panes() + .map(|(pane_id, p)| { + // here we look to see if this pane triggers any suppressed pane, + // and if so we take that suppressed pane - we do this because this + // is currently only the case the scrollback editing panes, and + // when dumping the layout we want the "real" pane and not the + // editor pane + match suppressed_panes.remove(pane_id) { + Some((is_scrollback_editor, suppressed_pane)) if *is_scrollback_editor => { + (suppressed_pane.pid(), suppressed_pane) + }, + _ => (*pane_id, p), + } + }) + .map(|(pane_id, p)| { + PaneLayoutMetadata::new( + pane_id, + p.position_and_size(), + false, // floating panes are never borderless + p.invoked_with().clone(), + p.custom_title(), + active_pane_id == Some(pane_id), + if self.serialize_pane_viewport { + p.serialize(self.scrollback_lines_to_serialize) + } else { + None + }, + ) + }) + .collect(); + session_layout_metadata.add_tab( + tab.name.clone(), + tab_is_focused, + hide_floating_panes, + tiled_panes, + floating_panes, + ); + } + session_layout_metadata + } } // The box is here in order to make the @@ -1915,11 +2038,16 @@ pub(crate) fn screen_thread_main( client_attributes: ClientAttributes, config_options: Box, debug: bool, + default_layout: Box, ) -> Result<()> { let capabilities = config_options.simplified_ui; let draw_pane_frames = config_options.pane_frames.unwrap_or(true); let auto_layout = config_options.auto_layout.unwrap_or(true); + let session_serialization = config_options.session_serialization.unwrap_or(true); + let serialize_pane_viewport = config_options.serialize_pane_viewport.unwrap_or(false); + let scrollback_lines_to_serialize = config_options.scrollback_lines_to_serialize; let session_is_mirrored = config_options.mirror_session.unwrap_or(false); + let default_shell = config_options.default_shell; let copy_options = CopyOptions::new( config_options.copy_command, config_options.copy_clipboard.unwrap_or_default(), @@ -1943,6 +2071,11 @@ pub(crate) fn screen_thread_main( session_is_mirrored, copy_options, debug, + default_layout, + default_shell, + session_serialization, + serialize_pane_viewport, + scrollback_lines_to_serialize, ); let mut pending_tab_ids: HashSet = HashSet::new(); @@ -1991,6 +2124,7 @@ pub(crate) fn screen_thread_main( initial_pane_title, should_float, hold_for_command, + invoked_with, client_or_tab_index, ) => { match client_or_tab_index { @@ -1999,7 +2133,7 @@ pub(crate) fn screen_thread_main( tab.new_pane(pid, initial_pane_title, should_float, - None, + invoked_with, Some(client_id) ) }, ?); @@ -2023,7 +2157,7 @@ pub(crate) fn screen_thread_main( pid, initial_pane_title, should_float, - None, + invoked_with, None, )?; if let Some(hold_for_command) = hold_for_command { @@ -2034,7 +2168,7 @@ pub(crate) fn screen_thread_main( log::error!("Tab index not found: {:?}", tab_index); } }, - ClientTabIndexOrPaneId::PaneId(pane_id) => { + ClientTabIndexOrPaneId::PaneId(_pane_id) => { log::error!("cannot open a pane with a pane id??"); }, }; @@ -2285,6 +2419,18 @@ pub(crate) fn screen_thread_main( screen.render()?; screen.unblock_input()?; }, + ScreenInstruction::DumpLayout(default_shell, client_id) => { + let err_context = || format!("Failed to dump layout"); + let session_layout_metadata = screen.get_layout_metadata(default_shell); + screen + .bus + .senders + .send_to_plugin(PluginInstruction::DumpLayout( + session_layout_metadata, + client_id, + )) + .with_context(err_context)?; + }, ScreenInstruction::EditScrollback(client_id) => { active_tab_and_connected_client_id!( screen, @@ -3299,13 +3445,13 @@ pub(crate) fn screen_thread_main( new_pane_id, hold_for_command, pane_title, + invoked_with, client_id_tab_index_or_pane_id, ) => { - let err_context = || format!("Failed to replace pane"); screen.replace_pane( new_pane_id, hold_for_command, - None, + invoked_with, pane_title, client_id_tab_index_or_pane_id, )?; @@ -3315,6 +3461,11 @@ pub(crate) fn screen_thread_main( screen.render()?; }, + ScreenInstruction::DumpLayoutToHd => { + if screen.session_serialization { + screen.dump_layout_to_hd()?; + } + }, } } Ok(()) diff --git a/zellij-server/src/session_layout_metadata.rs b/zellij-server/src/session_layout_metadata.rs new file mode 100644 index 0000000000..f746d14b00 --- /dev/null +++ b/zellij-server/src/session_layout_metadata.rs @@ -0,0 +1,278 @@ +use crate::panes::PaneId; +use std::collections::HashMap; +use std::path::PathBuf; +use zellij_utils::common_path::common_path_all; +use zellij_utils::pane_size::PaneGeom; +use zellij_utils::{ + input::command::RunCommand, + input::layout::{Layout, Run, RunPlugin}, + session_serialization::{GlobalLayoutManifest, PaneLayoutManifest, TabLayoutManifest}, +}; + +#[derive(Default, Debug, Clone)] +pub struct SessionLayoutMetadata { + default_layout: Box, + global_cwd: Option, + pub default_shell: Option, + tabs: Vec, +} + +impl SessionLayoutMetadata { + pub fn new(default_layout: Box) -> Self { + SessionLayoutMetadata { + default_layout, + ..Default::default() + } + } + pub fn update_default_shell(&mut self, default_shell: PathBuf) { + if self.default_shell.is_none() { + self.default_shell = Some(default_shell); + } + for tab in self.tabs.iter_mut() { + for tiled_pane in tab.tiled_panes.iter_mut() { + if let Some(Run::Command(run_command)) = tiled_pane.run.as_mut() { + if Self::is_default_shell( + self.default_shell.as_ref(), + &run_command.command.display().to_string(), + &run_command.args, + ) { + tiled_pane.run = None; + } + } + } + for floating_pane in tab.floating_panes.iter_mut() { + if let Some(Run::Command(run_command)) = floating_pane.run.as_mut() { + if Self::is_default_shell( + self.default_shell.as_ref(), + &run_command.command.display().to_string(), + &run_command.args, + ) { + floating_pane.run = None; + } + } + } + } + } + fn is_default_shell( + default_shell: Option<&PathBuf>, + command_name: &String, + args: &Vec, + ) -> bool { + default_shell + .as_ref() + .map(|c| c.display().to_string()) + .as_ref() + == Some(command_name) + && args.is_empty() + } +} + +impl SessionLayoutMetadata { + pub fn add_tab( + &mut self, + name: String, + is_focused: bool, + hide_floating_panes: bool, + tiled_panes: Vec, + floating_panes: Vec, + ) { + self.tabs.push(TabLayoutMetadata { + name: Some(name), + is_focused, + hide_floating_panes, + tiled_panes, + floating_panes, + }) + } + pub fn all_terminal_ids(&self) -> Vec { + let mut terminal_ids = vec![]; + for tab in &self.tabs { + for pane_layout_metadata in &tab.tiled_panes { + if let PaneId::Terminal(id) = pane_layout_metadata.id { + terminal_ids.push(id); + } + } + for pane_layout_metadata in &tab.floating_panes { + if let PaneId::Terminal(id) = pane_layout_metadata.id { + terminal_ids.push(id); + } + } + } + terminal_ids + } + pub fn all_plugin_ids(&self) -> Vec { + let mut plugin_ids = vec![]; + for tab in &self.tabs { + for pane_layout_metadata in &tab.tiled_panes { + if let PaneId::Plugin(id) = pane_layout_metadata.id { + plugin_ids.push(id); + } + } + for pane_layout_metadata in &tab.floating_panes { + if let PaneId::Plugin(id) = pane_layout_metadata.id { + plugin_ids.push(id); + } + } + } + plugin_ids + } + pub fn update_terminal_commands( + &mut self, + mut terminal_ids_to_commands: HashMap>, + ) { + let mut update_cmd_in_pane_metadata = |pane_layout_metadata: &mut PaneLayoutMetadata| { + if let PaneId::Terminal(id) = pane_layout_metadata.id { + if let Some(command) = terminal_ids_to_commands.remove(&id) { + let mut command_line = command.iter(); + if let Some(command_name) = command_line.next() { + let args: Vec = command_line.map(|c| c.to_owned()).collect(); + if Self::is_default_shell(self.default_shell.as_ref(), &command_name, &args) + { + pane_layout_metadata.run = None; + } else { + let mut run_command = RunCommand::new(PathBuf::from(command_name)); + run_command.args = args; + pane_layout_metadata.run = Some(Run::Command(run_command)); + } + } + } + } + }; + for tab in self.tabs.iter_mut() { + for pane_layout_metadata in tab.tiled_panes.iter_mut() { + update_cmd_in_pane_metadata(pane_layout_metadata); + } + for pane_layout_metadata in tab.floating_panes.iter_mut() { + update_cmd_in_pane_metadata(pane_layout_metadata); + } + } + } + pub fn update_terminal_cwds(&mut self, mut terminal_ids_to_cwds: HashMap) { + if let Some(common_path_between_cwds) = + common_path_all(terminal_ids_to_cwds.values().map(|p| p.as_path())) + { + terminal_ids_to_cwds.values_mut().for_each(|p| { + if let Ok(stripped) = p.strip_prefix(&common_path_between_cwds) { + *p = PathBuf::from(stripped) + } + }); + self.global_cwd = Some(PathBuf::from(common_path_between_cwds)); + } + let mut update_cwd_in_pane_metadata = |pane_layout_metadata: &mut PaneLayoutMetadata| { + if let PaneId::Terminal(id) = pane_layout_metadata.id { + if let Some(cwd) = terminal_ids_to_cwds.remove(&id) { + pane_layout_metadata.cwd = Some(cwd); + } + } + }; + for tab in self.tabs.iter_mut() { + for pane_layout_metadata in tab.tiled_panes.iter_mut() { + update_cwd_in_pane_metadata(pane_layout_metadata); + } + for pane_layout_metadata in tab.floating_panes.iter_mut() { + update_cwd_in_pane_metadata(pane_layout_metadata); + } + } + } + pub fn update_plugin_cmds(&mut self, mut plugin_ids_to_run_plugins: HashMap) { + let mut update_cmd_in_pane_metadata = |pane_layout_metadata: &mut PaneLayoutMetadata| { + if let PaneId::Plugin(id) = pane_layout_metadata.id { + if let Some(run_plugin) = plugin_ids_to_run_plugins.remove(&id) { + pane_layout_metadata.run = Some(Run::Plugin(run_plugin)); + } + } + }; + for tab in self.tabs.iter_mut() { + for pane_layout_metadata in tab.tiled_panes.iter_mut() { + update_cmd_in_pane_metadata(pane_layout_metadata); + } + for pane_layout_metadata in tab.floating_panes.iter_mut() { + update_cmd_in_pane_metadata(pane_layout_metadata); + } + } + } +} + +impl Into for SessionLayoutMetadata { + fn into(self) -> GlobalLayoutManifest { + GlobalLayoutManifest { + default_layout: self.default_layout, + default_shell: self.default_shell, + global_cwd: self.global_cwd, + tabs: self + .tabs + .into_iter() + .map(|t| (t.name.clone().unwrap_or_default(), t.into())) + .collect(), + } + } +} + +impl Into for TabLayoutMetadata { + fn into(self) -> TabLayoutManifest { + TabLayoutManifest { + tiled_panes: self.tiled_panes.into_iter().map(|t| t.into()).collect(), + floating_panes: self.floating_panes.into_iter().map(|t| t.into()).collect(), + is_focused: self.is_focused, + hide_floating_panes: self.hide_floating_panes, + } + } +} + +impl Into for PaneLayoutMetadata { + fn into(self) -> PaneLayoutManifest { + PaneLayoutManifest { + geom: self.geom, + run: self.run, + cwd: self.cwd, + is_borderless: self.is_borderless, + title: self.title, + is_focused: self.is_focused, + pane_contents: self.pane_contents, + } + } +} + +#[derive(Default, Debug, Clone)] +pub struct TabLayoutMetadata { + name: Option, + tiled_panes: Vec, + floating_panes: Vec, + is_focused: bool, + hide_floating_panes: bool, +} + +#[derive(Debug, Clone)] +pub struct PaneLayoutMetadata { + id: PaneId, + geom: PaneGeom, + run: Option, + cwd: Option, + is_borderless: bool, + title: Option, + is_focused: bool, + pane_contents: Option, +} + +impl PaneLayoutMetadata { + pub fn new( + id: PaneId, + geom: PaneGeom, + is_borderless: bool, + run: Option, + title: Option, + is_focused: bool, + pane_contents: Option, + ) -> Self { + PaneLayoutMetadata { + id, + geom, + run, + cwd: None, + is_borderless, + title, + is_focused, + pane_contents, + } + } +} diff --git a/zellij-server/src/tab/layout_applier.rs b/zellij-server/src/tab/layout_applier.rs index cf59e1fc27..24b2116194 100644 --- a/zellij-server/src/tab/layout_applier.rs +++ b/zellij-server/src/tab/layout_applier.rs @@ -101,8 +101,9 @@ impl<'a> LayoutApplier<'a> { mut new_plugin_ids: HashMap<(RunPluginLocation, PluginUserConfiguration), Vec>, client_id: ClientId, ) -> Result { - // true => layout has floating panes + // true => should_show_floating_panes let layout_name = layout.name.clone(); + let hide_floating_panes = layout.hide_floating_panes; self.apply_tiled_panes_layout(layout, new_terminal_ids, &mut new_plugin_ids, client_id)?; let layout_has_floating_panes = self.apply_floating_panes_layout( floating_panes_layout, @@ -110,7 +111,8 @@ impl<'a> LayoutApplier<'a> { &mut new_plugin_ids, layout_name, )?; - return Ok(layout_has_floating_panes); + let should_show_floating_panes = layout_has_floating_panes && !hide_floating_panes; + return Ok(should_show_floating_panes); } pub fn apply_tiled_panes_layout_to_existing_panes( &mut self, @@ -255,6 +257,11 @@ impl<'a> LayoutApplier<'a> { layout.run.clone(), self.debug, ); + if let Some(pane_initial_contents) = &layout.pane_initial_contents { + new_plugin.handle_pty_bytes(pane_initial_contents.as_bytes().into()); + new_plugin.handle_pty_bytes("\n\r".as_bytes().into()); + } + new_plugin.set_borderless(layout.borderless); if let Some(exclude_from_sync) = layout.exclude_from_sync { new_plugin.set_exclude_from_sync(exclude_from_sync); @@ -286,6 +293,10 @@ impl<'a> LayoutApplier<'a> { layout.run.clone(), self.debug, ); + if let Some(pane_initial_contents) = &layout.pane_initial_contents { + new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into()); + new_pane.handle_pty_bytes("\n\r".as_bytes().into()); + } new_pane.set_borderless(layout.borderless); if let Some(exclude_from_sync) = layout.exclude_from_sync { new_pane.set_exclude_from_sync(exclude_from_sync); @@ -371,6 +382,10 @@ impl<'a> LayoutApplier<'a> { floating_pane_layout.run.clone(), self.debug, ); + if let Some(pane_initial_contents) = &floating_pane_layout.pane_initial_contents { + new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into()); + new_pane.handle_pty_bytes("\n\r".as_bytes().into()); + } new_pane.set_borderless(false); new_pane.set_content_offset(Offset::frame(1)); resize_pty!( @@ -406,6 +421,10 @@ impl<'a> LayoutApplier<'a> { floating_pane_layout.run.clone(), self.debug, ); + if let Some(pane_initial_contents) = &floating_pane_layout.pane_initial_contents { + new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into()); + new_pane.handle_pty_bytes("\n\r".as_bytes().into()); + } new_pane.set_borderless(false); new_pane.set_content_offset(Offset::frame(1)); if let Some(held_command) = hold_for_command { diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index 1444a9c822..110ffcdd14 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -148,7 +148,7 @@ pub(crate) struct Tab { pub prev_name: String, tiled_panes: TiledPanes, floating_panes: FloatingPanes, - suppressed_panes: HashMap>, + suppressed_panes: HashMap)>, // bool => is scrollback editor max_panes: Option, viewport: Rc>, // includes all non-UI panes display_area: Rc>, // includes all panes (including eg. the status bar and tab bar in the default layout) @@ -461,6 +461,7 @@ pub trait Pane { fn start_loading_indication(&mut self, _loading_indication: LoadingIndication) {} // only relevant for plugins fn progress_animation_offset(&mut self) {} // only relevant for plugins fn current_title(&self) -> String; + fn custom_title(&self) -> Option; fn is_held(&self) -> bool { false } @@ -471,6 +472,9 @@ pub trait Pane { None } fn rename(&mut self, _buf: Vec) {} + fn serialize(&self, _scrollback_lines_to_serialize: Option) -> Option { + None + } } #[derive(Clone, Debug)] @@ -627,7 +631,7 @@ impl Tab { ) -> Result<()> { self.swap_layouts .set_base_layout((layout.clone(), floating_panes_layout.clone())); - let layout_has_floating_panes = LayoutApplier::new( + let should_show_floating_panes = LayoutApplier::new( &self.viewport, &self.senders, &self.sixel_image_store, @@ -653,10 +657,11 @@ impl Tab { new_plugin_ids, client_id, )?; - if layout_has_floating_panes { - if !self.floating_panes.panes_are_visible() { - self.toggle_floating_panes(Some(client_id), None)?; - } + #[allow(clippy::if_same_then_else)] + if should_show_floating_panes && !self.floating_panes.panes_are_visible() { + self.toggle_floating_panes(Some(client_id), None)?; + } else if !should_show_floating_panes && self.floating_panes.panes_are_visible() { + self.toggle_floating_panes(Some(client_id), None)?; } self.tiled_panes.reapply_pane_frames(); self.is_pending = false; @@ -1016,7 +1021,7 @@ impl Tab { pid: PaneId, initial_pane_title: Option, should_float: Option, - run_plugin: Option, // only relevant if this is a plugin pane + invoked_with: Option, client_id: Option, ) -> Result<()> { let err_context = || format!("failed to create new pane with id {pid:?}"); @@ -1042,7 +1047,7 @@ impl Tab { self.terminal_emulator_colors.clone(), self.terminal_emulator_color_codes.clone(), initial_pane_title, - None, + invoked_with, self.debug, )) as Box }, @@ -1064,7 +1069,7 @@ impl Tab { self.character_cell_size.clone(), self.connected_clients.borrow().iter().copied().collect(), self.style, - run_plugin, + invoked_with, self.debug, )) as Box }, @@ -1116,8 +1121,9 @@ impl Tab { }; match replaced_pane { Some(replaced_pane) => { + let is_scrollback_editor = true; self.suppressed_panes - .insert(PaneId::Terminal(pid), replaced_pane); + .insert(PaneId::Terminal(pid), (is_scrollback_editor, replaced_pane)); self.get_active_pane(client_id) .with_context(|| format!("no active pane found for client {client_id}")) .and_then(|current_active_pane| { @@ -1149,7 +1155,7 @@ impl Tab { &mut self, old_pane_id: PaneId, new_pane_id: PaneId, - run_plugin: Option, + run: Option, ) -> Result<()> { // this method creates a new pane from pid and replaces it with the active pane // the active pane is then suppressed (hidden and not rendered) until the current @@ -1159,7 +1165,7 @@ impl Tab { match new_pane_id { PaneId::Terminal(new_pane_id) => { let next_terminal_position = self.get_next_terminal_position(); // TODO: this is not accurate in this case - let mut new_pane = TerminalPane::new( + let new_pane = TerminalPane::new( new_pane_id, PaneGeom::default(), // the initial size will be set later self.style, @@ -1171,7 +1177,7 @@ impl Tab { self.terminal_emulator_colors.clone(), self.terminal_emulator_color_codes.clone(), None, - None, + run, self.debug, ); let replaced_pane = if self.floating_panes.panes_contain(&old_pane_id) { @@ -1184,14 +1190,17 @@ impl Tab { }; match replaced_pane { Some(replaced_pane) => { - resize_pty!( + let _ = resize_pty!( replaced_pane, self.os_api, self.senders, self.character_cell_size ); - self.suppressed_panes - .insert(PaneId::Terminal(new_pane_id), replaced_pane); + let is_scrollback_editor = false; + self.suppressed_panes.insert( + PaneId::Terminal(new_pane_id), + (is_scrollback_editor, replaced_pane), + ); }, None => { Err::<(), _>(anyhow!( @@ -1203,8 +1212,7 @@ impl Tab { } }, PaneId::Plugin(plugin_pid) => { - // TBD, currently unsupported - let mut new_pane = PluginPane::new( + let new_pane = PluginPane::new( plugin_pid, PaneGeom::default(), // this will be filled out later self.senders @@ -1221,7 +1229,7 @@ impl Tab { self.character_cell_size.clone(), self.connected_clients.borrow().iter().copied().collect(), self.style, - run_plugin, + run, self.debug, ); let replaced_pane = if self.floating_panes.panes_contain(&old_pane_id) { @@ -1234,14 +1242,17 @@ impl Tab { }; match replaced_pane { Some(replaced_pane) => { - resize_pty!( + let _ = resize_pty!( replaced_pane, self.os_api, self.senders, self.character_cell_size ); - self.suppressed_panes - .insert(PaneId::Plugin(plugin_pid), replaced_pane); + let is_scrollback_editor = false; + self.suppressed_panes.insert( + PaneId::Plugin(plugin_pid), + (is_scrollback_editor, replaced_pane), + ); }, None => { Err::<(), _>(anyhow!( @@ -1418,7 +1429,7 @@ impl Tab { || self .suppressed_panes .values() - .any(|s_p| s_p.pid() == PaneId::Terminal(pid)) + .any(|s_p| s_p.1.pid() == PaneId::Terminal(pid)) } pub fn has_plugin(&self, plugin_id: u32) -> bool { self.tiled_panes.panes_contain(&PaneId::Plugin(plugin_id)) @@ -1428,12 +1439,15 @@ impl Tab { || self .suppressed_panes .values() - .any(|s_p| s_p.pid() == PaneId::Plugin(plugin_id)) + .any(|s_p| s_p.1.pid() == PaneId::Plugin(plugin_id)) } pub fn has_pane_with_pid(&self, pid: &PaneId) -> bool { self.tiled_panes.panes_contain(pid) || self.floating_panes.panes_contain(pid) - || self.suppressed_panes.values().any(|s_p| s_p.pid() == *pid) + || self + .suppressed_panes + .values() + .any(|s_p| s_p.1.pid() == *pid) } pub fn has_non_suppressed_pane_with_pid(&self, pid: &PaneId) -> bool { self.tiled_panes.panes_contain(pid) || self.floating_panes.panes_contain(pid) @@ -1452,7 +1466,8 @@ impl Tab { .or_else(|| { self.suppressed_panes .values_mut() - .find(|s_p| s_p.pid() == PaneId::Terminal(pid)) + .find(|s_p| s_p.1.pid() == PaneId::Terminal(pid)) + .map(|s_p| &mut s_p.1) }) { // If the pane is scrolled buffer the vte events @@ -1484,7 +1499,8 @@ impl Tab { .or_else(|| { self.suppressed_panes .values_mut() - .find(|s_p| s_p.pid() == PaneId::Plugin(pid)) + .find(|s_p| s_p.1.pid() == PaneId::Plugin(pid)) + .map(|s_p| &mut s_p.1) }) { plugin_pane.handle_plugin_bytes(client_id, bytes); @@ -1511,7 +1527,8 @@ impl Tab { .or_else(|| { self.suppressed_panes .values_mut() - .find(|s_p| s_p.pid() == PaneId::Terminal(pid)) + .find(|s_p| s_p.1.pid() == PaneId::Terminal(pid)) + .map(|s_p| &mut s_p.1) }) { if self.pids_waiting_resize.remove(&pid) { @@ -1639,7 +1656,7 @@ impl Tab { .floating_panes .get_mut(&pane_id) .or_else(|| self.tiled_panes.get_pane_mut(pane_id)) - .or_else(|| self.suppressed_panes.get_mut(&pane_id)) + .or_else(|| self.suppressed_panes.get_mut(&pane_id).map(|p| &mut p.1)) .ok_or_else(|| anyhow!(format!("failed to find pane with id {pane_id:?}"))) .with_context(err_context)?; @@ -1914,12 +1931,18 @@ impl Tab { } } } - fn get_tiled_panes(&self) -> impl Iterator)> { + pub(crate) fn get_tiled_panes(&self) -> impl Iterator)> { self.tiled_panes.get_panes() } - fn get_floating_panes(&self) -> impl Iterator)> { + pub(crate) fn get_floating_panes(&self) -> impl Iterator)> { self.floating_panes.get_panes() } + pub(crate) fn get_suppressed_panes( + &self, + ) -> impl Iterator))> { + // bool => is_scrollback_editor + self.suppressed_panes.iter() + } fn get_selectable_tiled_panes(&self) -> impl Iterator)> { self.get_tiled_panes().filter(|(_, p)| p.selectable()) } @@ -2412,7 +2435,7 @@ impl Tab { } closed_pane } else if self.suppressed_panes.contains_key(&id) { - self.suppressed_panes.remove(&id) + self.suppressed_panes.remove(&id).map(|s_p| s_p.1) } else { None } @@ -2454,7 +2477,7 @@ impl Tab { pane_id ) }) - .and_then(|suppressed_pane| { + .and_then(|(_is_scrollback_editor, suppressed_pane)| { let suppressed_pane_id = suppressed_pane.pid(); let replaced_pane = if self.are_floating_panes_visible() { Some(self.floating_panes.replace_pane(pane_id, suppressed_pane)).transpose()? @@ -3312,7 +3335,11 @@ impl Tab { .floating_panes .get_pane_mut(pane_id) .or_else(|| self.tiled_panes.get_pane_mut(pane_id)) - .or_else(|| self.suppressed_panes.get_mut(&pane_id)) + .or_else(|| { + self.suppressed_panes + .get_mut(&pane_id) + .map(|s_p| &mut s_p.1) + }) .with_context(err_context)?; pane.rename(buf); Ok(()) @@ -3430,7 +3457,8 @@ impl Tab { .or_else(|| { self.suppressed_panes .values_mut() - .find(|s_p| s_p.pid() == pane_id) + .find(|s_p| s_p.1.pid() == pane_id) + .map(|s_p| &mut s_p.1) }) { pane.add_red_pane_frame_color_override(error_text); @@ -3444,7 +3472,8 @@ impl Tab { .or_else(|| { self.suppressed_panes .values_mut() - .find(|s_p| s_p.pid() == pane_id) + .find(|s_p| s_p.1.pid() == pane_id) + .map(|s_p| &mut s_p.1) }) { pane.clear_pane_frame_color_override(); @@ -3458,7 +3487,8 @@ impl Tab { .or_else(|| { self.suppressed_panes .values_mut() - .find(|s_p| s_p.pid() == PaneId::Plugin(pid)) + .find(|s_p| s_p.1.pid() == PaneId::Plugin(pid)) + .map(|s_p| &mut s_p.1) }) { plugin_pane.update_loading_indication(loading_indication); @@ -3476,7 +3506,8 @@ impl Tab { .or_else(|| { self.suppressed_panes .values_mut() - .find(|s_p| s_p.pid() == PaneId::Plugin(pid)) + .find(|s_p| s_p.1.pid() == PaneId::Plugin(pid)) + .map(|s_p| &mut s_p.1) }) { plugin_pane.start_loading_indication(loading_indication); @@ -3490,7 +3521,8 @@ impl Tab { .or_else(|| { self.suppressed_panes .values_mut() - .find(|s_p| s_p.pid() == PaneId::Plugin(pid)) + .find(|s_p| s_p.1.pid() == PaneId::Plugin(pid)) + .map(|s_p| &mut s_p.1) }) { plugin_pane.progress_animation_offset(); @@ -3519,7 +3551,7 @@ impl Tab { let run = Some(Run::Plugin(run_plugin.clone())); self.suppressed_panes .iter() - .find(|(_id, s_p)| s_p.invoked_with() == &run) + .find(|(_id, s_p)| s_p.1.invoked_with() == &run) .map(|(id, _)| *id) }) } @@ -3546,10 +3578,10 @@ impl Tab { Some(pane) => { if should_float { self.show_floating_panes(); - self.add_floating_pane(pane, pane_id, Some(client_id)) + self.add_floating_pane(pane.1, pane_id, Some(client_id)) } else { self.hide_floating_panes(); - self.add_tiled_pane(pane, pane_id, Some(client_id)) + self.add_tiled_pane(pane.1, pane_id, Some(client_id)) } }, None => Ok(()), @@ -3561,7 +3593,9 @@ impl Tab { // scrollback editor), but it has to take itself out on its own (eg. a plugin using the // show_self() method) if let Some(pane) = self.close_pane(pane_id, true, Some(client_id)) { - self.suppressed_panes.insert(pane_id, pane); + let is_scrollback_editor = false; + self.suppressed_panes + .insert(pane_id, (is_scrollback_editor, pane)); } } pub fn pane_infos(&self) -> Vec { @@ -3570,7 +3604,7 @@ impl Tab { let mut floating_pane_info = self.floating_panes.pane_info(); pane_info.append(&mut tiled_pane_info); pane_info.append(&mut floating_pane_info); - for (pane_id, pane) in self.suppressed_panes.iter() { + for (pane_id, (_is_scrollback_editor, pane)) in self.suppressed_panes.iter() { let mut pane_info_for_suppressed_pane = pane_info_for_pane(pane_id, pane); pane_info_for_suppressed_pane.is_floating = false; pane_info_for_suppressed_pane.is_suppressed = true; @@ -3646,7 +3680,8 @@ impl Tab { .or_else(|| { self.suppressed_panes .values_mut() - .find(|s_p| s_p.pid() == PaneId::Plugin(pid)) + .find(|s_p| s_p.1.pid() == PaneId::Plugin(pid)) + .map(|s_p| &mut s_p.1) }) { plugin_pane.request_permissions_from_user(permissions); diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 5d9e9abc40..cdadd4d676 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -233,6 +233,11 @@ fn create_new_screen(size: Size) -> Screen { let auto_layout = true; let session_is_mirrored = true; let copy_options = CopyOptions::default(); + let default_layout = Box::new(Layout::default()); + let default_shell = None; + let session_serialization = true; + let serialize_pane_viewport = false; + let scrollback_lines_to_serialize = None; let debug = false; let screen = Screen::new( @@ -245,6 +250,11 @@ fn create_new_screen(size: Size) -> Screen { session_is_mirrored, copy_options, debug, + default_layout, + default_shell, + session_serialization, + serialize_pane_viewport, + scrollback_lines_to_serialize, ); screen } @@ -300,6 +310,7 @@ impl MockScreen { client_attributes, Box::new(config_options), debug, + Box::new(Layout::default()), ) .expect("TEST") }) diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap index 0943801b24..7ad89e776c 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 2217 +assertion_line: 2246 expression: "format!(\"{:#?}\", new_tab_action)" --- Some( @@ -25,6 +25,8 @@ Some( is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -39,6 +41,8 @@ Some( is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -50,6 +54,8 @@ Some( is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ), [], diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap index 257ffddf45..f3bcbecb0b 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 2263 +assertion_line: 2292 expression: "format!(\"{:#?}\", new_tab_instruction)" --- NewTab( @@ -28,6 +28,8 @@ NewTab( is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -46,6 +48,8 @@ NewTab( is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -64,6 +68,8 @@ NewTab( is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -75,6 +81,8 @@ NewTab( is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ), [], diff --git a/zellij-utils/Cargo.toml b/zellij-utils/Cargo.toml index 45398a516a..55a596fbcc 100644 --- a/zellij-utils/Cargo.toml +++ b/zellij-utils/Cargo.toml @@ -43,6 +43,7 @@ uuid = { version = "1.4.1", features = ["serde", "v4"] } async-channel = "1.8.0" include_dir = "0.7.3" prost = "0.11.9" +common-path = "1.0.0" #[cfg(not(target_family = "wasm"))] [target.'cfg(not(target_family = "wasm"))'.dependencies] @@ -52,9 +53,11 @@ signal-hook = "0.3" interprocess = "1.2.1" async-std = { version = "1.3.0", features = ["unstable"] } notify-debouncer-full = "0.1.0" +humantime = "2.1.0" [dev-dependencies] insta = { version = "1.6.0", features = ["backtrace"] } +expect-test = "1.4.1" [build-dependencies] prost-build = "0.11.9" diff --git a/zellij-utils/assets/config/default.kdl b/zellij-utils/assets/config/default.kdl index bf54c86208..f27733a0c3 100644 --- a/zellij-utils/assets/config/default.kdl +++ b/zellij-utils/assets/config/default.kdl @@ -229,6 +229,25 @@ plugins { // // auto_layout true +// Whether sessions should be serialized to the cache folder (including their tabs/panes, cwds and running commands) so that they can later be resurrected +// Options: +// - true (default) +// - false +// +// session_serialization false + +// Whether pane viewports are serialized along with the session, default is false +// Options: +// - true +// - false (default) +// serialize_pane_viewport true + +// Scrollback lines to serialize along with the pane viewport when serializing sessions, 0 +// defaults to the scrollback size. If this number is higher than the scrollback size, it will +// also default to the scrollback size. This does nothing if `serialize_pane_viewport` is not true. +// +// scrollback_lines_to_serialize 10000 + // Define color themes for Zellij // For more examples, see: https://github.com/zellij-org/zellij/tree/main/example/themes // Once these themes are defined, one of them should to be selected in the "theme" section of this file diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs index d096254502..dd810658d0 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -98,7 +98,11 @@ pub enum SessionCommand { pub enum Sessions { /// List active sessions #[clap(visible_alias = "ls")] - ListSessions, + ListSessions { + /// Do not add colors and formatting to the list (useful for parsing) + #[clap(short, long, value_parser, takes_value(false), default_value("false"))] + no_formatting: bool, + }, /// Attach to a session #[clap(visible_alias = "a")] @@ -118,9 +122,13 @@ pub enum Sessions { /// Change the behaviour of zellij #[clap(subcommand, name = "options")] options: Option>, + + /// If resurrecting a dead session, immediately run all its commands on startup + #[clap(short, long, value_parser, takes_value(false), default_value("false"))] + force_run_commands: bool, }, - /// Kill the specific session + /// Kill a specific session #[clap(visible_alias = "k")] KillSession { /// Name of target session @@ -128,6 +136,17 @@ pub enum Sessions { target_session: Option, }, + /// Delete a specific session + #[clap(visible_alias = "d")] + DeleteSession { + /// Name of target session + #[clap(value_parser)] + target_session: Option, + /// Kill the session if it's running before deleting it + #[clap(short, long, value_parser, takes_value(false), default_value("false"))] + force: bool, + }, + /// Kill all sessions #[clap(visible_alias = "ka")] KillAllSessions { @@ -135,6 +154,18 @@ pub enum Sessions { #[clap(short, long, value_parser)] yes: bool, }, + + /// Delete all sessions + #[clap(visible_alias = "da")] + DeleteAllSessions { + /// Automatic yes to prompts + #[clap(short, long, value_parser)] + yes: bool, + /// Kill the sessions if they're running before deleting them + #[clap(short, long, value_parser, takes_value(false), default_value("false"))] + force: bool, + }, + /// Send actions to a specific session #[clap(visible_alias = "ac")] #[clap(subcommand)] @@ -271,6 +302,8 @@ pub enum CliAction { #[clap(short, long, value_parser, default_value("false"), takes_value(false))] full: bool, }, + /// Dump current layout to stdout + DumpLayout, /// Open the pane scrollback in your default editor EditScrollback, /// Scroll up in the focused pane diff --git a/zellij-utils/src/consts.rs b/zellij-utils/src/consts.rs index 0f679f668f..05794495ad 100644 --- a/zellij-utils/src/consts.rs +++ b/zellij-utils/src/consts.rs @@ -20,6 +20,18 @@ pub const SYSTEM_DEFAULT_DATA_DIR_PREFIX: &str = system_default_data_dir(); pub static ZELLIJ_DEFAULT_THEMES: Dir = include_dir!("$CARGO_MANIFEST_DIR/assets/themes"); +pub fn session_info_cache_file_name(session_name: &str) -> PathBuf { + session_info_folder_for_session(session_name).join("session-metadata.kdl") +} + +pub fn session_layout_cache_file_name(session_name: &str) -> PathBuf { + session_info_folder_for_session(session_name).join("session-layout.kdl") +} + +pub fn session_info_folder_for_session(session_name: &str) -> PathBuf { + ZELLIJ_SESSION_INFO_CACHE_DIR.join(session_name) +} + const fn system_default_data_dir() -> &'static str { if let Some(data_dir) = std::option_env!("PREFIX") { data_dir diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index 8022859e3b..b9fd65d60c 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -258,6 +258,7 @@ pub enum ScreenContext { Exit, ClearScreen, DumpScreen, + DumpLayout, EditScrollback, ScrollUp, ScrollUpAt, @@ -346,6 +347,7 @@ pub enum ScreenContext { UpdateSessionInfos, ReplacePane, NewInPlacePluginPane, + DumpLayoutToHd, } /// Stack call representations corresponding to the different types of [`PtyInstruction`]s. @@ -362,6 +364,8 @@ pub enum PtyContext { CloseTab, ReRunCommandInPane, SpawnInPlaceTerminal, + DumpLayout, + LogLayoutToHd, Exit, } @@ -384,6 +388,8 @@ pub enum PluginContext { PostMessageToPlugin, PluginSubscribedToEvents, PermissionRequestResult, + DumpLayout, + LogLayoutToHd, } /// Stack call representations corresponding to the different types of [`ClientInstruction`]s. @@ -438,6 +444,7 @@ pub enum BackgroundJobContext { StopPluginLoadingAnimation, ReadAllSessionInfosOnMachine, ReportSessionInfo, + ReportLayoutInfo, Exit, } diff --git a/zellij-utils/src/home.rs b/zellij-utils/src/home.rs new file mode 100644 index 0000000000..e0f123990f --- /dev/null +++ b/zellij-utils/src/home.rs @@ -0,0 +1,84 @@ +//! +//! # This module contain everything you'll need to access local system paths +//! containing configuration and layouts + +use crate::consts::{SYSTEM_DEFAULT_DATA_DIR_PREFIX, ZELLIJ_PROJ_DIR}; + +#[cfg(not(test))] +use crate::consts::SYSTEM_DEFAULT_CONFIG_DIR; + +use directories::BaseDirs; +use std::{path::Path, path::PathBuf}; + +pub(crate) const CONFIG_LOCATION: &str = ".config/zellij"; + +#[cfg(not(test))] +/// Goes through a predefined list and checks for an already +/// existing config directory, returns the first match +pub fn find_default_config_dir() -> Option { + default_config_dirs() + .into_iter() + .filter(|p| p.is_some()) + .find(|p| p.clone().unwrap().exists()) + .flatten() +} + +#[cfg(test)] +pub fn find_default_config_dir() -> Option { + None +} + +/// Order in which config directories are checked +#[cfg(not(test))] +pub(crate) fn default_config_dirs() -> Vec> { + vec![ + home_config_dir(), + Some(xdg_config_dir()), + Some(Path::new(SYSTEM_DEFAULT_CONFIG_DIR).to_path_buf()), + ] +} + +/// Looks for an existing dir, uses that, else returns a +/// dir matching the config spec. +pub fn get_default_data_dir() -> PathBuf { + [ + xdg_data_dir(), + Path::new(SYSTEM_DEFAULT_DATA_DIR_PREFIX).join("share/zellij"), + ] + .into_iter() + .find(|p| p.exists()) + .unwrap_or_else(xdg_data_dir) +} + +pub fn xdg_config_dir() -> PathBuf { + ZELLIJ_PROJ_DIR.config_dir().to_owned() +} + +pub fn xdg_data_dir() -> PathBuf { + ZELLIJ_PROJ_DIR.data_dir().to_owned() +} + +pub fn home_config_dir() -> Option { + if let Some(user_dirs) = BaseDirs::new() { + let config_dir = user_dirs.home_dir().join(CONFIG_LOCATION); + Some(config_dir) + } else { + None + } +} + +pub fn get_layout_dir(config_dir: Option) -> Option { + config_dir.map(|dir| dir.join("layouts")) +} + +pub fn default_layout_dir() -> Option { + find_default_config_dir().map(|dir| dir.join("layouts")) +} + +pub fn get_theme_dir(config_dir: Option) -> Option { + config_dir.map(|dir| dir.join("themes")) +} + +pub fn default_theme_dir() -> Option { + find_default_config_dir().map(|dir| dir.join("themes")) +} diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index c83092ce67..c0714e59f1 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -6,10 +6,11 @@ use super::layout::{ TiledPaneLayout, }; use crate::cli::CliAction; -use crate::data::{Direction, InputMode, Resize}; +use crate::data::InputMode; +use crate::data::{Direction, Resize}; +use crate::home::{find_default_config_dir, get_layout_dir}; use crate::input::config::{Config, ConfigError, KdlError}; use crate::input::options::OnForceClose; -use crate::setup::{find_default_config_dir, get_layout_dir}; use miette::{NamedSource, Report}; use serde::{Deserialize, Serialize}; @@ -123,6 +124,8 @@ pub enum Action { ClearScreen, /// Dumps the screen to a file DumpScreen(String, bool), + /// Dumps + DumpLayout, /// Scroll up in focus pane. EditScrollback, ScrollUp, @@ -279,6 +282,7 @@ impl Action { path.as_os_str().to_string_lossy().into(), full, )]), + CliAction::DumpLayout => Ok(vec![Action::DumpLayout]), CliAction::EditScrollback => Ok(vec![Action::EditScrollback]), CliAction::ScrollUp => Ok(vec![Action::ScrollUp]), CliAction::ScrollDown => Ok(vec![Action::ScrollDown]), diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs index 55170aec24..23c05d4f8d 100644 --- a/zellij-utils/src/input/config.rs +++ b/zellij-utils/src/input/config.rs @@ -13,7 +13,7 @@ use super::plugins::{PluginsConfig, PluginsConfigError}; use super::theme::{Themes, UiConfig}; use crate::cli::{CliArgs, Command}; use crate::envs::EnvironmentVariables; -use crate::setup; +use crate::{home, setup}; const DEFAULT_CONFIG_FILE_NAME: &str = "config.kdl"; @@ -143,7 +143,7 @@ impl TryFrom<&CliArgs> for Config { let config_dir = opts .config_dir .clone() - .or_else(setup::find_default_config_dir); + .or_else(home::find_default_config_dir); if let Some(ref config) = config_dir { let path = config.join(DEFAULT_CONFIG_FILE_NAME); diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index 262f27788c..ace471f8f0 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -10,14 +10,16 @@ // then [`zellij-utils`] could be a proper place. use crate::{ data::Direction, + home::find_default_config_dir, input::{ command::RunCommand, config::{Config, ConfigError}, }, - pane_size::{Dimension, PaneGeom}, - setup, + pane_size::{Constraint, Dimension, PaneGeom}, + setup::{self}, }; +use std::fmt::{Display, Formatter}; use std::str::FromStr; use super::plugins::{PluginTag, PluginsConfigError}; @@ -205,6 +207,14 @@ impl Run { _ => false, } } + pub fn get_cwd(&self) -> Option { + match self { + Run::Plugin(_) => None, // TBD + Run::Command(run_command) => run_command.cwd.clone(), + Run::EditFile(_file, _line_num, cwd) => cwd.clone(), + Run::Cwd(cwd) => Some(cwd.clone()), + } + } } #[allow(clippy::derive_hash_xor_eq)] @@ -325,6 +335,12 @@ impl RunPluginLocation { _ => Err(PluginsConfigError::InvalidUrlScheme(url)), } } + pub fn display(&self) -> String { + match self { + RunPluginLocation::File(pathbuf) => format!("file:{}", pathbuf.display()), + RunPluginLocation::Zellij(plugin_tag) => format!("zellij:{}", plugin_tag), + } + } } impl From<&RunPluginLocation> for Url { @@ -362,6 +378,17 @@ pub enum LayoutConstraint { NoConstraint, } +impl Display for LayoutConstraint { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + match self { + LayoutConstraint::MaxPanes(max_panes) => write!(f, "max_panes={}", max_panes), + LayoutConstraint::MinPanes(min_panes) => write!(f, "min_panes={}", min_panes), + LayoutConstraint::ExactPanes(exact_panes) => write!(f, "exact_panes={}", exact_panes), + LayoutConstraint::NoConstraint => write!(f, ""), + } + } +} + pub type SwapTiledLayout = (BTreeMap, Option); // Option is the swap layout name pub type SwapFloatingLayout = ( BTreeMap>, @@ -384,6 +411,15 @@ pub enum PercentOrFixed { Fixed(usize), // An absolute number of columns or rows } +impl From for PercentOrFixed { + fn from(dimension: Dimension) -> Self { + match dimension.constraint { + Constraint::Percent(percent) => PercentOrFixed::Percent(percent as usize), + Constraint::Fixed(fixed_size) => PercentOrFixed::Fixed(fixed_size), + } + } +} + impl PercentOrFixed { pub fn to_position(&self, whole: usize) -> usize { match self { @@ -438,6 +474,7 @@ pub struct FloatingPaneLayout { pub run: Option, pub focus: Option, pub already_running: bool, + pub pane_initial_contents: Option, } impl FloatingPaneLayout { @@ -449,6 +486,11 @@ impl FloatingPaneLayout { }, } } + pub fn add_start_suspended(&mut self, start_suspended: Option) { + if let Some(run) = self.run.as_mut() { + run.add_start_suspended(start_suspended); + } + } } impl From<&TiledPaneLayout> for FloatingPaneLayout { @@ -476,6 +518,8 @@ pub struct TiledPaneLayout { pub is_expanded_in_stack: bool, pub exclude_from_sync: Option, pub run_instructions_to_ignore: Vec>, + pub hide_floating_panes: bool, // only relevant if this is the base layout + pub pane_initial_contents: Option, } impl TiledPaneLayout { @@ -723,6 +767,14 @@ impl TiledPaneLayout { } false } + pub fn recursively_add_start_suspended(&mut self, start_suspended: Option) { + if let Some(run) = self.run.as_mut() { + run.add_start_suspended(start_suspended); + } + for child in self.children.iter_mut() { + child.recursively_add_start_suspended(start_suspended); + } + } } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] @@ -824,7 +876,15 @@ impl Layout { Layout::stringified_from_default_assets(layout) } }, - None => Layout::stringified_from_default_assets(layout), + None => { + let home = find_default_config_dir(); + let Some(home) = home else { + return Layout::stringified_from_default_assets(layout) + }; + + let layout_path = &home.join(layout); + Self::stringified_from_path(layout_path) + }, } } pub fn stringified_from_path( @@ -936,6 +996,15 @@ impl Layout { self.focused_tab_index } + pub fn recursively_add_start_suspended(&mut self, start_suspended: Option) { + for (_tab_name, tiled_panes, floating_panes) in self.tabs.iter_mut() { + tiled_panes.recursively_add_start_suspended(start_suspended); + for floating_pane in floating_panes.iter_mut() { + floating_pane.add_start_suspended(start_suspended); + } + } + } + fn swap_layout_and_path(path: &Path) -> Option<(String, String)> { // Option let mut swap_layout_path = PathBuf::from(path); @@ -975,7 +1044,6 @@ fn split_space( layout: &TiledPaneLayout, total_space_to_split: &PaneGeom, ) -> Result, &'static str> { - let mut pane_positions = Vec::new(); let sizes: Vec> = if layout.children_are_stacked { let index_of_expanded_pane = layout.children.iter().position(|p| p.is_expanded_in_stack); let mut sizes: Vec> = layout @@ -1051,6 +1119,7 @@ fn split_space( Dimension::percent(free_percent / flex_parts as f64) }, }; + split_dimension.adjust_inner( total_split_dimension_space .as_usize() @@ -1077,26 +1146,13 @@ fn split_space( split_geom.push(geom); current_position += split_dimension.as_usize(); } - - if total_pane_size < split_dimension_space.as_usize() { - // add extra space from rounding errors to the last pane - let increase_by = split_dimension_space.as_usize() - total_pane_size; - if let Some(last_geom) = split_geom.last_mut() { - match layout.children_split_direction { - SplitDirection::Vertical => last_geom.cols.increase_inner(increase_by), - SplitDirection::Horizontal => last_geom.rows.increase_inner(increase_by), - } - } - } else if total_pane_size > split_dimension_space.as_usize() { - // remove extra space from rounding errors to the last pane - let decrease_by = total_pane_size - split_dimension_space.as_usize(); - if let Some(last_geom) = split_geom.last_mut() { - match layout.children_split_direction { - SplitDirection::Vertical => last_geom.cols.decrease_inner(decrease_by), - SplitDirection::Horizontal => last_geom.rows.decrease_inner(decrease_by), - } - } - } + adjust_geoms_for_rounding_errors( + total_pane_size, + &mut split_geom, + split_dimension_space, + layout.children_split_direction, + ); + let mut pane_positions = Vec::new(); for (i, part) in layout.children.iter().enumerate() { let part_position_and_size = split_geom.get(i).unwrap(); if !part.children.is_empty() { @@ -1115,6 +1171,74 @@ fn split_space( Ok(pane_positions) } +fn adjust_geoms_for_rounding_errors( + total_pane_size: usize, + split_geoms: &mut Vec, + split_dimension_space: Dimension, + children_split_direction: SplitDirection, +) { + if total_pane_size < split_dimension_space.as_usize() { + // add extra space from rounding errors to the last pane + + let increase_by = split_dimension_space + .as_usize() + .saturating_sub(total_pane_size); + let position_of_last_flexible_geom = split_geoms + .iter() + .rposition(|s_g| s_g.is_flexible_in_direction(children_split_direction)); + position_of_last_flexible_geom + .map(|p| split_geoms.iter_mut().skip(p)) + .map(|mut flexible_geom_and_following_geoms| { + if let Some(flexible_geom) = flexible_geom_and_following_geoms.next() { + match children_split_direction { + SplitDirection::Vertical => flexible_geom.cols.increase_inner(increase_by), + SplitDirection::Horizontal => { + flexible_geom.rows.increase_inner(increase_by) + }, + } + } + for following_geom in flexible_geom_and_following_geoms { + match children_split_direction { + SplitDirection::Vertical => { + following_geom.x += increase_by; + }, + SplitDirection::Horizontal => { + following_geom.y += increase_by; + }, + } + } + }); + } else if total_pane_size > split_dimension_space.as_usize() { + // remove extra space from rounding errors to the last pane + let decrease_by = total_pane_size - split_dimension_space.as_usize(); + let position_of_last_flexible_geom = split_geoms + .iter() + .rposition(|s_g| s_g.is_flexible_in_direction(children_split_direction)); + position_of_last_flexible_geom + .map(|p| split_geoms.iter_mut().skip(p)) + .map(|mut flexible_geom_and_following_geoms| { + if let Some(flexible_geom) = flexible_geom_and_following_geoms.next() { + match children_split_direction { + SplitDirection::Vertical => flexible_geom.cols.decrease_inner(decrease_by), + SplitDirection::Horizontal => { + flexible_geom.rows.decrease_inner(decrease_by) + }, + } + } + for following_geom in flexible_geom_and_following_geoms { + match children_split_direction { + SplitDirection::Vertical => { + following_geom.x = following_geom.x.saturating_sub(decrease_by) + }, + SplitDirection::Horizontal => { + following_geom.y = following_geom.y.saturating_sub(decrease_by) + }, + } + } + }); + } +} + impl Default for SplitDirection { fn default() -> Self { SplitDirection::Horizontal diff --git a/zellij-utils/src/input/options.rs b/zellij-utils/src/input/options.rs index 68ef49e0f2..7cf0ca8fed 100644 --- a/zellij-utils/src/input/options.rs +++ b/zellij-utils/src/input/options.rs @@ -124,6 +124,24 @@ pub struct Options { #[clap(long, value_parser)] #[serde(default)] pub auto_layout: Option, + + /// Whether sessions should be serialized to the HD so that they can be later resurrected, + /// default is true + #[clap(long, value_parser)] + #[serde(default)] + pub session_serialization: Option, + + /// Whether pane viewports are serialized along with the session, default is false + #[clap(long, value_parser)] + #[serde(default)] + pub serialize_pane_viewport: Option, + + /// Scrollback lines to serialize along with the pane viewport when serializing sessions, 0 + /// defaults to the scrollback size. If this number is higher than the scrollback size, it will + /// also default to the scrollback size + #[clap(long, value_parser)] + #[serde(default)] + pub scrollback_lines_to_serialize: Option, } #[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, Copy, PartialEq)] @@ -187,6 +205,13 @@ impl Options { let attach_to_session = other .attach_to_session .or_else(|| self.attach_to_session.clone()); + let session_serialization = other.session_serialization.or(self.session_serialization); + let serialize_pane_viewport = other + .serialize_pane_viewport + .or(self.serialize_pane_viewport); + let scrollback_lines_to_serialize = other + .scrollback_lines_to_serialize + .or(self.scrollback_lines_to_serialize); Options { simplified_ui, @@ -209,6 +234,9 @@ impl Options { session_name, attach_to_session, auto_layout, + session_serialization, + serialize_pane_viewport, + scrollback_lines_to_serialize, } } @@ -232,6 +260,10 @@ impl Options { let pane_frames = merge_bool(other.pane_frames, self.pane_frames); let auto_layout = merge_bool(other.auto_layout, self.auto_layout); let mirror_session = merge_bool(other.mirror_session, self.mirror_session); + let session_serialization = + merge_bool(other.session_serialization, self.session_serialization); + let serialize_pane_viewport = + merge_bool(other.serialize_pane_viewport, self.serialize_pane_viewport); let default_mode = other.default_mode.or(self.default_mode); let default_shell = other.default_shell.or_else(|| self.default_shell.clone()); @@ -252,6 +284,9 @@ impl Options { let attach_to_session = other .attach_to_session .or_else(|| self.attach_to_session.clone()); + let scrollback_lines_to_serialize = other + .scrollback_lines_to_serialize + .or_else(|| self.scrollback_lines_to_serialize.clone()); Options { simplified_ui, @@ -274,6 +309,9 @@ impl Options { session_name, attach_to_session, auto_layout, + session_serialization, + serialize_pane_viewport, + scrollback_lines_to_serialize, } } @@ -333,6 +371,9 @@ impl From for Options { 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, ..Default::default() } } diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs index 038161a49a..424a5b22d2 100644 --- a/zellij-utils/src/input/unit/layout_test.rs +++ b/zellij-utils/src/input/unit/layout_test.rs @@ -173,6 +173,36 @@ fn layout_with_mixed_panes_and_floating_panes() { assert_eq!(layout, expected_layout); } +#[test] +fn layout_with_hidden_floating_panes() { + let kdl_layout = r#" + layout { + tab hide_floating_panes=true { + pane + pane + floating_panes { + pane + } + } + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None, None).unwrap(); + let expected_layout = Layout { + tabs: vec![( + None, + TiledPaneLayout { + children: vec![TiledPaneLayout::default(), TiledPaneLayout::default()], + hide_floating_panes: true, + ..Default::default() + }, + vec![FloatingPaneLayout::default()], + )], + template: Some((TiledPaneLayout::default(), vec![])), + ..Default::default() + }; + assert_eq!(layout, expected_layout); +} + #[test] fn layout_with_floating_panes_template() { let kdl_layout = r#" @@ -786,6 +816,31 @@ fn layout_with_default_tab_template() { assert_snapshot!(format!("{:#?}", layout)); } +#[test] +fn layout_with_new_tab_template() { + let kdl_layout = r#" + layout { + new_tab_template { + pane split_direction="vertical" { + pane + pane + } + } + tab name="my first tab" split_direction="Vertical" { + pane + pane + } + tab name="my second tab" { + pane + pane + } + tab + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None, None).unwrap(); + assert_snapshot!(format!("{:#?}", layout)); +} + #[test] fn layout_with_pane_templates() { let kdl_layout = r#" diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap index 9d2d9e37e2..0d8a3635de 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap @@ -35,6 +35,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -62,6 +64,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -73,6 +77,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap index e8c140421a..65db816917 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap @@ -38,6 +38,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -65,6 +67,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -76,6 +80,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_a_stack_with_an_expanded_pane.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_a_stack_with_an_expanded_pane.snap index 83fb940433..6a7e9aa64e 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_a_stack_with_an_expanded_pane.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_a_stack_with_an_expanded_pane.snap @@ -29,6 +29,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -43,6 +45,8 @@ Layout { is_expanded_in_stack: true, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -57,6 +61,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -68,6 +74,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -79,6 +87,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_stacked_children_for_pane_node.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_stacked_children_for_pane_node.snap index b82e9cd1e3..cb37a62e2b 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_stacked_children_for_pane_node.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_stacked_children_for_pane_node.snap @@ -29,6 +29,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -43,6 +45,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -54,6 +58,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -65,6 +71,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_stacked_children_for_pane_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_stacked_children_for_pane_template.snap index 44da7ce78a..a73fa6bf6a 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_stacked_children_for_pane_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_stacked_children_for_pane_template.snap @@ -33,6 +33,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -47,6 +49,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -58,6 +62,8 @@ Layout { is_expanded_in_stack: true, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -69,6 +75,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -80,6 +88,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_load_swap_layouts_from_a_different_file.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_load_swap_layouts_from_a_different_file.snap index 07428ee0c9..0ebdf2ca26 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_load_swap_layouts_from_a_different_file.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_load_swap_layouts_from_a_different_file.snap @@ -25,6 +25,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -36,6 +38,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), @@ -81,6 +85,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -103,6 +109,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -119,6 +127,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -130,6 +140,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -141,6 +153,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -173,6 +187,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -184,6 +200,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, MaxPanes( 8, @@ -222,6 +240,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -246,6 +266,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -264,6 +286,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -278,6 +302,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -292,6 +318,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -306,6 +334,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -317,6 +347,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -328,6 +360,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -339,6 +373,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -371,6 +407,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -382,6 +420,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, MaxPanes( 12, @@ -420,6 +460,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -444,6 +486,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -462,6 +506,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -476,6 +522,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -490,6 +538,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -504,6 +554,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -515,6 +567,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -533,6 +587,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -547,6 +603,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -561,6 +619,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -575,6 +635,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -586,6 +648,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -597,6 +661,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -608,6 +674,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -640,6 +708,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -651,6 +721,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, }, Some( diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap index 4bb0487202..40bb0581cf 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap @@ -35,6 +35,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -53,6 +55,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -67,6 +71,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -78,6 +84,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -89,6 +97,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -103,6 +113,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -114,6 +126,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -136,6 +150,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -149,6 +165,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -163,6 +181,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -174,6 +194,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -185,6 +207,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_tab_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_tab_template.snap index c5f538f968..9dde780ba2 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_tab_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_tab_template.snap @@ -30,6 +30,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -48,6 +50,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -62,6 +66,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -73,6 +79,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -84,6 +92,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -98,6 +108,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -109,6 +121,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), @@ -135,6 +149,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -148,6 +164,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -162,6 +180,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -173,6 +193,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), @@ -193,6 +215,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap index fdd0b7f0c1..f9208fc3dd 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap @@ -35,6 +35,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -59,6 +61,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -70,6 +74,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap index 3a4092cff9..adc88ee89c 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap @@ -35,6 +35,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -59,6 +61,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -70,6 +74,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__combined_tab_and_pane_template_both_with_children.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__combined_tab_and_pane_template_both_with_children.snap index 0f0a62ac0e..85b2dcfeca 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__combined_tab_and_pane_template_both_with_children.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__combined_tab_and_pane_template_both_with_children.snap @@ -30,6 +30,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -50,6 +52,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -61,6 +65,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -75,6 +81,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -86,6 +94,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -104,6 +114,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -118,6 +130,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -129,6 +143,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -140,6 +156,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), @@ -166,6 +184,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -186,6 +206,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -197,6 +219,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -211,6 +235,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -222,6 +248,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -236,6 +264,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -247,6 +277,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), @@ -267,6 +299,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap index 469eb9b977..721b37d5eb 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap @@ -35,6 +35,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -61,6 +63,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -72,6 +76,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap index 42d8bcf0c9..bad9bf62f1 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap @@ -37,6 +37,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -63,6 +65,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -74,6 +78,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__env_var_expansion.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__env_var_expansion.snap index 6691e25e34..25cfceb566 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__env_var_expansion.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__env_var_expansion.snap @@ -29,6 +29,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -47,6 +49,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -65,6 +69,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -83,6 +89,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -109,6 +117,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -131,6 +141,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -153,6 +165,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -179,6 +193,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -201,6 +217,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -212,6 +230,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd.snap index 6060b071b5..8bf48d2775 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd.snap @@ -28,6 +28,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -54,6 +56,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -69,6 +73,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), @@ -89,6 +95,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_pane_templates.snap index 723af76229..a8938ed83c 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_pane_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_pane_templates.snap @@ -32,6 +32,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -58,6 +60,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -80,6 +84,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -95,6 +101,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -110,6 +118,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -125,6 +135,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), @@ -145,6 +157,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_tab_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_tab_templates.snap index de740071ad..3abbaaeea6 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_tab_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_tab_templates.snap @@ -28,6 +28,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -54,6 +56,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -76,6 +80,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -91,6 +97,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -106,6 +114,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), @@ -126,6 +136,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap index 72f9e14e33..adb410722e 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap @@ -29,6 +29,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -55,6 +57,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -66,6 +70,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap index b6a29005b3..3ae5eee2f3 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap @@ -29,6 +29,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -55,6 +57,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -66,6 +70,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap index c673885a68..dcd2d9321f 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap @@ -29,6 +29,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -55,6 +57,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -66,6 +70,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap index 41678b395c..d292e95fe1 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap @@ -29,6 +29,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -55,6 +57,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -66,6 +70,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_with_tab_cwd_given_to_panes_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_with_tab_cwd_given_to_panes_without_cwd.snap index d386d9c94b..2abeb3d16b 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_with_tab_cwd_given_to_panes_without_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_with_tab_cwd_given_to_panes_without_cwd.snap @@ -28,6 +28,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -54,6 +56,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -69,6 +73,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), @@ -89,6 +95,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap index b213ea2115..1c3e5a38fe 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap @@ -35,6 +35,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -46,6 +48,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_start_suspended.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_start_suspended.snap index 35c343730c..1198604036 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_start_suspended.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_start_suspended.snap @@ -35,6 +35,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -46,6 +48,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_default_tab_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_default_tab_template.snap index 92e58f7d97..81abc9987f 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_default_tab_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_default_tab_template.snap @@ -26,6 +26,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Vertical, @@ -44,6 +46,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -58,6 +62,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -69,6 +75,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -83,6 +91,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -94,6 +104,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), @@ -118,6 +130,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -136,6 +150,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -150,6 +166,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -161,6 +179,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -175,6 +195,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -186,6 +208,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), @@ -208,6 +232,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -222,6 +248,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -236,6 +264,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -247,6 +277,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), @@ -271,6 +303,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -285,6 +319,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -299,6 +335,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -310,6 +348,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_branched_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_branched_pane_templates.snap index 707ec26822..42983dee8b 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_branched_pane_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_branched_pane_templates.snap @@ -29,6 +29,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -43,6 +45,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -61,6 +65,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -75,6 +81,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -86,6 +94,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -104,6 +114,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -118,6 +130,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -132,6 +146,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -143,6 +159,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -154,6 +172,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -165,6 +185,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_pane_templates.snap index ce8ee0a96d..06102b00fa 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_pane_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_pane_templates.snap @@ -29,6 +29,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -43,6 +45,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -61,6 +65,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -75,6 +81,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -86,6 +94,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -100,6 +110,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -111,6 +123,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -122,6 +136,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_new_tab_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_new_tab_template.snap new file mode 100644 index 0000000000..701dd1b45a --- /dev/null +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_new_tab_template.snap @@ -0,0 +1,214 @@ +--- +source: zellij-utils/src/input/./unit/layout_test.rs +assertion_line: 846 +expression: "format!(\"{:#?}\", layout)" +--- +Layout { + tabs: [ + ( + Some( + "my first tab", + ), + TiledPaneLayout { + children_split_direction: Vertical, + name: None, + children: [ + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + ], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + [], + ), + ( + Some( + "my second tab", + ), + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + ], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + [], + ), + ( + None, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + [], + ), + ], + focused_tab_index: None, + template: Some( + ( + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [ + TiledPaneLayout { + children_split_direction: Vertical, + name: None, + children: [ + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + TiledPaneLayout { + children_split_direction: Horizontal, + name: None, + children: [], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + ], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + ], + split_size: None, + run: None, + borderless: false, + focus: None, + external_children_index: None, + children_are_stacked: false, + is_expanded_in_stack: false, + exclude_from_sync: None, + run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, + }, + [], + ), + ), + swap_layouts: [], + swap_tiled_layouts: [], + swap_floating_layouts: [], +} diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_excluded_from_sync.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_excluded_from_sync.snap index 554420b1dd..1b56c2e88c 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_excluded_from_sync.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_excluded_from_sync.snap @@ -27,6 +27,8 @@ Layout { true, ), run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -38,6 +40,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_templates.snap index 0eeb76af4d..7a836351bb 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_templates.snap @@ -29,6 +29,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -47,6 +49,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -58,6 +62,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -72,6 +78,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -83,6 +91,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Vertical, @@ -101,6 +111,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -119,6 +131,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -133,6 +147,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -144,6 +160,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -158,6 +176,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -169,6 +189,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Vertical, @@ -187,6 +209,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Vertical, @@ -205,6 +229,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -219,6 +245,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -230,6 +258,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -244,6 +274,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -255,6 +287,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Vertical, @@ -273,6 +307,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -287,6 +323,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -301,6 +339,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -312,6 +352,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -323,6 +365,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap index 1bc1b01c64..30bd1b8597 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap @@ -28,6 +28,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -56,6 +58,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -67,6 +71,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -81,6 +87,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -92,6 +100,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -103,6 +113,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), @@ -123,6 +135,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tabs_and_floating_panes.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tabs_and_floating_panes.snap index c9776160e4..6981d9d7d0 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tabs_and_floating_panes.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tabs_and_floating_panes.snap @@ -24,6 +24,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -35,6 +37,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [ FloatingPaneLayout { @@ -46,6 +50,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, ], ), @@ -68,6 +73,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -79,6 +86,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [ FloatingPaneLayout { @@ -90,6 +99,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, FloatingPaneLayout { name: None, @@ -100,6 +110,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, ], ), @@ -120,6 +131,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap index a8bff78ed8..e8cbac5618 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap @@ -37,6 +37,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -48,6 +50,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap index 433283fcda..e03609cfa0 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap @@ -37,6 +37,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -48,6 +50,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap index 8d06ac560b..c7214bb239 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap @@ -37,6 +37,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -48,6 +50,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap index 5d7f6b9918..e3308e18ee 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap @@ -37,6 +37,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -48,6 +50,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap index f49d65f4f8..748f5f91cf 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap @@ -37,6 +37,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -48,6 +50,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap index cf7c7b7a31..62f8e03840 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap @@ -29,6 +29,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -40,6 +42,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap index c55fb95213..4923ab97f0 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap @@ -37,6 +37,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -48,6 +50,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap index 34280469a2..5067ae7936 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap @@ -37,6 +37,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -48,6 +50,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_edit.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_edit.snap index faac5c46d3..adaa2de126 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_edit.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_edit.snap @@ -33,6 +33,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -44,6 +46,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_command_propagated_to_its_consumer_edit.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_command_propagated_to_its_consumer_edit.snap index 5930f95184..eef3d4ba5a 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_command_propagated_to_its_consumer_edit.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_command_propagated_to_its_consumer_edit.snap @@ -33,6 +33,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -44,6 +46,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_given_to_panes_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_given_to_panes_without_cwd.snap index 98b7a5cd8e..49019f51ef 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_given_to_panes_without_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_given_to_panes_without_cwd.snap @@ -28,6 +28,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -54,6 +56,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -69,6 +73,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), @@ -89,6 +95,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_prepended_to_panes_with_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_prepended_to_panes_with_cwd.snap index 5ca79f19e2..ffc6958e0e 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_prepended_to_panes_with_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_prepended_to_panes_with_cwd.snap @@ -28,6 +28,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -54,6 +56,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -69,6 +73,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), @@ -89,6 +95,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/kdl/kdl_layout_parser.rs b/zellij-utils/src/kdl/kdl_layout_parser.rs index 9cee47988e..16be5fcaaa 100644 --- a/zellij-utils/src/kdl/kdl_layout_parser.rs +++ b/zellij-utils/src/kdl/kdl_layout_parser.rs @@ -39,16 +39,20 @@ pub struct KdlLayoutParser<'a> { tab_templates: HashMap, KdlNode)>, pane_templates: HashMap, default_tab_template: Option<(TiledPaneLayout, Vec, KdlNode)>, + new_tab_template: Option<(TiledPaneLayout, Vec)>, + file_name: PathBuf, } impl<'a> KdlLayoutParser<'a> { - pub fn new(raw_layout: &'a str, global_cwd: Option) -> Self { + pub fn new(raw_layout: &'a str, global_cwd: Option, file_name: String) -> Self { KdlLayoutParser { raw_layout, tab_templates: HashMap::new(), pane_templates: HashMap::new(), default_tab_template: None, + new_tab_template: None, global_cwd, + file_name: PathBuf::from(file_name), } } fn is_a_reserved_word(&self, word: &str) -> bool { @@ -59,6 +63,7 @@ impl<'a> KdlLayoutParser<'a> { || word == "pane_template" || word == "tab_template" || word == "default_tab_template" + || word == "new_tab_template" || word == "command" || word == "edit" || word == "plugin" @@ -75,6 +80,8 @@ impl<'a> KdlLayoutParser<'a> { || word == "split_direction" || word == "swap_tiled_layout" || word == "swap_floating_layout" + || word == "hide_floating_panes" + || word == "contents_file" } fn is_a_valid_pane_property(&self, property_name: &str) -> bool { property_name == "borderless" @@ -94,6 +101,7 @@ impl<'a> KdlLayoutParser<'a> { || property_name == "stacked" || property_name == "expanded" || property_name == "exclude_from_sync" + || property_name == "contents_file" } fn is_a_valid_floating_pane_property(&self, property_name: &str) -> bool { property_name == "borderless" @@ -110,6 +118,7 @@ impl<'a> KdlLayoutParser<'a> { || property_name == "y" || property_name == "width" || property_name == "height" + || property_name == "contents_file" } fn is_a_valid_tab_property(&self, property_name: &str) -> bool { property_name == "focus" @@ -121,6 +130,7 @@ impl<'a> KdlLayoutParser<'a> { || property_name == "max_panes" || property_name == "min_panes" || property_name == "exact_panes" + || property_name == "hide_floating_panes" } pub fn is_a_reserved_plugin_property(property_name: &str) -> bool { property_name == "location" @@ -511,6 +521,8 @@ impl<'a> KdlLayoutParser<'a> { .map(|name| name.to_string()); let exclude_from_sync = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "exclude_from_sync"); + let contents_file = + kdl_get_string_property_or_child_value_with_error!(kdl_node, "contents_file"); let split_size = self.parse_split_size(kdl_node)?; let run = self.parse_command_plugin_or_edit_block(kdl_node)?; let children_split_direction = self.parse_split_direction(kdl_node)?; @@ -541,6 +553,11 @@ impl<'a> KdlLayoutParser<'a> { )); } self.assert_no_mixed_children_and_properties(kdl_node)?; + let pane_initial_contents = contents_file.and_then(|contents_file| { + self.file_name.parent().and_then(|parent_folder| { + std::fs::read_to_string(parent_folder.join(contents_file)).ok() + }) + }); Ok(TiledPaneLayout { borderless: borderless.unwrap_or_default(), focus, @@ -553,6 +570,7 @@ impl<'a> KdlLayoutParser<'a> { children, children_are_stacked, is_expanded_in_stack, + pane_initial_contents, ..Default::default() }) } @@ -569,7 +587,14 @@ impl<'a> KdlLayoutParser<'a> { let focus = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "focus"); let name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name") .map(|name| name.to_string()); + let contents_file = + kdl_get_string_property_or_child_value_with_error!(kdl_node, "contents_file"); self.assert_no_mixed_children_and_properties(kdl_node)?; + let pane_initial_contents = contents_file.and_then(|contents_file| { + self.file_name.parent().and_then(|parent_folder| { + std::fs::read_to_string(parent_folder.join(contents_file)).ok() + }) + }); Ok(FloatingPaneLayout { name, height, @@ -578,6 +603,7 @@ impl<'a> KdlLayoutParser<'a> { y, run, focus, + pane_initial_contents, ..Default::default() }) } @@ -1112,6 +1138,8 @@ impl<'a> KdlLayoutParser<'a> { kdl_get_string_property_or_child_value!(kdl_node, "name").map(|s| s.to_string()); let tab_cwd = self.parse_path(kdl_node, "cwd")?; let is_focused = kdl_get_bool_property_or_child_value!(kdl_node, "focus").unwrap_or(false); + let hide_floating_panes = + kdl_get_bool_property_or_child_value!(kdl_node, "hide_floating_panes").unwrap_or(false); let children_split_direction = self.parse_split_direction(kdl_node)?; let mut child_floating_panes = vec![]; let children = match kdl_children_nodes!(kdl_node) { @@ -1128,6 +1156,7 @@ impl<'a> KdlLayoutParser<'a> { let mut pane_layout = TiledPaneLayout { children_split_direction, children, + hide_floating_panes, ..Default::default() }; if let Some(cwd_prefix) = &self.cwd_prefix(tab_cwd.as_ref())? { @@ -1587,6 +1616,12 @@ impl<'a> KdlLayoutParser<'a> { Some((tab_template, tab_template_floating_panes, kdl_node.clone())); Ok(()) } + fn populate_new_tab_template(&mut self, kdl_node: &KdlNode) -> Result<(), ConfigError> { + let (_is_focused, _tab_name, tab_template, tab_template_floating_panes) = + self.parse_tab_node(kdl_node)?; + self.new_tab_template = Some((tab_template, tab_template_floating_panes)); + Ok(()) + } fn parse_tab_template_node( &self, kdl_node: &KdlNode, @@ -1788,6 +1823,8 @@ impl<'a> KdlLayoutParser<'a> { self.populate_one_tab_template(child)?; } else if child_name == "default_tab_template" { self.populate_default_tab_template(child)?; + } else if child_name == "new_tab_template" { + self.populate_new_tab_template(child)?; } } Ok(()) @@ -2031,13 +2068,18 @@ impl<'a> KdlLayoutParser<'a> { swap_tiled_layouts: Vec, swap_floating_layouts: Vec, ) -> Result { - let template = self - .default_template()? - .unwrap_or_else(|| TiledPaneLayout::default()); + let template = if let Some(new_tab_template) = &self.new_tab_template { + Some(new_tab_template.clone()) + } else { + let default_tab_tiled_panes_template = self + .default_template()? + .unwrap_or_else(|| TiledPaneLayout::default()); + Some((default_tab_tiled_panes_template, vec![])) + }; Ok(Layout { tabs, - template: Some((template, vec![])), + template, focused_tab_index, swap_tiled_layouts, swap_floating_layouts, @@ -2056,18 +2098,21 @@ impl<'a> KdlLayoutParser<'a> { ..Default::default() }; let default_template = self.default_template()?; - let tabs = if default_template.is_none() { + let tabs = if default_template.is_none() && self.new_tab_template.is_none() { // in this case, the layout will be created as the default template and we don't need // to explicitly place it in the first tab vec![] } else { vec![(None, main_tab_layout.clone(), floating_panes.clone())] }; - let template = default_template.unwrap_or_else(|| main_tab_layout.clone()); + let template = default_template + .map(|tiled_panes_template| (tiled_panes_template, floating_panes.clone())) + .or_else(|| self.new_tab_template.clone()) + .unwrap_or_else(|| (main_tab_layout.clone(), floating_panes.clone())); // create a layout with one tab that has these child panes Ok(Layout { tabs, - template: Some((template, floating_panes)), + template: Some(template), swap_tiled_layouts, swap_floating_layouts, ..Default::default() @@ -2079,11 +2124,16 @@ impl<'a> KdlLayoutParser<'a> { swap_tiled_layouts: Vec, swap_floating_layouts: Vec, ) -> Result { - let template = self - .default_template()? - .unwrap_or_else(|| TiledPaneLayout::default()); + let template = if let Some(new_tab_template) = &self.new_tab_template { + Some(new_tab_template.clone()) + } else { + let default_tab_tiled_panes_template = self + .default_template()? + .unwrap_or_else(|| TiledPaneLayout::default()); + Some((default_tab_tiled_panes_template, child_floating_panes)) + }; Ok(Layout { - template: Some((template, child_floating_panes)), + template, swap_tiled_layouts, swap_floating_layouts, ..Default::default() diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs index 35bf89403d..dc99bc66da 100644 --- a/zellij-utils/src/kdl/mod.rs +++ b/zellij-utils/src/kdl/mod.rs @@ -4,6 +4,7 @@ use crate::data::{ Resize, SessionInfo, TabInfo, }; use crate::envs::EnvironmentVariables; +use crate::home::{find_default_config_dir, get_layout_dir}; use crate::input::config::{Config, ConfigError, KdlError}; use crate::input::keybinds::Keybinds; use crate::input::layout::{Layout, PluginUserConfiguration, RunPlugin, RunPluginLocation}; @@ -11,7 +12,6 @@ use crate::input::options::{Clipboard, OnForceClose, Options}; use crate::input::permission::{GrantedPermission, PermissionCache}; use crate::input::plugins::{PluginConfig, PluginTag, PluginType, PluginsConfig}; use crate::input::theme::{FrameConfig, Theme, Themes, UiConfig}; -use crate::setup::{find_default_config_dir, get_layout_dir}; use kdl_layout_parser::KdlLayoutParser; use std::collections::{BTreeMap, HashMap, HashSet}; use strum::IntoEnumIterator; @@ -469,6 +469,7 @@ impl Action { }, "MovePaneBackwards" => Ok(Action::MovePaneBackwards), "DumpScreen" => Ok(Action::DumpScreen(string, false)), + "DumpLayout" => Ok(Action::DumpLayout), "NewPane" => { if string.is_empty() { return Ok(Action::NewPane(None, None)); @@ -756,6 +757,11 @@ impl TryFrom<(&KdlNode, &Options)> for Action { action_arguments, kdl_action ), + "DumpLayout" => parse_kdl_action_char_or_string_arguments!( + action_name, + action_arguments, + kdl_action + ), "NewPane" => parse_kdl_action_char_or_string_arguments!( action_name, action_arguments, @@ -1423,6 +1429,15 @@ impl Options { let attach_to_session = kdl_property_first_arg_as_bool_or_error!(kdl_options, "attach_to_session") .map(|(v, _)| v); + let session_serialization = + kdl_property_first_arg_as_bool_or_error!(kdl_options, "session_serialization") + .map(|(v, _)| v); + let serialize_pane_viewport = + kdl_property_first_arg_as_bool_or_error!(kdl_options, "serialize_pane_viewport") + .map(|(v, _)| v); + let scrollback_lines_to_serialize = + kdl_property_first_arg_as_i64_or_error!(kdl_options, "scrollback_lines_to_serialize") + .map(|(v, _)| v as usize); Ok(Options { simplified_ui, theme, @@ -1444,6 +1459,9 @@ impl Options { session_name, attach_to_session, auto_layout, + session_serialization, + serialize_pane_viewport, + scrollback_lines_to_serialize, }) } } @@ -1455,7 +1473,7 @@ impl Layout { raw_swap_layouts: Option<(&str, &str)>, // raw_swap_layouts swap_layouts_file_name cwd: Option, ) -> Result { - let mut kdl_layout_parser = KdlLayoutParser::new(raw_layout, cwd); + let mut kdl_layout_parser = KdlLayoutParser::new(raw_layout, cwd, file_name.clone()); let layout = kdl_layout_parser.parse().map_err(|e| match e { ConfigError::KdlError(kdl_error) => { ConfigError::KdlError(kdl_error.add_src(file_name, String::from(raw_layout))) diff --git a/zellij-utils/src/lib.rs b/zellij-utils/src/lib.rs index c9b3581d00..b301e1c634 100644 --- a/zellij-utils/src/lib.rs +++ b/zellij-utils/src/lib.rs @@ -3,11 +3,13 @@ pub mod consts; pub mod data; pub mod envs; pub mod errors; +pub mod home; pub mod input; pub mod kdl; pub mod pane_size; pub mod plugin_api; pub mod position; +pub mod session_serialization; pub mod setup; pub mod shared; @@ -21,8 +23,8 @@ pub mod logging; // Requires log4rs #[cfg(not(target_family = "wasm"))] pub use ::{ - anyhow, async_channel, async_std, clap, interprocess, lazy_static, libc, miette, nix, - notify_debouncer_full, regex, serde, signal_hook, tempfile, termwiz, vte, + anyhow, async_channel, async_std, clap, common_path, humantime, interprocess, lazy_static, + libc, miette, nix, notify_debouncer_full, regex, serde, signal_hook, tempfile, termwiz, vte, }; pub use ::prost; diff --git a/zellij-utils/src/pane_size.rs b/zellij-utils/src/pane_size.rs index bef4f652a0..c3c74748a2 100644 --- a/zellij-utils/src/pane_size.rs +++ b/zellij-utils/src/pane_size.rs @@ -1,6 +1,10 @@ use serde::{Deserialize, Serialize}; -use std::hash::{Hash, Hasher}; +use std::{ + fmt::Display, + hash::{Hash, Hasher}, +}; +use crate::input::layout::SplitDirection; use crate::position::Position; /// Contains the position and size of a [`Pane`], or more generally of any terminal, measured @@ -51,7 +55,7 @@ pub struct SizeInPixels { #[derive(Eq, Clone, Copy, PartialEq, Debug, Serialize, Deserialize, Hash)] pub struct Dimension { pub constraint: Constraint, - inner: usize, + pub(crate) inner: usize, } impl Default for Dimension { @@ -106,7 +110,6 @@ impl Dimension { let leftover = rounded - new_inner; self.set_inner(rounded as usize); leftover - // self.set_inner(((percent / 100.0) * full_size as f64).round() as usize); }, Constraint::Fixed(fixed_size) => { self.set_inner(fixed_size); @@ -137,6 +140,17 @@ pub enum Constraint { Percent(f64), } +impl Display for Constraint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let actual = match self { + Constraint::Fixed(v) => *v as f64, + Constraint::Percent(v) => *v, + }; + write!(f, "{}", actual)?; + Ok(()) + } +} + #[allow(clippy::derive_hash_xor_eq)] impl Hash for Constraint { fn hash(&self, state: &mut H) { @@ -161,6 +175,26 @@ impl PaneGeom { pub fn is_at_least_minimum_size(&self) -> bool { self.rows.as_usize() > 0 && self.cols.as_usize() > 0 } + pub fn is_flexible_in_direction(&self, split_direction: SplitDirection) -> bool { + match split_direction { + SplitDirection::Vertical => self.cols.is_percent(), + SplitDirection::Horizontal => self.rows.is_percent(), + } + } +} + +impl Display for PaneGeom { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{{ ")?; + write!(f, r#""x": {},"#, self.x)?; + write!(f, r#""y": {},"#, self.y)?; + write!(f, r#""cols": {},"#, self.cols.constraint)?; + write!(f, r#""rows": {},"#, self.rows.constraint)?; + write!(f, r#""stacked": {}"#, self.is_stacked)?; + write!(f, " }}")?; + + Ok(()) + } } impl Offset { diff --git a/zellij-utils/src/plugin_api/action.rs b/zellij-utils/src/plugin_api/action.rs index bba6e821c1..0addb0dcc6 100644 --- a/zellij-utils/src/plugin_api/action.rs +++ b/zellij-utils/src/plugin_api/action.rs @@ -1170,6 +1170,7 @@ impl TryFrom for ProtobufAction { | Action::NewInPlacePluginPane(..) | Action::Deny | Action::Copy + | Action::DumpLayout | Action::SkipConfirm(..) => Err("Unsupported action"), } } diff --git a/zellij-utils/src/session_serialization.rs b/zellij-utils/src/session_serialization.rs new file mode 100644 index 0000000000..1a3671b628 --- /dev/null +++ b/zellij-utils/src/session_serialization.rs @@ -0,0 +1,1109 @@ +//! # Persistence module +//! !WIP! This module is holding the logic for all persistence sessions need +//! +//! # Examples +//! ```rust,no_run +//! fn main() { +//! } +//! ``` +//! +use std::collections::BTreeMap; +use std::path::PathBuf; + +use crate::{ + input::layout::PluginUserConfiguration, + input::layout::{ + FloatingPaneLayout, Layout, PercentOrFixed, Run, SplitDirection, SplitSize, + SwapFloatingLayout, SwapTiledLayout, TiledPaneLayout, + }, + pane_size::{Constraint, PaneGeom}, +}; + +const INDENT: &str = " "; +const DOUBLE_INDENT: &str = " "; +const TRIPLE_INDENT: &str = " "; + +fn indent(s: &str, prefix: &str) -> String { + let mut result = String::new(); + for line in s.lines() { + if line.chars().any(|c| !c.is_whitespace()) { + result.push_str(prefix); + result.push_str(line); + } + result.push('\n'); + } + result +} + +#[derive(Default, Debug, Clone)] +pub struct GlobalLayoutManifest { + pub global_cwd: Option, + pub default_shell: Option, + pub default_layout: Box, + pub tabs: Vec<(String, TabLayoutManifest)>, +} + +#[derive(Default, Debug, Clone)] +pub struct TabLayoutManifest { + pub tiled_panes: Vec, + pub floating_panes: Vec, + pub is_focused: bool, + pub hide_floating_panes: bool, +} + +#[derive(Default, Debug, Clone)] +pub struct PaneLayoutManifest { + pub geom: PaneGeom, + pub run: Option, + pub cwd: Option, + pub is_borderless: bool, + pub title: Option, + pub is_focused: bool, + pub pane_contents: Option, +} + +pub fn serialize_session_layout( + global_layout_manifest: GlobalLayoutManifest, +) -> (String, BTreeMap) { + // BTreeMap is the pane contents and their file names + let mut kdl_string = String::from("layout {\n"); + let mut pane_contents = BTreeMap::new(); + stringify_global_cwd(&global_layout_manifest.global_cwd, &mut kdl_string); + stringify_multiple_tabs( + global_layout_manifest.tabs, + &mut pane_contents, + &mut kdl_string, + ); + stringify_new_tab_template( + global_layout_manifest.default_layout.template, + &mut pane_contents, + &mut kdl_string, + ); + stringify_swap_tiled_layouts( + global_layout_manifest.default_layout.swap_tiled_layouts, + &mut pane_contents, + &mut kdl_string, + ); + stringify_swap_floating_layouts( + global_layout_manifest.default_layout.swap_floating_layouts, + &mut pane_contents, + &mut kdl_string, + ); + kdl_string.push_str("}"); + (kdl_string, pane_contents) +} + +fn stringify_tab( + tab_name: String, + is_focused: bool, + hide_floating_panes: bool, + tiled_panes: &Vec, + floating_panes: &Vec, + pane_contents: &mut BTreeMap, +) -> String { + let mut kdl_string = String::new(); + let tiled_panes_layout = get_tiled_panes_layout_from_panegeoms(tiled_panes, None); + let floating_panes_layout = get_floating_panes_layout_from_panegeoms(floating_panes); + let tiled_panes = if &tiled_panes_layout.children_split_direction != &SplitDirection::default() + { + vec![tiled_panes_layout] + } else { + tiled_panes_layout.children + }; + let mut tab_attributes = vec![format!("name=\"{}\"", tab_name,)]; + if is_focused { + tab_attributes.push(format!("focus=true")); + } + if hide_floating_panes { + tab_attributes.push(format!("hide_floating_panes=true")); + } + kdl_string.push_str(&kdl_string_from_tab( + &tiled_panes, + &floating_panes_layout, + tab_attributes, + None, + pane_contents, + )); + kdl_string +} + +/// Redundant with `geoms_to_kdl_tab` +fn kdl_string_from_tab( + tiled_panes: &Vec, + floating_panes: &Vec, + node_attributes: Vec, + node_name: Option, + pane_contents: &mut BTreeMap, +) -> String { + let mut kdl_string = if node_attributes.is_empty() { + format!("{} {{\n", node_name.unwrap_or_else(|| "tab".to_owned())) + } else { + format!( + "{} {} {{\n", + node_name.unwrap_or_else(|| "tab".to_owned()), + node_attributes.join(" ") + ) + }; + for tiled_pane_layout in tiled_panes { + let ignore_size = false; + let sub_kdl_string = + kdl_string_from_tiled_pane(&tiled_pane_layout, ignore_size, pane_contents); + kdl_string.push_str(&indent(&sub_kdl_string, INDENT)); + } + if !floating_panes.is_empty() { + kdl_string.push_str(&indent("floating_panes {\n", INDENT)); + for floating_pane_layout in floating_panes { + let sub_kdl_string = + kdl_string_from_floating_pane(&floating_pane_layout, pane_contents); + kdl_string.push_str(&indent(&sub_kdl_string, DOUBLE_INDENT)); + } + kdl_string.push_str(&indent("}\n", INDENT)); + } + kdl_string.push_str("}\n"); + kdl_string +} + +/// Pane declaration and recursion +fn kdl_string_from_tiled_pane( + layout: &TiledPaneLayout, + ignore_size: bool, + pane_contents: &mut BTreeMap, +) -> String { + let (command, args) = extract_command_and_args(&layout.run); + let (plugin, plugin_config) = extract_plugin_and_config(&layout.run); + let (edit, _line_number) = extract_edit_and_line_number(&layout.run); + let cwd = layout.run.as_ref().and_then(|r| r.get_cwd()); + let mut kdl_string = stringify_pane_title_and_attributes( + &command, + &edit, + &layout.name, + cwd, + layout.focus, + &layout.pane_initial_contents, + pane_contents, + ); + + stringify_tiled_layout_attributes(&layout, ignore_size, &mut kdl_string); + let has_child_attributes = !layout.children.is_empty() + || layout.external_children_index.is_some() + || !args.is_empty() + || plugin.is_some() + || command.is_some(); + if has_child_attributes { + kdl_string.push_str(" {\n"); + stringify_args(args, &mut kdl_string); + stringify_start_suspended(&command, &mut kdl_string); + stringify_plugin(plugin, plugin_config, &mut kdl_string); + if layout.children.is_empty() && layout.external_children_index.is_some() { + kdl_string.push_str(&indent(&"children\n", INDENT)); + } + for (i, pane) in layout.children.iter().enumerate() { + if Some(i) == layout.external_children_index { + kdl_string.push_str(&indent(&"children\n", INDENT)); + } else { + let ignore_size = layout.children_are_stacked; + let sub_kdl_string = kdl_string_from_tiled_pane(&pane, ignore_size, pane_contents); + kdl_string.push_str(&indent(&sub_kdl_string, INDENT)); + } + } + kdl_string.push_str("}\n"); + } else { + kdl_string.push_str("\n"); + } + kdl_string +} + +fn extract_command_and_args(layout_run: &Option) -> (Option, Vec) { + match layout_run { + Some(Run::Command(run_command)) => ( + Some(run_command.command.display().to_string()), + run_command.args.clone(), + ), + _ => (None, vec![]), + } +} +fn extract_plugin_and_config( + layout_run: &Option, +) -> (Option, Option) { + match &layout_run { + Some(Run::Plugin(run_plugin)) => ( + Some(run_plugin.location.display()), + Some(run_plugin.configuration.clone()), + ), + _ => (None, None), + } +} +fn extract_edit_and_line_number(layout_run: &Option) -> (Option, Option) { + match &layout_run { + // TODO: line number in layouts? + Some(Run::EditFile(path, line_number, _cwd)) => { + (Some(path.display().to_string()), line_number.clone()) + }, + _ => (None, None), + } +} + +fn stringify_pane_title_and_attributes( + command: &Option, + edit: &Option, + name: &Option, + cwd: Option, + focus: Option, + initial_pane_contents: &Option, + pane_contents: &mut BTreeMap, +) -> String { + let mut kdl_string = match (&command, &edit) { + (Some(command), _) => format!("pane command=\"{}\"", command), + (None, Some(edit)) => format!("pane edit=\"{}\"", edit), + (None, None) => format!("pane"), + }; + if let Some(name) = name { + kdl_string.push_str(&format!(" name=\"{}\"", name)); + } + if let Some(cwd) = cwd { + let path = cwd.display().to_string(); + if !path.is_empty() { + kdl_string.push_str(&format!(" cwd=\"{}\"", path)); + } + } + if focus.unwrap_or(false) { + kdl_string.push_str(&" focus=true"); + } + if let Some(initial_pane_contents) = initial_pane_contents.as_ref() { + if command.is_none() && edit.is_none() { + let file_name = format!("initial_contents_{}", pane_contents.keys().len() + 1); + kdl_string.push_str(&format!(" contents_file=\"{}\"", file_name)); + pane_contents.insert(file_name.to_string(), initial_pane_contents.clone()); + } + } + kdl_string +} + +fn stringify_args(args: Vec, kdl_string: &mut String) { + if !args.is_empty() { + let args = args + .iter() + .map(|a| format!("\"{}\"", a)) + .collect::>() + .join(" "); + kdl_string.push_str(&indent(&format!("args {}\n", args), INDENT)); + } +} + +fn stringify_plugin( + plugin: Option, + plugin_config: Option, + kdl_string: &mut String, +) { + if let Some(plugin) = plugin { + if let Some(plugin_config) = + plugin_config.and_then(|p| if p.inner().is_empty() { None } else { Some(p) }) + { + kdl_string.push_str(&indent( + &format!("plugin location=\"{}\" {{\n", plugin), + INDENT, + )); + for (config_key, config_value) in plugin_config.inner() { + kdl_string.push_str(&indent( + &format!("{} \"{}\"\n", config_key, config_value), + INDENT, + )); + } + kdl_string.push_str(&indent("}\n", INDENT)); + } else { + kdl_string.push_str(&indent( + &format!("plugin location=\"{}\"\n", plugin), + INDENT, + )); + } + } +} + +fn stringify_tiled_layout_attributes( + layout: &TiledPaneLayout, + ignore_size: bool, + kdl_string: &mut String, +) { + if !ignore_size { + match layout.split_size { + Some(SplitSize::Fixed(size)) => kdl_string.push_str(&format!(" size={size}")), + Some(SplitSize::Percent(size)) => kdl_string.push_str(&format!(" size=\"{size}%\"")), + None => (), + }; + } + if layout.borderless { + kdl_string.push_str(&" borderless=true"); + } + if layout.children_are_stacked { + kdl_string.push_str(&" stacked=true"); + } + if layout.is_expanded_in_stack { + kdl_string.push_str(&" expanded=true"); + } + if layout.children_split_direction != SplitDirection::default() { + let direction = match layout.children_split_direction { + SplitDirection::Horizontal => "horizontal", + SplitDirection::Vertical => "vertical", + }; + kdl_string.push_str(&format!(" split_direction=\"{direction}\"")); + } +} + +fn stringify_floating_layout_attributes(layout: &FloatingPaneLayout, kdl_string: &mut String) { + match layout.height { + Some(PercentOrFixed::Fixed(fixed_height)) => { + kdl_string.push_str(&indent(&format!("height {}\n", fixed_height), INDENT)); + }, + Some(PercentOrFixed::Percent(percent)) => { + kdl_string.push_str(&indent(&format!("height \"{}%\"\n", percent), INDENT)); + }, + None => {}, + } + match layout.width { + Some(PercentOrFixed::Fixed(fixed_width)) => { + kdl_string.push_str(&indent(&format!("width {}\n", fixed_width), INDENT)); + }, + Some(PercentOrFixed::Percent(percent)) => { + kdl_string.push_str(&indent(&format!("width \"{}%\"\n", percent), INDENT)); + }, + None => {}, + } + match layout.x { + Some(PercentOrFixed::Fixed(fixed_x)) => { + kdl_string.push_str(&indent(&format!("x {}\n", fixed_x), INDENT)); + }, + Some(PercentOrFixed::Percent(percent)) => { + kdl_string.push_str(&indent(&format!("x \"{}%\"\n", percent), INDENT)); + }, + None => {}, + } + match layout.y { + Some(PercentOrFixed::Fixed(fixed_y)) => { + kdl_string.push_str(&indent(&format!("y {}\n", fixed_y), INDENT)); + }, + Some(PercentOrFixed::Percent(percent)) => { + kdl_string.push_str(&indent(&format!("y \"{}%\"\n", percent), INDENT)); + }, + None => {}, + } +} + +fn stringify_start_suspended(command: &Option, kdl_string: &mut String) { + if command.is_some() { + kdl_string.push_str(&indent(&"start_suspended true\n", INDENT)); + } +} + +fn stringify_global_cwd(global_cwd: &Option, kdl_string: &mut String) { + if let Some(global_cwd) = global_cwd { + kdl_string.push_str(&indent( + &format!("cwd \"{}\"\n", global_cwd.display()), + INDENT, + )); + } +} + +fn stringify_new_tab_template( + new_tab_template: Option<(TiledPaneLayout, Vec)>, + pane_contents: &mut BTreeMap, + kdl_string: &mut String, +) { + if let Some((tiled_panes, floating_panes)) = new_tab_template { + let tiled_panes = if &tiled_panes.children_split_direction != &SplitDirection::default() { + vec![tiled_panes] + } else { + tiled_panes.children + }; + kdl_string.push_str(&indent( + &kdl_string_from_tab( + &tiled_panes, + &floating_panes, + vec![], + Some(String::from("new_tab_template")), + pane_contents, + ), + INDENT, + )); + } +} + +fn stringify_swap_tiled_layouts( + swap_tiled_layouts: Vec, + pane_contents: &mut BTreeMap, + kdl_string: &mut String, +) { + for swap_tiled_layout in swap_tiled_layouts { + let swap_tiled_layout_name = swap_tiled_layout.1; + match &swap_tiled_layout_name { + Some(name) => kdl_string.push_str(&indent( + &format!("swap_tiled_layout name=\"{}\" {{\n", name), + INDENT, + )), + None => kdl_string.push_str(&indent("swap_tiled_layout {\n", INDENT)), + }; + for (layout_constraint, tiled_panes_layout) in swap_tiled_layout.0 { + let tiled_panes_layout = + if &tiled_panes_layout.children_split_direction != &SplitDirection::default() { + vec![tiled_panes_layout] + } else { + tiled_panes_layout.children + }; + kdl_string.push_str(&indent( + &kdl_string_from_tab( + &tiled_panes_layout, + &vec![], + vec![layout_constraint.to_string()], + None, + pane_contents, + ), + DOUBLE_INDENT, + )); + } + kdl_string.push_str(&indent("}", INDENT)); + } +} + +fn stringify_swap_floating_layouts( + swap_floating_layouts: Vec, + pane_contents: &mut BTreeMap, + kdl_string: &mut String, +) { + for swap_floating_layout in swap_floating_layouts { + let swap_floating_layout_name = swap_floating_layout.1; + match &swap_floating_layout_name { + Some(name) => kdl_string.push_str(&indent( + &format!("swap_floating_layout name=\"{}\" {{\n", name), + INDENT, + )), + None => kdl_string.push_str(&indent("swap_floating_layout {\n", INDENT)), + }; + for (layout_constraint, floating_panes_layout) in swap_floating_layout.0 { + let has_floating_panes = !floating_panes_layout.is_empty(); + if has_floating_panes { + kdl_string.push_str(&indent( + &format!("floating_panes {} {{\n", layout_constraint), + DOUBLE_INDENT, + )); + } else { + kdl_string.push_str(&indent( + &format!("floating_panes {}\n", layout_constraint), + DOUBLE_INDENT, + )); + } + for floating_pane_layout in floating_panes_layout { + let sub_kdl_string = + kdl_string_from_floating_pane(&floating_pane_layout, pane_contents); + kdl_string.push_str(&indent(&sub_kdl_string, TRIPLE_INDENT)); + } + if has_floating_panes { + kdl_string.push_str(&indent("}\n", DOUBLE_INDENT)); + } + } + kdl_string.push_str(&indent("}", INDENT)); + } +} + +fn stringify_multiple_tabs( + tabs: Vec<(String, TabLayoutManifest)>, + pane_contents: &mut BTreeMap, + kdl_string: &mut String, +) { + for (tab_name, tab_layout_manifest) in tabs { + let tiled_panes = tab_layout_manifest.tiled_panes; + let floating_panes = tab_layout_manifest.floating_panes; + let hide_floating_panes = tab_layout_manifest.hide_floating_panes; + kdl_string.push_str(&indent( + &stringify_tab( + tab_name.clone(), + tab_layout_manifest.is_focused, + hide_floating_panes, + &tiled_panes, + &floating_panes, + pane_contents, + ), + INDENT, + )); + } +} + +fn kdl_string_from_floating_pane( + layout: &FloatingPaneLayout, + pane_contents: &mut BTreeMap, +) -> String { + let (command, args) = extract_command_and_args(&layout.run); + let (plugin, plugin_config) = extract_plugin_and_config(&layout.run); + let (edit, _line_number) = extract_edit_and_line_number(&layout.run); + let cwd = layout.run.as_ref().and_then(|r| r.get_cwd()); + let mut kdl_string = stringify_pane_title_and_attributes( + &command, + &edit, + &layout.name, + cwd, + layout.focus, + &layout.pane_initial_contents, + pane_contents, + ); + kdl_string.push_str(" {\n"); + stringify_start_suspended(&command, &mut kdl_string); + stringify_floating_layout_attributes(&layout, &mut kdl_string); + stringify_args(args, &mut kdl_string); + stringify_plugin(plugin, plugin_config, &mut kdl_string); + kdl_string.push_str("}\n"); + kdl_string +} + +fn tiled_pane_layout_from_manifest( + manifest: Option<&PaneLayoutManifest>, + split_size: Option, +) -> TiledPaneLayout { + let (run, borderless, is_expanded_in_stack, name, focus, pane_initial_contents) = manifest + .map(|g| { + let mut run = g.run.clone(); + if let Some(cwd) = &g.cwd { + if let Some(run) = run.as_mut() { + run.add_cwd(cwd); + } else { + run = Some(Run::Cwd(cwd.clone())); + } + } + ( + run, + g.is_borderless, + g.geom.is_stacked && g.geom.rows.inner > 1, + g.title.clone(), + Some(g.is_focused), + g.pane_contents.clone(), + ) + }) + .unwrap_or((None, false, false, None, None, None)); + TiledPaneLayout { + split_size, + run, + borderless, + is_expanded_in_stack, + name, + focus, + pane_initial_contents, + ..Default::default() + } +} + +/// Tab-level parsing +fn get_tiled_panes_layout_from_panegeoms( + geoms: &Vec, + split_size: Option, +) -> TiledPaneLayout { + let (children_split_direction, splits) = match get_splits(&geoms) { + Some(x) => x, + None => return tiled_pane_layout_from_manifest(geoms.iter().next(), split_size), + }; + let mut children = Vec::new(); + let mut remaining_geoms = geoms.clone(); + let mut new_geoms = Vec::new(); + let mut new_constraints = Vec::new(); + for i in 1..splits.len() { + let (v_min, v_max) = (splits[i - 1], splits[i]); + let subgeoms: Vec; + (subgeoms, remaining_geoms) = match children_split_direction { + SplitDirection::Horizontal => remaining_geoms + .clone() + .into_iter() + .partition(|g| g.geom.y + g.geom.rows.as_usize() <= v_max), + SplitDirection::Vertical => remaining_geoms + .clone() + .into_iter() + .partition(|g| g.geom.x + g.geom.cols.as_usize() <= v_max), + }; + let constraint = + get_domain_constraint(&subgeoms, &children_split_direction, (v_min, v_max)); + new_geoms.push(subgeoms); + new_constraints.push(constraint); + } + let new_split_sizes = get_split_sizes(&new_constraints); + for (subgeoms, subsplit_size) in new_geoms.iter().zip(new_split_sizes) { + children.push(get_tiled_panes_layout_from_panegeoms( + &subgeoms, + subsplit_size, + )); + } + let children_are_stacked = children_split_direction == SplitDirection::Horizontal + && new_geoms + .iter() + .all(|c| c.iter().all(|c| c.geom.is_stacked)); + TiledPaneLayout { + children_split_direction, + split_size, + children, + children_are_stacked, + ..Default::default() + } +} + +fn get_floating_panes_layout_from_panegeoms( + manifests: &Vec, +) -> Vec { + manifests + .iter() + .map(|m| { + let mut run = m.run.clone(); + if let Some(cwd) = &m.cwd { + run.as_mut().map(|r| r.add_cwd(cwd)); + } + FloatingPaneLayout { + name: m.title.clone(), + height: Some(m.geom.rows.into()), + width: Some(m.geom.cols.into()), + x: Some(PercentOrFixed::Fixed(m.geom.x)), + y: Some(PercentOrFixed::Fixed(m.geom.y)), + run, + focus: Some(m.is_focused), + already_running: false, + pane_initial_contents: m.pane_contents.clone(), + } + }) + .collect() +} + +fn get_x_lims(geoms: &Vec) -> Option<(usize, usize)> { + match ( + geoms.iter().map(|g| g.geom.x).min(), + geoms + .iter() + .map(|g| g.geom.x + g.geom.cols.as_usize()) + .max(), + ) { + (Some(x_min), Some(x_max)) => Some((x_min, x_max)), + _ => None, + } +} + +fn get_y_lims(geoms: &Vec) -> Option<(usize, usize)> { + match ( + geoms.iter().map(|g| g.geom.y).min(), + geoms + .iter() + .map(|g| g.geom.y + g.geom.rows.as_usize()) + .max(), + ) { + (Some(y_min), Some(y_max)) => Some((y_min, y_max)), + _ => None, + } +} + +/// Returns the `SplitDirection` as well as the values, on the axis +/// perpendicular the `SplitDirection`, for which there is a split spanning +/// the max_cols or max_rows of the domain. The values are ordered +/// increasingly and contains the boundaries of the domain. +fn get_splits(geoms: &Vec) -> Option<(SplitDirection, Vec)> { + if geoms.len() == 1 { + return None; + } + let (x_lims, y_lims) = match (get_x_lims(&geoms), get_y_lims(&geoms)) { + (Some(x_lims), Some(y_lims)) => (x_lims, y_lims), + _ => return None, + }; + let mut direction = SplitDirection::default(); + let mut splits = match direction { + SplitDirection::Vertical => get_col_splits(&geoms, &x_lims, &y_lims), + SplitDirection::Horizontal => get_row_splits(&geoms, &x_lims, &y_lims), + }; + if splits.len() <= 2 { + // ie only the boundaries are present and no real split has been found + direction = !direction; + splits = match direction { + SplitDirection::Vertical => get_col_splits(&geoms, &x_lims, &y_lims), + SplitDirection::Horizontal => get_row_splits(&geoms, &x_lims, &y_lims), + }; + } + if splits.len() <= 2 { + // ie no real split has been found in both directions + None + } else { + Some((direction, splits)) + } +} + +/// Returns a vector containing the abscisse (x) of the cols that split the +/// domain including the boundaries, ie the min and max abscisse values. +fn get_col_splits( + // geoms: &Vec<(PaneGeom, Option>)>, + geoms: &Vec, + (_, x_max): &(usize, usize), + (y_min, y_max): &(usize, usize), +) -> Vec { + let max_rows = y_max - y_min; + let mut splits = Vec::new(); + let mut sorted_geoms = geoms.clone(); + sorted_geoms.sort_by_key(|g| g.geom.x); + for x in sorted_geoms.iter().map(|g| g.geom.x) { + if splits.contains(&x) { + continue; + } + if sorted_geoms + .iter() + .filter(|g| g.geom.x == x) + .map(|g| g.geom.rows.as_usize()) + .sum::() + == max_rows + { + splits.push(x); + }; + } + splits.push(*x_max); // Necessary as `g.x` is from the upper-left corner + splits +} + +/// Returns a vector containing the coordinate (y) of the rows that split the +/// domain including the boundaries, ie the min and max coordinate values. +fn get_row_splits( + geoms: &Vec, + (x_min, x_max): &(usize, usize), + (_, y_max): &(usize, usize), +) -> Vec { + let max_cols = x_max - x_min; + let mut splits = Vec::new(); + let mut sorted_geoms = geoms.clone(); + sorted_geoms.sort_by_key(|g| g.geom.y); + for y in sorted_geoms.iter().map(|g| g.geom.y) { + if splits.contains(&y) { + continue; + } + if sorted_geoms + .iter() + .filter(|g| g.geom.y == y) + .map(|g| g.geom.cols.as_usize()) + .sum::() + == max_cols + { + splits.push(y); + }; + } + splits.push(*y_max); // Necessary as `g.y` is from the upper-left corner + splits +} + +/// Get the constraint of the domain considered, base on the rows or columns, +/// depending on the split direction provided. +fn get_domain_constraint( + // geoms: &Vec<(PaneGeom, Option>)>, + geoms: &Vec, + split_direction: &SplitDirection, + (v_min, v_max): (usize, usize), +) -> Constraint { + match split_direction { + SplitDirection::Horizontal => get_domain_row_constraint(&geoms, (v_min, v_max)), + SplitDirection::Vertical => get_domain_col_constraint(&geoms, (v_min, v_max)), + } +} + +// fn get_domain_col_constraint(geoms: &Vec<(PaneGeom, Option>)>, (x_min, x_max): (usize, usize)) -> Constraint { +fn get_domain_col_constraint( + geoms: &Vec, + (x_min, x_max): (usize, usize), +) -> Constraint { + let mut percent = 0.0; + let mut x = x_min; + while x != x_max { + // we only look at one (ie the last) geom that has value `x` for `g.x` + let geom = geoms.iter().filter(|g| g.geom.x == x).last().unwrap(); + if let Some(size) = geom.geom.cols.as_percent() { + percent += size; + } + x += geom.geom.cols.as_usize(); + } + if percent == 0.0 { + Constraint::Fixed(x_max - x_min) + } else { + Constraint::Percent(percent) + } +} + +// fn get_domain_row_constraint(geoms: &Vec<(PaneGeom, Option>)>, (y_min, y_max): (usize, usize)) -> Constraint { +fn get_domain_row_constraint( + geoms: &Vec, + (y_min, y_max): (usize, usize), +) -> Constraint { + let mut percent = 0.0; + let mut y = y_min; + while y != y_max { + // we only look at one (ie the last) geom that has value `y` for `g.y` + let geom = geoms.iter().filter(|g| g.geom.y == y).last().unwrap(); + if let Some(size) = geom.geom.rows.as_percent() { + percent += size; + } + y += geom.geom.rows.as_usize(); + } + if percent == 0.0 { + Constraint::Fixed(y_max - y_min) + } else { + Constraint::Percent(percent) + } +} + +/// Returns split sizes for all the children of a `TiledPaneLayout` based on +/// their constraints. +fn get_split_sizes(constraints: &Vec) -> Vec> { + let mut split_sizes = Vec::new(); + let max_percent = constraints + .iter() + .filter_map(|c| match c { + Constraint::Percent(size) => Some(size), + _ => None, + }) + .sum::(); + for constraint in constraints { + let split_size = match constraint { + Constraint::Fixed(size) => Some(SplitSize::Fixed(*size)), + Constraint::Percent(size) => { + if size == &max_percent { + None + } else { + Some(SplitSize::Percent((100.0 * size / max_percent) as usize)) + } + }, + }; + split_sizes.push(split_size); + } + split_sizes +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::pane_size::Dimension; + use expect_test::expect; + use serde_json::Value; + use std::collections::HashMap; + const PANEGEOMS_JSON: &[&[&str]] = &[ + &[ + r#"{ "x": 0, "y": 1, "rows": { "constraint": "Percent(100.0)", "inner": 43 }, "cols": { "constraint": "Percent(100.0)", "inner": 211 }, "is_stacked": false }"#, + r#"{ "x": 0, "y": 0, "rows": { "constraint": "Fixed(1)", "inner": 1 }, "cols": { "constraint": "Percent(100.0)", "inner": 211 }, "is_stacked": false }"#, + r#"{ "x": 0, "y": 44, "rows": { "constraint": "Fixed(2)", "inner": 2 }, "cols": { "constraint": "Percent(100.0)", "inner": 211 }, "is_stacked": false }"#, + ], + &[ + r#"{ "x": 0, "y": 0, "rows": { "constraint": "Percent(100.0)", "inner": 26 }, "cols": { "constraint": "Percent(100.0)", "inner": 211 }, "is_stacked": false }"#, + r#"{ "x": 0, "y": 26, "rows": { "constraint": "Fixed(20)", "inner": 20 }, "cols": { "constraint": "Fixed(50)", "inner": 50 }, "is_stacked": false }"#, + r#"{ "x": 50, "y": 26, "rows": { "constraint": "Fixed(20)", "inner": 20 }, "cols": { "constraint": "Percent(100.0)", "inner": 161 }, "is_stacked": false }"#, + ], + &[ + r#"{ "x": 0, "y": 0, "rows": { "constraint": "Fixed(10)", "inner": 10 }, "cols": { "constraint": "Percent(50.0)", "inner": 106 }, "is_stacked": false }"#, + r#"{ "x": 106, "y": 0, "rows": { "constraint": "Fixed(10)", "inner": 10 }, "cols": { "constraint": "Percent(50.0)", "inner": 105 }, "is_stacked": false }"#, + r#"{ "x": 0, "y": 10, "rows": { "constraint": "Percent(100.0)", "inner": 26 }, "cols": { "constraint": "Fixed(40)", "inner": 40 }, "is_stacked": false }"#, + r#"{ "x": 40, "y": 10, "rows": { "constraint": "Percent(100.0)", "inner": 26 }, "cols": { "constraint": "Percent(100.0)", "inner": 131 }, "is_stacked": false }"#, + r#"{ "x": 171, "y": 10, "rows": { "constraint": "Percent(100.0)", "inner": 26 }, "cols": { "constraint": "Fixed(40)", "inner": 40 }, "is_stacked": false }"#, + r#"{ "x": 0, "y": 36, "rows": { "constraint": "Fixed(10)", "inner": 10 }, "cols": { "constraint": "Percent(50.0)", "inner": 106 }, "is_stacked": false }"#, + r#"{ "x": 106, "y": 36, "rows": { "constraint": "Fixed(10)", "inner": 10 }, "cols": { "constraint": "Percent(50.0)", "inner": 105 }, "is_stacked": false }"#, + ], + &[ + r#"{ "x": 0, "y": 0, "rows": { "constraint": "Percent(30.0)", "inner": 11 }, "cols": { "constraint": "Percent(35.0)", "inner": 74 }, "is_stacked": false }"#, + r#"{ "x": 0, "y": 11, "rows": { "constraint": "Percent(30.0)", "inner": 11 }, "cols": { "constraint": "Percent(35.0)", "inner": 74 }, "is_stacked": false }"#, + r#"{ "x": 0, "y": 22, "rows": { "constraint": "Percent(40.0)", "inner": 14 }, "cols": { "constraint": "Percent(35.0)", "inner": 74 }, "is_stacked": false }"#, + r#"{ "x": 74, "y": 0, "rows": { "constraint": "Percent(100.0)", "inner": 36 }, "cols": { "constraint": "Percent(35.0)", "inner": 74 }, "is_stacked": false }"#, + r#"{ "x": 0, "y": 36, "rows": { "constraint": "Fixed(10)", "inner": 10 }, "cols": { "constraint": "Percent(70.0)", "inner": 148 }, "is_stacked": false }"#, + r#"{ "x": 148, "y": 0, "rows": { "constraint": "Percent(100.0)", "inner": 46 }, "cols": { "constraint": "Percent(30.0)", "inner": 63 }, "is_stacked": false }"#, + ], + &[ + r#"{ "x": 0, "y": 0, "rows": { "constraint": "Fixed(5)", "inner": 5 }, "cols": { "constraint": "Percent(100.0)", "inner": 211 }, "is_stacked": false }"#, + r#"{ "x": 0, "y": 5, "rows": { "constraint": "Percent(100.0)", "inner": 36 }, "cols": { "constraint": "Fixed(20)", "inner": 20 }, "is_stacked": false }"#, + r#"{ "x": 20, "y": 5, "rows": { "constraint": "Percent(100.0)", "inner": 36 }, "cols": { "constraint": "Percent(50.0)", "inner": 86 }, "is_stacked": false }"#, + r#"{ "x": 106, "y": 5, "rows": { "constraint": "Percent(100.0)", "inner": 36 }, "cols": { "constraint": "Percent(50.0)", "inner": 85 }, "is_stacked": false }"#, + r#"{ "x": 191, "y": 5, "rows": { "constraint": "Percent(100.0)", "inner": 36 }, "cols": { "constraint": "Fixed(20)", "inner": 20 }, "is_stacked": false }"#, + r#"{ "x": 0, "y": 41, "rows": { "constraint": "Fixed(5)", "inner": 5 }, "cols": { "constraint": "Percent(100.0)", "inner": 211 }, "is_stacked": false }"#, + ], + ]; + + #[test] + fn geoms() { + let geoms = PANEGEOMS_JSON[0] + .iter() + .map(|pg| parse_panegeom_from_json(pg)) + .map(|geom| PaneLayoutManifest { + geom, + ..Default::default() + }) + .collect(); + let tab_layout_manifest = TabLayoutManifest { + tiled_panes: geoms, + ..Default::default() + }; + let global_layout_manifest = GlobalLayoutManifest { + tabs: vec![("Tab #1".to_owned(), tab_layout_manifest)], + ..Default::default() + }; + // let kdl = kdl_string_from_panegeoms(&geoms); + let kdl = serialize_session_layout(global_layout_manifest); + expect![[r#"layout { + tab name="Tab #1" { + pane size=1 + pane + pane size=2 + } +}"#]] + .assert_eq(&kdl.0); + + let geoms = PANEGEOMS_JSON[1] + .iter() + .map(|pg| parse_panegeom_from_json(pg)) + .map(|geom| PaneLayoutManifest { + geom, + ..Default::default() + }) + .collect(); + let tab_layout_manifest = TabLayoutManifest { + tiled_panes: geoms, + ..Default::default() + }; + let global_layout_manifest = GlobalLayoutManifest { + tabs: vec![("Tab #1".to_owned(), tab_layout_manifest)], + ..Default::default() + }; + let kdl = serialize_session_layout(global_layout_manifest); + expect![[r#"layout { + tab name="Tab #1" { + pane + pane size=20 split_direction="vertical" { + pane size=50 + pane + } + } +}"#]] + .assert_eq(&kdl.0); + + let geoms = PANEGEOMS_JSON[2] + .iter() + .map(|pg| parse_panegeom_from_json(pg)) + .map(|geom| PaneLayoutManifest { + geom, + ..Default::default() + }) + .collect(); + let tab_layout_manifest = TabLayoutManifest { + tiled_panes: geoms, + ..Default::default() + }; + let global_layout_manifest = GlobalLayoutManifest { + tabs: vec![("Tab #1".to_owned(), tab_layout_manifest)], + ..Default::default() + }; + let kdl = serialize_session_layout(global_layout_manifest); + expect![[r#"layout { + tab name="Tab #1" { + pane size=10 split_direction="vertical" { + pane size="50%" + pane size="50%" + } + pane split_direction="vertical" { + pane size=40 + pane + pane size=40 + } + pane size=10 split_direction="vertical" { + pane size="50%" + pane size="50%" + } + } +}"#]] + .assert_eq(&kdl.0); + + let geoms = PANEGEOMS_JSON[3] + .iter() + .map(|pg| parse_panegeom_from_json(pg)) + .map(|geom| PaneLayoutManifest { + geom, + ..Default::default() + }) + .collect(); + let tab_layout_manifest = TabLayoutManifest { + tiled_panes: geoms, + ..Default::default() + }; + let global_layout_manifest = GlobalLayoutManifest { + tabs: vec![("Tab #1".to_owned(), tab_layout_manifest)], + ..Default::default() + }; + let kdl = serialize_session_layout(global_layout_manifest); + expect![[r#"layout { + tab name="Tab #1" { + pane split_direction="vertical" { + pane size="70%" { + pane split_direction="vertical" { + pane size="50%" { + pane size="30%" + pane size="30%" + pane size="40%" + } + pane size="50%" + } + pane size=10 + } + pane size="30%" + } + } +}"#]] + .assert_eq(&kdl.0); + + let geoms = PANEGEOMS_JSON[4] + .iter() + .map(|pg| parse_panegeom_from_json(pg)) + .map(|geom| PaneLayoutManifest { + geom, + ..Default::default() + }) + .collect(); + let tab_layout_manifest = TabLayoutManifest { + tiled_panes: geoms, + ..Default::default() + }; + let global_layout_manifest = GlobalLayoutManifest { + tabs: vec![("Tab #1".to_owned(), tab_layout_manifest)], + ..Default::default() + }; + let kdl = serialize_session_layout(global_layout_manifest); + expect![[r#"layout { + tab name="Tab #1" { + pane size=5 + pane split_direction="vertical" { + pane size=20 + pane size="50%" + pane size="50%" + pane size=20 + } + pane size=5 + } +}"#]] + .assert_eq(&kdl.0); + } + // utility functions + fn parse_panegeom_from_json(data_str: &str) -> PaneGeom { + // + // Expects this input + // + // r#"{ "x": 0, "y": 1, "rows": { "constraint": "Percent(100.0)", "inner": 43 }, "cols": { "constraint": "Percent(100.0)", "inner": 211 }, "is_stacked": false }"#, + // + let data: HashMap = serde_json::from_str(data_str).unwrap(); + PaneGeom { + x: data["x"].to_string().parse().unwrap(), + y: data["y"].to_string().parse().unwrap(), + rows: get_dim(&data["rows"]), + cols: get_dim(&data["cols"]), + is_stacked: data["is_stacked"].to_string().parse().unwrap(), + } + } + + fn get_dim(dim_hm: &Value) -> Dimension { + let constr_str = dim_hm["constraint"].to_string(); + let dim = if constr_str.contains("Fixed") { + let value = &constr_str[7..constr_str.len() - 2]; + Dimension::fixed(value.parse().unwrap()) + } else if constr_str.contains("Percent") { + let value = &constr_str[9..constr_str.len() - 2]; + let mut dim = Dimension::percent(value.parse().unwrap()); + dim.set_inner(dim_hm["inner"].to_string().parse().unwrap()); + dim + } else { + panic!("Constraint is nor a percent nor fixed"); + }; + dim + } +} diff --git a/zellij-utils/src/setup.rs b/zellij-utils/src/setup.rs index bacb12b410..2c40f29d5f 100644 --- a/zellij-utils/src/setup.rs +++ b/zellij-utils/src/setup.rs @@ -8,6 +8,7 @@ use crate::{ ZELLIJ_DEFAULT_THEMES, ZELLIJ_PROJ_DIR, }, errors::prelude::*, + home::*, input::{ config::{Config, ConfigError}, layout::Layout, @@ -17,12 +18,17 @@ use crate::{ use clap::{Args, IntoApp}; use clap_complete::Shell; use directories::BaseDirs; +use log::info; use serde::{Deserialize, Serialize}; use std::{ - convert::TryFrom, fmt::Write as FmtWrite, io::Write, path::Path, path::PathBuf, process, + convert::TryFrom, + fmt::Write as FmtWrite, + fs, + io::Write, + path::{Path, PathBuf}, + process, }; -const CONFIG_LOCATION: &str = ".config/zellij"; const CONFIG_NAME: &str = "config.kdl"; static ARROW_SEPARATOR: &str = ""; @@ -107,6 +113,7 @@ pub fn get_layout_dir(config_dir: Option) -> Option { pub fn get_theme_dir(config_dir: Option) -> Option { config_dir.map(|dir| dir.join("themes")) } + pub fn dump_asset(asset: &[u8]) -> std::io::Result<()> { std::io::stdout().write_all(asset)?; Ok(()) @@ -196,6 +203,17 @@ pub const ZSH_AUTO_START_SCRIPT: &[u8] = include_bytes!(concat!( "assets/shell/auto-start.zsh" )); +pub fn add_layout_ext(s: &str) -> String { + match s { + c if s.ends_with(".kdl") => c.to_owned(), + _ => { + let mut s = s.to_owned(); + s.push_str(".kdl"); + s + }, + } +} + pub fn dump_default_config() -> std::io::Result<()> { dump_asset(DEFAULT_CONFIG) } @@ -206,10 +224,24 @@ pub fn dump_specified_layout(layout: &str) -> std::io::Result<()> { "default" => dump_asset(DEFAULT_LAYOUT), "compact" => dump_asset(COMPACT_BAR_LAYOUT), "disable-status" => dump_asset(NO_STATUS_LAYOUT), - not_found => Err(std::io::Error::new( - std::io::ErrorKind::Other, - format!("Layout: {} not found", not_found), - )), + custom => { + info!("Dump {custom} layout"); + let custom = add_layout_ext(custom); + let home = default_layout_dir(); + let path = home.map(|h| h.join(&custom)); + let layout_exists = path.as_ref().map(|p| p.exists()).unwrap_or_default(); + + match (path, layout_exists) { + (Some(path), true) => { + let content = fs::read_to_string(path)?; + std::io::stdout().write_all(content.as_bytes()) + }, + _ => { + log::error!("No layout named {custom} found"); + return Ok(()); + }, + } + }, } } @@ -276,7 +308,7 @@ pub struct Setup { #[clap(long, value_parser)] pub check: bool, - /// Dump the specified layout file to stdout + /// Dump specified layout to stdout #[clap(long, value_parser)] pub dump_layout: Option, @@ -376,7 +408,7 @@ impl Setup { } if let Some(layout) = &self.dump_layout { - dump_specified_layout(layout)?; + dump_specified_layout(&layout)?; std::process::exit(0); } diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_config_options.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_config_options.snap index 0ef13c0251..6e08b82cd3 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_config_options.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_config_options.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 597 +assertion_line: 686 expression: "format!(\"{:#?}\", options)" --- Options { @@ -26,4 +26,7 @@ Options { session_name: None, attach_to_session: None, auto_layout: None, + session_serialization: None, + serialize_pane_viewport: None, + scrollback_lines_to_serialize: None, } diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options-2.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options-2.snap index a3d69ce85b..276c2fb3c2 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options-2.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options-2.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 683 +assertion_line: 715 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -21,6 +21,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options.snap index ac3f4bf1e4..d02d83bcd2 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 625 +assertion_line: 714 expression: "format!(\"{:#?}\", options)" --- Options { @@ -26,4 +26,7 @@ Options { session_name: None, attach_to_session: None, auto_layout: None, + session_serialization: None, + serialize_pane_viewport: None, + scrollback_lines_to_serialize: None, } diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap index f68f6c5baa..e98379ddba 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 640 +assertion_line: 672 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -43,6 +43,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -57,6 +59,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -89,6 +93,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -100,6 +106,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), @@ -145,6 +153,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -167,6 +177,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -183,6 +195,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -194,6 +208,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -205,6 +221,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -237,6 +255,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -248,6 +268,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, MaxPanes( 8, @@ -286,6 +308,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -310,6 +334,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -328,6 +354,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -342,6 +370,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -356,6 +386,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -370,6 +402,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -381,6 +415,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -392,6 +428,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -403,6 +441,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -435,6 +475,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -446,6 +488,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, MaxPanes( 12, @@ -484,6 +528,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -508,6 +554,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -526,6 +574,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -540,6 +590,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -554,6 +606,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -568,6 +622,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -579,6 +635,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -597,6 +655,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -611,6 +671,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -625,6 +687,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -639,6 +703,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -650,6 +716,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -661,6 +729,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -672,6 +742,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -704,6 +776,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -715,6 +789,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, }, Some( @@ -760,6 +836,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -778,6 +856,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -792,6 +872,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -803,6 +885,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -835,6 +919,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -846,6 +932,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, MaxPanes( 8, @@ -884,6 +972,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -908,6 +998,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Vertical, @@ -926,6 +1018,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -940,6 +1034,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -954,6 +1050,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -968,6 +1066,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -979,6 +1079,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -990,6 +1092,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -1001,6 +1105,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -1033,6 +1139,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -1044,6 +1152,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, MaxPanes( 12, @@ -1082,6 +1192,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -1106,6 +1218,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Vertical, @@ -1124,6 +1238,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -1138,6 +1254,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -1152,6 +1270,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -1166,6 +1286,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -1177,6 +1299,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Vertical, @@ -1195,6 +1319,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -1209,6 +1335,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -1223,6 +1351,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -1237,6 +1367,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -1248,6 +1380,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -1259,6 +1393,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -1270,6 +1406,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -1302,6 +1440,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -1313,6 +1453,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, }, Some( @@ -1358,6 +1500,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -1380,6 +1524,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -1396,6 +1542,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -1407,6 +1555,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -1418,6 +1568,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, TiledPaneLayout { children_split_direction: Horizontal, @@ -1450,6 +1602,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, ], split_size: None, @@ -1461,6 +1615,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, }, Some( @@ -1507,6 +1663,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, FloatingPaneLayout { name: None, @@ -1533,6 +1690,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, FloatingPaneLayout { name: None, @@ -1559,6 +1717,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, FloatingPaneLayout { name: None, @@ -1585,6 +1744,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, FloatingPaneLayout { name: None, @@ -1611,6 +1771,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, FloatingPaneLayout { name: None, @@ -1637,6 +1798,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, FloatingPaneLayout { name: None, @@ -1663,6 +1825,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, FloatingPaneLayout { name: None, @@ -1689,6 +1852,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, FloatingPaneLayout { name: None, @@ -1715,6 +1879,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, FloatingPaneLayout { name: None, @@ -1743,6 +1908,7 @@ Layout { true, ), already_running: false, + pane_initial_contents: None, }, ], }, @@ -1772,6 +1938,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, ], MaxPanes( @@ -1798,6 +1965,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, FloatingPaneLayout { name: None, @@ -1820,6 +1988,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, ], MaxPanes( @@ -1848,6 +2017,7 @@ Layout { true, ), already_running: false, + pane_initial_contents: None, }, FloatingPaneLayout { name: None, @@ -1870,6 +2040,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, FloatingPaneLayout { name: None, @@ -1892,6 +2063,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, ], MaxPanes( @@ -1922,6 +2094,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, FloatingPaneLayout { name: None, @@ -1950,6 +2123,7 @@ Layout { true, ), already_running: false, + pane_initial_contents: None, }, FloatingPaneLayout { name: None, @@ -1976,6 +2150,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, FloatingPaneLayout { name: None, @@ -2002,6 +2177,7 @@ Layout { run: None, focus: None, already_running: false, + pane_initial_contents: None, }, ], }, diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-3.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-3.snap index cf623d2e52..fbf93ce208 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-3.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-3.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 584 +assertion_line: 673 expression: "format!(\"{:#?}\", options)" --- Options { @@ -24,4 +24,7 @@ Options { session_name: None, attach_to_session: None, auto_layout: None, + session_serialization: None, + serialize_pane_viewport: None, + scrollback_lines_to_serialize: None, } diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap index 9715474152..3083777a79 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 639 +assertion_line: 671 expression: "format!(\"{:#?}\", config)" --- Config { @@ -3589,6 +3589,9 @@ Config { session_name: None, attach_to_session: None, auto_layout: None, + session_serialization: None, + serialize_pane_viewport: None, + scrollback_lines_to_serialize: None, }, themes: {}, plugins: { diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap index 589de23284..3bd3a5ab89 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 697 +assertion_line: 729 expression: "format!(\"{:#?}\", config)" --- Config { @@ -3589,6 +3589,9 @@ Config { session_name: None, attach_to_session: None, auto_layout: None, + session_serialization: None, + serialize_pane_viewport: None, + scrollback_lines_to_serialize: None, }, themes: {}, plugins: { diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap index 0a7f74b72c..2a64a6f1a0 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 753 +assertion_line: 785 expression: "format!(\"{:#?}\", config)" --- Config { @@ -82,6 +82,9 @@ Config { session_name: None, attach_to_session: None, auto_layout: None, + session_serialization: None, + serialize_pane_viewport: None, + scrollback_lines_to_serialize: None, }, themes: {}, plugins: { diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options-2.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options-2.snap index 4f5321b166..a57f4fd539 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options-2.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options-2.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 665 +assertion_line: 697 expression: "format!(\"{:#?}\", layout)" --- Layout { @@ -21,6 +21,8 @@ Layout { is_expanded_in_stack: false, exclude_from_sync: None, run_instructions_to_ignore: [], + hide_floating_panes: false, + pane_initial_contents: None, }, [], ), diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options.snap index d354e6fed0..a2ccc3c3df 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 607 +assertion_line: 696 expression: "format!(\"{:#?}\", options)" --- Options { @@ -26,4 +26,7 @@ Options { session_name: None, attach_to_session: None, auto_layout: None, + session_serialization: None, + serialize_pane_viewport: None, + scrollback_lines_to_serialize: None, } diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap index ab867fa707..e3911cb8d0 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 725 +assertion_line: 757 expression: "format!(\"{:#?}\", config)" --- Config { @@ -3589,6 +3589,9 @@ Config { session_name: None, attach_to_session: None, auto_layout: None, + session_serialization: None, + serialize_pane_viewport: None, + scrollback_lines_to_serialize: None, }, themes: {}, plugins: { diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap index 6de213ed77..7168d0962b 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 739 +assertion_line: 771 expression: "format!(\"{:#?}\", config)" --- Config { @@ -3589,6 +3589,9 @@ Config { session_name: None, attach_to_session: None, auto_layout: None, + session_serialization: None, + serialize_pane_viewport: None, + scrollback_lines_to_serialize: None, }, themes: { "other-theme-from-config": Theme { diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap index e44e52f256..b1bbe7457e 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 711 +assertion_line: 743 expression: "format!(\"{:#?}\", config)" --- Config { @@ -3589,6 +3589,9 @@ Config { session_name: None, attach_to_session: None, auto_layout: None, + session_serialization: None, + serialize_pane_viewport: None, + scrollback_lines_to_serialize: None, }, themes: {}, plugins: {