From 0ad202988916828cf4c97101580926dc6dffd741 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Thu, 12 Oct 2023 16:05:45 +0200 Subject: [PATCH] feat(sessions): session resurrection (#2801) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add necessary actions in server and utils * update * move all logic relevant to local default config directories to utils::home * add debug statements for pane geom * add tests; print resulting kdl * fix dumping custom layouts from setup; start fixing algorithm for simplest layout possible * fix: fixed persistence code and tests to support flexible layouts * fix(tab-bar,compact-bar): tab switching with mouse sometimes not working (#2587) * tab-bar: fix clicks sometimes not registering Caching the click position wasn't working across multiple plugin instances. Also a couple of refactors: - move the code with the tab switching logic inside update - avoid rendering when calling switch_tab_to, since it will happen anyway afterwards * same fix for compact-bar * docs(changelog): plugins tab switching with mouse fix * feat(ui): new status bar mode (#2619) * supermode prototype * fix integration tests * fix tests * style(fmt): rustfmt * docs(changelog): status-bar supermode * fix(rendering): occasional glitches while resizing (#2621) * docs(changelog): resize glitches fix * chore(version): bump development version * Fix colored pane frames in mirrored sessions (#2625) * server/panes/tiled: Fix colored frames in mirrored sessions. Colored frames were previously ignored because they were treated like floating panes when rendering tiled panes. * CHANGELOG: Add PR #2625 * server/tab/unit: Fix unit tests for server. * fix(sessions): use custom lists of adjectives and nouns for generating session names (#2122) * Create custom lists of adjectives and nouns for generating session names * move word lists to const slices * add logic to retry name generation * refactor - reuse the name generator - iterator instead of for loop --------- Co-authored-by: Thomas Linford * docs(changelog): generate session names with custom words list * feat(plugins): make plugins configurable (#2646) * work * make every plugin entry point configurable * make integration tests pass * make e2e tests pass * add test for plugin configuration * add test snapshot * add plugin config parsing test * cleanups * style(fmt): rustfmt * style(comment): remove commented code * docs(changelog): configurable plugins * fix(terminal): properly handle resizes in alternate screen (#2654) * docs(changelog): focus glitches * feat(plugins): utility functions to find active pane and tab (#2652) * docs(changelog): plugin api utility functions * feat(ui): break pane to new tab and move panes between tabs (#2664) * prototype * some tests * break out floating pane * break out plugin panes * add keybind and fix some minor issues * remove cli * move pane to left/right tab * update ui * adjust ui * style(fmt): rustfmt * style(comment): remove commented code * update snapshots * docs(changelog): break pane to new tab * fix(performance): plug memory leak (#2675) * docs(changelog): plug memory leak * feat(plugins): use protocol buffers for serializing across the wasm boundary (#2686) * work * almost done with command protobuffers * done translating command data structures * mid transferring of every command to protobuff command * transferred plugin_command.rs, now moving on to shim.rs * plugin command working with protobufs * protobuffers in update * protobuf event tests * various TODOs and comments * fix zellij-tile * clean up prost deps * remove version mismatch error * fix panic * some cleanups * clean up event protobuffers * clean up command protobuffers * clean up various protobufs * refactor protobufs * update comments * some transformation fixes * use protobufs for workers * style(fmt): rustfmt * style(fmt): rustfmt * chore(build): add protoc * chore(build): authenticate protoc * docs(changelog): protobuffers * feat: add plugin permission system (#2624) * WIP: add exaple of permission ui * feat: add request permission ui * feat: add caching permission in memory * feat: add permission check * feat: add file caching * fix: changes request * feat(ui): new status bar mode (#2619) * supermode prototype * fix integration tests * fix tests * style(fmt): rustfmt * docs(changelog): status-bar supermode * fix(rendering): occasional glitches while resizing (#2621) * docs(changelog): resize glitches fix * chore(version): bump development version * Fix colored pane frames in mirrored sessions (#2625) * server/panes/tiled: Fix colored frames in mirrored sessions. Colored frames were previously ignored because they were treated like floating panes when rendering tiled panes. * CHANGELOG: Add PR #2625 * server/tab/unit: Fix unit tests for server. * fix(sessions): use custom lists of adjectives and nouns for generating session names (#2122) * Create custom lists of adjectives and nouns for generating session names * move word lists to const slices * add logic to retry name generation * refactor - reuse the name generator - iterator instead of for loop --------- Co-authored-by: Thomas Linford * docs(changelog): generate session names with custom words list * feat(plugins): make plugins configurable (#2646) * work * make every plugin entry point configurable * make integration tests pass * make e2e tests pass * add test for plugin configuration * add test snapshot * add plugin config parsing test * cleanups * style(fmt): rustfmt * style(comment): remove commented code * docs(changelog): configurable plugins * style(fmt): rustfmt * touch up ui * fix: don't save permission data in memory * feat: load cached permission * test: add example test (WIP) * fix: issue event are always denied * test: update snapshot * apply formatting * refactor: update default cache function * test: add more new test * apply formatting * Revert "apply formatting" This reverts commit a4e93703fbfdb6865131daa1c8b90fc5c36ab25e. * apply format * fix: update cache path * apply format * fix: cache path * fix: update log level * test for github workflow * Revert "test for github workflow" This reverts commit 01eff3bc5d1627a4e60bc6dac8ebe5500bc5b56e. * refactor: permission cache * fix(test): permission grant/deny race condition * style(fmt): rustfmt * style(fmt): rustfmt * configure permissions * permission denied test * snapshot * add ui for small plugins * style(fmt): rustfmt * some cleanups --------- Co-authored-by: Aram Drevekenin Co-authored-by: har7an <99636919+har7an@users.noreply.github.com> Co-authored-by: Kyle Sutherland-Cash Co-authored-by: Thomas Linford Co-authored-by: Thomas Linford * docs(changelog): permission system * feat(sessions): add a session manager to switch between sessions, tabs and panes and create new ones (#2721) * write/read session metadata to disk for all sessions * switch session client side * fix tests * various adjustments * fix full screen focus bug in tiled panes * fix tests * fix permission sorting issue * cleanups * add session manager * fix tests * various cleanups * style(fmt): rustfmt * clear screen before switching sessions * I hate you clippy * truncate controls line to width * version session cache * attempt to fix plugin tests * style(fmt): rustfmt * another attempt to fix the tests in the ci * docs(changelog): session manager * fix(ux): various ui/ux fixes (#2722) * force plugin render on permission request response * clear warnings * Revert "feat(ui): new status bar mode (#2619)" This reverts commit 27763d26ab83dd9f81c69c75601cbf6075f13790. * adjust status bar help * fix colors in session manager and shortcut in status-bar * adjust keybindings * docs(changelog): update ux fixes * feat(plugins): optionally move plugin to focused tab (#2725) * feat(plugins): move_to_focused_tab attribute for LaunchOrFocusPlugin * style(fmt): rustfmt * docs(changelog): move plugin to focused tab * fix(keybinds): add 'floating' and 'name' to the Run command keybinding (#2726) * fix(keybinds): add 'floating' and 'name' to the Run command keybinding * style(fmt): rustfmt * docs(changelog): keybind run floating pane * fix(plugins): make sure configuration is also part of the plugin keys (#2727) * fix(plugins): make sure configuration is also part of the plugin keys * no thanks clippy * docs(changelog): fix plugin configuration uniqueness * fix(plugins): remove protobuf duplications (#2729) * fix(plugins): remove protobuf duplications * style(fmt): rustfmt * Update CHANGELOG.md * fix(plugins): various ui fixes (#2731) * Update CHANGELOG.md * fix(panes): refocus pane properly on tab change (#2734) * fix(panes): stacked panes focus bug * style(fmt): rustfmt * docs(changelog): stacked pane focus glitch * xtask/pipeline: Fix publish task (#2711) * xtask/pipeline: Fix publish task which was previously stuck in an infinite loop after successfully publishing a crate. The error originated in the code only checking for error conditions but not breaking out of the inner infinite loop in case of success. * xtask: Improve publish failure UX by offering the user more actions to choose from when an error occured. * utils/assets: Add generated prost files to assets to make sure they're available at build time and are picked up by all components. It seems we hit some strange bug with the build script where, when running `cargo publish --dry-run` the build script **is not** run before regularly compiling zellij-utils. This shouldn't happen according to the docs, but I cannot explain what's causing it. So we're using this as a workaround for now to make a smooth release. * xtask: Prevent accidental git commit deletion when dry-running a publish. * utils: Add comments to protobuf-related code to explain why these changes were performed. The comments all include a link to an issue comment explaining the situation in greater detail. * xtask: Build protobuf definitions when building any part of the project, similar to how we build the plugins when required. This should ensure that all crates built through `cargo xtask` (which is the officially supported build method) will receive up-to-date protobuf definitions. * chore(release): v0.38.0 * chore(version): bump development version * refactor(server): remove unnecessary mut (#2735) * docs(changelog): refactor server * chore(repo): update build instructions * fix(status-bar): add break tab hints (#2748) * fix(status-bar): add break tab hints * fix(tests): update snapshot to new hints * Update CHANGELOG.md * fix(reconnect): do not clear terminal state when entering alternate screen (#2750) * debug * refactor(reconnect): articular reconnection logic * docs(changelog): fix glitches on windows terminal * fix(grid): memory leak with unfocused tabs (#2745) * use hashset instead of vec for changed lines avoid output buffer growring indefinitely if tab does not get rendered * tidy up - improve hashset -> vec conversion - remove now unnecessary dedup * use copied instead of cloned on iter * docs(changelog): grid memory leak fix * fix(input): block input thread for newtiledpane and newfloatingpane as well (#2757) * docs(changelog): input action new pane fix * chore(version): adjust version for release * chore(release): v0.38.1 * chore(version): bump development version * fix(terminal): wrap lines when adding characters in alternate screen (#2789) * docs(changelog): line wrap bug * chore(version): bump version for patch release * chore(release): v0.38.2 * chore(version): bump development version * fix(utils): validate session name (#2607) * fix(utils): validate session name * cargo fmt * refactor: assign constant values to variables * refactor: move operations unrealted to the condition --------- Co-authored-by: Jae-Heon Ji * docs(changelog): fix validate session name * merge conflict fix * feat(panes): in place run (#2795) * prototype * fix tests * add to all the things except plugins * add in-place to plugin commands * fix launch-or-focus should_float and in place behavior * various cleanups * style(fmt): rustfmt * docs * bring in commands to dumped layout * tidy up data structures * bring in plugins to dumped layout * fix tests * style(fmt): rustfmt * chore: rename file (#2803) Signed-off-by: AlixBernard * bring in floating panes * bring in stacked panes * style(fmt): rustfmt * bring in new_tab_template * bring in swap layouts * bring in edit panes, command panes and cwds * consolidate CWD common prefixes when possible * filter out default shell * style(fmt): rustfmt * handle scrollback editor panes properly * handle in place panes properly * bring in pane names * style(fmt): rustfmt * style(fmt): rustfmt * dump layout action to terminal * log session layout to HD periodically * resurrect dead sessions by attaching to them * delete dead sessions * style(fmt): rustfmt * start command panes as suspended by default * style(fmt): rustfmt * respect tab/pane focus * improve dump performance * hide_floating_panes in layout and resurrection * show resurrectable sessions in zellij ls and include timestamps * style(fmt): rustfmt * allow disabling session serialization in config * style(fmt): rustfmt * fix e2e tests * add e2e test * style(fmt): rustfmt * style(fmt): rustfmt * serialize and restore pane viewport * fix e2e tests and add new one * style(fmt): rustfmt * cleanups * cleanups * more cleanups * refactor: move stuff around * fix e2e tests * style(fmt): rustfmt * style(fmt): handle compilation warnings * add tests for new layout properties * fix current session name indication * style(fmt): rustfmt * adjust default config * some cleanups * go away clippy --------- Signed-off-by: AlixBernard Co-authored-by: alekspickle Co-authored-by: Example Name Co-authored-by: Oleks Gnatovskyi <22867443+alekspickle@users.noreply.github.com> Co-authored-by: Thomas Linford Co-authored-by: har7an <99636919+har7an@users.noreply.github.com> Co-authored-by: Kyle Sutherland-Cash Co-authored-by: Thomas Linford Co-authored-by: Nacho114 <17376073+Nacho114@users.noreply.github.com> Co-authored-by: Jae-Heon Ji <32578710+jaeheonji@users.noreply.github.com> Co-authored-by: Orhun Parmaksız Co-authored-by: deepsghimire <70006817+deepsghimire@users.noreply.github.com> Co-authored-by: Jae-Heon Ji Co-authored-by: AlixBernard <56587201+AlixBernard@users.noreply.github.com> --- Cargo.lock | 25 + Cargo.toml | 8 +- docs/MANPAGE.md | 1 + src/commands.rs | 119 +- src/main.rs | 13 +- src/sessions.rs | 249 +++- src/tests/e2e/cases.rs | 119 ++ src/tests/e2e/remote_runner.rs | 123 ++ ...2e__cases__quit_and_resurrect_session.snap | 29 + ...t_session_with_viewport_serialization.snap | 29 + .../fixtures/layout_for_resurrection.kdl | 373 ++++++ zellij-client/src/lib.rs | 4 +- .../convert_old_yaml_files.rs | 2 +- zellij-server/src/background_jobs.rs | 79 +- zellij-server/src/lib.rs | 7 +- zellij-server/src/os_input_output.rs | 52 + zellij-server/src/output/mod.rs | 82 +- zellij-server/src/panes/floating_panes/mod.rs | 4 +- zellij-server/src/panes/grid.rs | 22 + zellij-server/src/panes/plugin_pane.rs | 7 + zellij-server/src/panes/terminal_pane.rs | 10 + zellij-server/src/plugins/mod.rs | 37 + zellij-server/src/plugins/plugin_map.rs | 24 +- ...new_tabs_with_layout_plugin_command-2.snap | 8 +- ...__new_tabs_with_layout_plugin_command.snap | 8 +- zellij-server/src/plugins/wasm_bridge.rs | 10 +- zellij-server/src/pty.rs | 103 +- zellij-server/src/route.rs | 9 + zellij-server/src/screen.rs | 173 ++- zellij-server/src/session_layout_metadata.rs | 278 +++++ zellij-server/src/tab/layout_applier.rs | 23 +- zellij-server/src/tab/mod.rs | 125 +- zellij-server/src/unit/screen_tests.rs | 11 + ...end_cli_new_tab_action_default_params.snap | 8 +- ...i_new_tab_action_with_name_and_layout.snap | 10 +- zellij-utils/Cargo.toml | 3 + zellij-utils/assets/config/default.kdl | 19 + zellij-utils/src/cli.rs | 37 +- zellij-utils/src/consts.rs | 12 + zellij-utils/src/errors.rs | 7 + zellij-utils/src/home.rs | 84 ++ zellij-utils/src/input/actions.rs | 8 +- zellij-utils/src/input/config.rs | 4 +- zellij-utils/src/input/layout.rs | 172 ++- zellij-utils/src/input/options.rs | 41 + zellij-utils/src/input/unit/layout_test.rs | 55 + ..._test__args_added_to_args_in_template.snap | 6 + ..._test__args_override_args_in_template.snap | 6 + ..._define_a_stack_with_an_expanded_pane.snap | 10 + ...define_stacked_children_for_pane_node.snap | 8 + ...ne_stacked_children_for_pane_template.snap | 10 + ...ad_swap_layouts_from_a_different_file.snap | 72 ++ ...n_not_as_first_child_of_pane_template.snap | 24 + ...en_not_as_first_child_of_tab_template.snap | 24 + ...it_added_to_close_on_exit_in_template.snap | 6 + ...t_overrides_close_on_exit_in_template.snap | 6 + ..._and_pane_template_both_with_children.snap | 34 + ...ut_test__cwd_added_to_cwd_in_template.snap | 6 + ...ut_test__cwd_override_cwd_in_template.snap | 6 + ...ayout__layout_test__env_var_expansion.snap | 20 + ...epended_to_panes_with_and_without_cwd.snap | 8 + ...ith_and_without_cwd_in_pane_templates.snap | 14 + ...with_and_without_cwd_in_tab_templates.snap | 12 + ...global_cwd_given_to_panes_without_cwd.snap | 6 + ...al_cwd_passed_from_layout_constructor.snap | 6 + ...r_overrides_global_cwd_in_layout_file.snap | 6 + ...lobal_cwd_prepended_to_panes_with_cwd.snap | 6 + ...th_tab_cwd_given_to_panes_without_cwd.snap | 8 + ..._with_command_panes_and_close_on_exit.snap | 4 + ...ith_command_panes_and_start_suspended.snap | 4 + ...est__layout_with_default_tab_template.snap | 40 + ...t_with_nested_branched_pane_templates.snap | 22 + ...st__layout_with_nested_pane_templates.snap | 16 + ...ut_test__layout_with_new_tab_template.snap | 214 ++++ ...__layout_with_pane_excluded_from_sync.snap | 4 + ...yout_test__layout_with_pane_templates.snap | 44 + ...t__layout_with_tab_and_pane_templates.snap | 14 + ...__layout_with_tabs_and_floating_panes.snap | 13 + ...s_overriden_by_its_consumers_bare_cwd.snap | 4 + ...verriden_by_its_consumers_command_cwd.snap | 4 + ..._consumer_command_does_not_have_a_cwd.snap | 4 + ...cwd_is_overriden_by_its_consumers_cwd.snap | 4 + ...t_cwd_receives_its_consumers_bare_cwd.snap | 4 + ...d_overriden_by_its_consumers_bare_cwd.snap | 4 + ...ated_to_its_consumer_command_with_cwd.snap | 4 + ...d_to_its_consumer_command_without_cwd.snap | 4 + ..._bare_propagated_to_its_consumer_edit.snap | 4 + ...mmand_propagated_to_its_consumer_edit.snap | 4 + ...t__tab_cwd_given_to_panes_without_cwd.snap | 8 + ...__tab_cwd_prepended_to_panes_with_cwd.snap | 8 + zellij-utils/src/kdl/kdl_layout_parser.rs | 74 +- zellij-utils/src/kdl/mod.rs | 22 +- zellij-utils/src/lib.rs | 6 +- zellij-utils/src/pane_size.rs | 40 +- zellij-utils/src/plugin_api/action.rs | 1 + zellij-utils/src/session_serialization.rs | 1109 +++++++++++++++++ zellij-utils/src/setup.rs | 48 +- ...cli_arguments_override_config_options.snap | 5 +- ...i_arguments_override_layout_options-2.snap | 4 +- ...cli_arguments_override_layout_options.snap | 5 +- ...efault_config_with_no_cli_arguments-2.snap | 178 ++- ...efault_config_with_no_cli_arguments-3.snap | 5 +- ..._default_config_with_no_cli_arguments.snap | 5 +- ...out_env_vars_override_config_env_vars.snap | 5 +- ...out_keybinds_override_config_keybinds.snap | 5 +- ...out_options_override_config_options-2.snap | 4 +- ...ayout_options_override_config_options.snap | 5 +- ...ayout_plugins_override_config_plugins.snap | 5 +- ..._layout_themes_override_config_themes.snap | 5 +- ..._ui_config_overrides_config_ui_config.snap | 5 +- 110 files changed, 4668 insertions(+), 229 deletions(-) create mode 100644 src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session.snap create mode 100644 src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session_with_viewport_serialization.snap create mode 100644 src/tests/fixtures/layout_for_resurrection.kdl create mode 100644 zellij-server/src/session_layout_metadata.rs create mode 100644 zellij-utils/src/home.rs create mode 100644 zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_new_tab_template.snap create mode 100644 zellij-utils/src/session_serialization.rs 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: {