feat(sessions): session resurrection (#2801)

* 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 <linford.t@gmail.com>

* 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 <linford.t@gmail.com>

* 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 <aram@poor.dev>
Co-authored-by: har7an <99636919+har7an@users.noreply.github.com>
Co-authored-by: Kyle Sutherland-Cash <kyle.sutherlandcash@gmail.com>
Co-authored-by: Thomas Linford <linford.t@gmail.com>
Co-authored-by: Thomas Linford <tlinford@users.noreply.github.com>

* 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 27763d26ab.

* 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 <atx6419@gmail.com>

* 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 <alix.bernard9@gmail.com>

* 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 <alix.bernard9@gmail.com>
Co-authored-by: alekspickle <aleks.work2222+gh@gmail.com>
Co-authored-by: Example Name <example@example.test>
Co-authored-by: Oleks Gnatovskyi <22867443+alekspickle@users.noreply.github.com>
Co-authored-by: Thomas Linford <tlinford@users.noreply.github.com>
Co-authored-by: har7an <99636919+har7an@users.noreply.github.com>
Co-authored-by: Kyle Sutherland-Cash <kyle.sutherlandcash@gmail.com>
Co-authored-by: Thomas Linford <linford.t@gmail.com>
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 <orhunparmaksiz@gmail.com>
Co-authored-by: deepsghimire <70006817+deepsghimire@users.noreply.github.com>
Co-authored-by: Jae-Heon Ji <atx6419@gmail.com>
Co-authored-by: AlixBernard <56587201+AlixBernard@users.noreply.github.com>
This commit is contained in:
Aram Drevekenin 2023-10-12 16:05:45 +02:00 committed by GitHub
parent 9e8ebe465b
commit 23e80c1fad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
110 changed files with 4665 additions and 227 deletions

25
Cargo.lock generated
View file

@ -506,6 +506,12 @@ version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dfdf9179d546b55ff3f88c9d93ecfaa3e9760163da5a1080af5243230dbbb70" checksum = "4dfdf9179d546b55ff3f88c9d93ecfaa3e9760163da5a1080af5243230dbbb70"
[[package]]
name = "common-path"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101"
[[package]] [[package]]
name = "compact-bar" name = "compact-bar"
version = "0.1.0" version = "0.1.0"
@ -892,6 +898,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]]
name = "dissimilar"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "210ec60ae7d710bed8683e333e9d2855a8a56a3e9892b38bad3bb0d4d29b0d5e"
[[package]] [[package]]
name = "dynasm" name = "dynasm"
version = "1.2.3" version = "1.2.3"
@ -1007,6 +1019,16 @@ version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" 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]] [[package]]
name = "fake-simd" name = "fake-simd"
version = "0.1.2" version = "0.1.2"
@ -4638,8 +4660,11 @@ dependencies = [
"clap_complete", "clap_complete",
"colored", "colored",
"colorsys", "colorsys",
"common-path",
"crossbeam", "crossbeam",
"directories", "directories",
"expect-test",
"humantime",
"include_dir", "include_dir",
"insta", "insta",
"interprocess", "interprocess",

View file

@ -77,10 +77,10 @@ pkg-fmt = "tgz"
[features] [features]
# See remarks in zellij_utils/Cargo.toml # See remarks in zellij_utils/Cargo.toml
default = [ "zellij-utils/plugins_from_target" ] default = ["zellij-utils/plugins_from_target"]
disable_automatic_asset_installation = [ "zellij-utils/disable_automatic_asset_installation" ] disable_automatic_asset_installation = ["zellij-utils/disable_automatic_asset_installation"]
unstable = [ "zellij-client/unstable", "zellij-utils/unstable" ] unstable = ["zellij-client/unstable", "zellij-utils/unstable"]
singlepass = [ "zellij-server/singlepass" ] singlepass = ["zellij-server/singlepass"]
# uncomment this when developing plugins in the Zellij UI to make plugin compilation faster # uncomment this when developing plugins in the Zellij UI to make plugin compilation faster
# [profile.dev.package."*"] # [profile.dev.package."*"]

View file

@ -155,6 +155,7 @@ ACTIONS
Right, Up, Down). Right, Up, Down).
* __Clear__ - clears current screen. * __Clear__ - clears current screen.
* __DumpScreen: <File\>__ - dumps the screen in the specified file. * __DumpScreen: <File\>__ - dumps the screen in the specified file.
* __DumpLayout: <File\>__ - dumps the screen in the specified or default file.
* __EditScrollback__ - replaces the current pane with the scrollback buffer. * __EditScrollback__ - replaces the current pane with the scrollback buffer.
* __ScrollUp__ - scrolls up 1 line in the focused pane. * __ScrollUp__ - scrolls up 1 line in the focused pane.
* __ScrollDown__ - scrolls down 1 line in the focused pane. * __ScrollDown__ - scrolls down 1 line in the focused pane.

View file

@ -1,10 +1,12 @@
use dialoguer::Confirm; 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::{ 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, 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::{ use zellij_client::{
old_config_converter::{ old_config_converter::{
@ -49,7 +51,7 @@ pub(crate) fn kill_all_sessions(yes: bool) {
} }
} }
for session in &sessions { for session in &sessions {
kill_session_impl(session); kill_session_impl(&session.0);
} }
process::exit(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<String> = 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<String>) { pub(crate) fn kill_session(target_session: &Option<String>) {
match target_session { match target_session {
Some(target_session) => { Some(target_session) => {
@ -74,6 +109,20 @@ pub(crate) fn kill_session(target_session: &Option<String>) {
} }
} }
pub(crate) fn delete_session(target_session: &Option<String>, 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<OsInputOutput>( fn get_os_input<OsInputOutput>(
fn_get_os_input: fn() -> Result<OsInputOutput, nix::Error>, fn_get_os_input: fn() -> Result<OsInputOutput, nix::Error>,
) -> OsInputOutput { ) -> 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( pub(crate) fn send_action_to_session(
cli_action: zellij_utils::cli::CliAction, cli_action: zellij_utils::cli::CliAction,
requested_session_name: Option<String>, requested_session_name: Option<String>,
@ -141,7 +193,11 @@ pub(crate) fn send_action_to_session(
attach_with_cli_client(cli_action, &session_name, config); attach_with_cli_client(cli_action, &session_name, config);
}, },
ActiveSession::Many => { ActiveSession::Many => {
let existing_sessions = get_sessions().unwrap(); let existing_sessions: Vec<String> = get_sessions()
.unwrap_or_default()
.iter()
.map(|s| s.0.clone())
.collect();
if let Some(session_name) = requested_session_name { if let Some(session_name) = requested_session_name {
if existing_sessions.contains(&session_name) { if existing_sessions.contains(&session_name) {
attach_with_cli_client(cli_action, &session_name, config); 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 '{}' not found. The following sessions are active:",
session_name session_name
); );
print_sessions(existing_sessions); list_sessions(false);
std::process::exit(1); std::process::exit(1);
} }
} else if let Ok(session_name) = envs::get_session_name() { } else if let Ok(session_name) = envs::get_session_name() {
attach_with_cli_client(cli_action, &session_name, config); attach_with_cli_client(cli_action, &session_name, config);
} else { } else {
eprintln!("Please specify the session name to send actions to. The following sessions are active:"); 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); std::process::exit(1);
} }
}, },
@ -293,7 +349,13 @@ fn attach_with_session_name(
"Ambiguous selection: multiple sessions names start with '{}':", "Ambiguous selection: multiple sessions names start with '{}':",
prefix prefix
); );
print_sessions(sessions); print_sessions(
sessions
.iter()
.map(|s| (s.clone(), Duration::default(), false))
.collect(),
false,
);
process::exit(1); process::exit(1);
}, },
SessionNameMatch::None => { SessionNameMatch::None => {
@ -310,7 +372,7 @@ fn attach_with_session_name(
ActiveSession::One(session_name) => ClientInfo::Attach(session_name, config_options), ActiveSession::One(session_name) => ClientInfo::Attach(session_name, config_options),
ActiveSession::Many => { 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:"); 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); process::exit(1);
}, },
}, },
@ -351,6 +413,7 @@ pub(crate) fn start_client(opts: CliArgs) {
opts.command = Some(Command::Sessions(Sessions::Attach { opts.command = Some(Command::Sessions(Sessions::Attach {
session_name: reconnect_to_session.name.clone(), session_name: reconnect_to_session.name.clone(),
create: true, create: true,
force_run_commands: false,
index: None, index: None,
options: None, options: None,
})); }));
@ -369,6 +432,7 @@ pub(crate) fn start_client(opts: CliArgs) {
if let Some(Command::Sessions(Sessions::Attach { if let Some(Command::Sessions(Sessions::Attach {
session_name, session_name,
create, create,
force_run_commands,
index, index,
options, options,
})) = opts.command.clone() })) = opts.command.clone()
@ -387,10 +451,20 @@ pub(crate) fn start_client(opts: CliArgs) {
.as_ref() .as_ref()
.and_then(|s| session_exists(&s).ok()) .and_then(|s| session_exists(&s).ok())
.unwrap_or(false); .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); 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) { 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::Attach(_, _) => None,
ClientInfo::New(_) => Some(layout), ClientInfo::New(_) => Some(layout),
ClientInfo::Resurrect(_session_name, layout_to_resurrect) => {
Some(layout_to_resurrect.clone())
},
}; };
let tab_position_to_focus = reconnect_to_session let tab_position_to_focus = reconnect_to_session
@ -457,9 +534,12 @@ pub(crate) fn start_client(opts: CliArgs) {
config_options.clone(), config_options.clone(),
true, true,
); );
let attach_layout = match client { let attach_layout = match &client {
ClientInfo::Attach(_, _) => None, ClientInfo::Attach(_, _) => None,
ClientInfo::New(_) => Some(layout), ClientInfo::New(_) => Some(layout),
ClientInfo::Resurrect(_, resurrection_layout) => {
Some(resurrection_layout.clone())
},
}; };
reconnect_to_session = start_client_impl( reconnect_to_session = start_client_impl(
Box::new(os_input), Box::new(os_input),
@ -518,7 +598,16 @@ pub(crate) fn start_client(opts: CliArgs) {
} }
fn generate_unique_session_name() -> String { fn generate_unique_session_name() -> String {
let sessions = get_sessions(); let sessions = get_sessions().map(|sessions| {
sessions
.iter()
.map(|s| s.0.clone())
.collect::<Vec<String>>()
});
let dead_sessions: Vec<String> = get_resurrectable_sessions()
.iter()
.map(|(s, _, _)| s.clone())
.collect();
let Ok(sessions) = sessions else { let Ok(sessions) = sessions else {
eprintln!("Failed to list existing sessions: {:?}", sessions); eprintln!("Failed to list existing sessions: {:?}", sessions);
process::exit(1); process::exit(1);
@ -526,7 +615,7 @@ fn generate_unique_session_name() -> String {
let name = get_name_generator() let name = get_name_generator()
.take(1000) .take(1000)
.find(|name| !sessions.contains(name)); .find(|name| !sessions.contains(name) && !dead_sessions.contains(name));
if let Some(name) = name { if let Some(name) = name {
return name; return name;

View file

@ -87,14 +87,23 @@ fn main() {
} }
} }
if let Some(Command::Sessions(Sessions::ListSessions)) = opts.command { if let Some(Command::Sessions(Sessions::ListSessions { no_formatting })) = opts.command {
commands::list_sessions(); commands::list_sessions(no_formatting);
} else if let Some(Command::Sessions(Sessions::KillAllSessions { yes })) = opts.command { } else if let Some(Command::Sessions(Sessions::KillAllSessions { yes })) = opts.command {
commands::kill_all_sessions(yes); commands::kill_all_sessions(yes);
} else if let Some(Command::Sessions(Sessions::KillSession { ref target_session })) = } else if let Some(Command::Sessions(Sessions::KillSession { ref target_session })) =
opts.command opts.command
{ {
commands::kill_session(target_session); 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 { } else if let Some(path) = opts.server {
commands::start_server(path, opts.debug); commands::start_server(path, opts.debug);
} else { } else {

View file

@ -1,24 +1,36 @@
use std::collections::HashMap;
use std::os::unix::fs::FileTypeExt; use std::os::unix::fs::FileTypeExt;
use std::time::SystemTime; use std::time::{Duration, SystemTime};
use std::{fs, io, process}; use std::{fs, io, process};
use suggest::Suggest; use suggest::Suggest;
use zellij_utils::{ use zellij_utils::{
anyhow, 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, envs,
humantime::format_duration,
input::layout::Layout,
interprocess::local_socket::LocalSocketStream, interprocess::local_socket::LocalSocketStream,
ipc::{ClientToServerMsg, IpcReceiverWithContext, IpcSenderWithContext, ServerToClientMsg}, ipc::{ClientToServerMsg, IpcReceiverWithContext, IpcSenderWithContext, ServerToClientMsg},
}; };
pub(crate) fn get_sessions() -> Result<Vec<String>, io::ErrorKind> { pub(crate) fn get_sessions() -> Result<Vec<(String, Duration)>, io::ErrorKind> {
match fs::read_dir(&*ZELLIJ_SOCK_DIR) { match fs::read_dir(&*ZELLIJ_SOCK_DIR) {
Ok(files) => { Ok(files) => {
let mut sessions = Vec::new(); let mut sessions = Vec::new();
files.for_each(|file| { files.for_each(|file| {
let file = file.unwrap(); let file = file.unwrap();
let file_name = file.file_name().into_string().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) { if file.file_type().unwrap().is_socket() && assert_socket(&file_name) {
sessions.push(file_name); sessions.push((file_name, duration));
} }
}); });
Ok(sessions) Ok(sessions)
@ -28,6 +40,70 @@ pub(crate) fn get_sessions() -> Result<Vec<String>, 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<Vec<String>> { pub(crate) fn get_sessions_sorted_by_mtime() -> anyhow::Result<Vec<String>> {
match fs::read_dir(&*ZELLIJ_SOCK_DIR) { match fs::read_dir(&*ZELLIJ_SOCK_DIR) {
Ok(files) => { Ok(files) => {
@ -70,15 +146,38 @@ fn assert_socket(name: &str) -> bool {
} }
} }
pub(crate) fn print_sessions(sessions: Vec<String>) { 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()); let curr_session = envs::get_session_name().unwrap_or_else(|_| "".into());
sessions.iter().for_each(|session| { sessions.sort_by(|a, b| a.1.cmp(&b.1));
let suffix = if curr_session == *session { sessions
" (current)" .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 { } else {
"" String::new()
}; };
println!("{}{}", session, suffix); 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);
}
}) })
} }
@ -103,7 +202,7 @@ pub(crate) enum ActiveSession {
pub(crate) fn get_active_session() -> ActiveSession { pub(crate) fn get_active_session() -> ActiveSession {
match get_sessions() { match get_sessions() {
Ok(sessions) if sessions.is_empty() => ActiveSession::None, 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, Ok(_) => ActiveSession::Many,
Err(e) => { Err(e) => {
eprintln!("Error occurred: {:?}", 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() { let exit_code = match get_sessions() {
Ok(sessions) if !sessions.is_empty() => { Ok(running_sessions) => {
print_sessions(sessions); let resurrectable_sessions = get_resurrectable_sessions();
0 let mut all_sessions: HashMap<String, (Duration, bool)> = resurrectable_sessions
}, .iter()
Ok(_) => { .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."); eprintln!("No active zellij sessions found.");
1 1
} else {
print_sessions(
all_sessions
.iter()
.map(|(name, (timestamp, is_dead))| {
(name.clone(), timestamp.clone(), *is_dead)
})
.collect(),
no_formatting,
);
0
}
}, },
Err(e) => { Err(e) => {
eprintln!("Error occurred: {:?}", e); eprintln!("Error occurred: {:?}", e);
@ -154,19 +291,22 @@ pub enum SessionNameMatch {
pub(crate) fn match_session_name(prefix: &str) -> Result<SessionNameMatch, io::ErrorKind> { pub(crate) fn match_session_name(prefix: &str) -> Result<SessionNameMatch, io::ErrorKind> {
let sessions = get_sessions()?; 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())); return Ok(SessionNameMatch::Exact(prefix.to_string()));
} }
Ok({ Ok({
match &filtered_sessions[..] { match &filtered_sessions[..] {
[] => SessionNameMatch::None, [] => SessionNameMatch::None,
[s] => SessionNameMatch::UniquePrefix(s.to_string()), [s] => SessionNameMatch::UniquePrefix(s.0.to_string()),
_ => { _ => SessionNameMatch::AmbiguousPrefix(
SessionNameMatch::AmbiguousPrefix(filtered_sessions.into_iter().cloned().collect()) filtered_sessions.into_iter().map(|s| s.0.clone()).collect(),
}, ),
} }
}) })
} }
@ -179,6 +319,20 @@ pub(crate) fn session_exists(name: &str) -> Result<bool, io::ErrorKind> {
} }
} }
// 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<Layout> {
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) { pub(crate) fn assert_session(name: &str) {
match session_exists(name) { match session_exists(name) {
Ok(result) => { Ok(result) => {
@ -186,7 +340,13 @@ pub(crate) fn assert_session(name: &str) {
return; return;
} else { } else {
println!("No session named {:?} found.", name); 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::<Vec<_>>()
.suggest(name)
{
println!(" help: Did you mean `{}`?", sugg); println!(" help: Did you mean `{}`?", sugg);
} }
} }
@ -198,6 +358,28 @@ pub(crate) fn assert_session(name: &str) {
process::exit(1); 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) { pub(crate) fn assert_session_ne(name: &str) {
if name.trim().is_empty() { if name.trim().is_empty() {
eprintln!("Session name cannot be empty. Please provide a specific session name."); 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) { 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), 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), Err(e) => eprintln!("Error occurred: {:?}", e),
}; };

View file

@ -990,6 +990,125 @@ pub fn detach_and_attach_session() {
assert_snapshot!(last_snapshot); 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] #[test]
#[ignore] #[ignore]
pub fn status_bar_loads_custom_keybindings() { pub fn status_bar_loads_custom_keybindings() {

View file

@ -66,6 +66,10 @@ fn stop_zellij(channel: &mut ssh2::Channel) {
channel.write_all(b"killall -KILL zellij\n").unwrap(); channel.write_all(b"killall -KILL zellij\n").unwrap();
channel.write_all(b"rm -rf /tmp/*\n").unwrap(); // remove temporary artifacts from previous channel.write_all(b"rm -rf /tmp/*\n").unwrap(); // remove temporary artifacts from previous
// tests // 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) { 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 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) { fn start_zellij_in_session(channel: &mut ssh2::Channel, session_name: &str, mirrored: bool) {
stop_zellij(channel); stop_zellij(channel);
channel channel
@ -464,6 +509,84 @@ impl RemoteRunner {
reader_thread, 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) { pub fn kill_running_sessions(win_size: Size) {
let sess = ssh_connect(); let sess = ssh_connect();
let mut channel = sess.channel_session().unwrap(); let mut channel = sess.channel_session().unwrap();

View file

@ -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 │ │────────────────────────────┐
│ │ │ │ │ │
│ │ │ <ENTER> to run, <Ctrl-c> to exit │ │ │
│ └─│ │ │ │
│ │ │ │ │
│ └─ <ENTER> to run, <Ctrl-c> to exit ─────────────────────┘ │ │
│ │ │ │
│ └────────────────────────────────────────────────────────┘ │
│ ││ │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT 
(FLOATING PANES VISIBLE): Press Ctrl+p, <w> to hide.

View file

@ -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 │ │────────────────────────────┐
│ │ │ │ │ │
│ │ │ <ENTER> to run, <Ctrl-c> to exit │ │ │
│ └─│ │ │ │
│ │ │ │ │
│ └─ <ENTER> to run, <Ctrl-c> to exit ─────────────────────┘ │ │
│ │ │ │
│ └────────────────────────────────────────────────────────┘ │
│ ││ │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT 
(FLOATING PANES VISIBLE): Press Ctrl+p, <w> to hide.

View file

@ -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%"
}
}
}
}

View file

@ -117,6 +117,7 @@ fn spawn_server(socket_path: &Path, debug: bool) -> io::Result<()> {
pub enum ClientInfo { pub enum ClientInfo {
Attach(String, Options), Attach(String, Options),
New(String), New(String),
Resurrect(String, Layout),
} }
impl ClientInfo { impl ClientInfo {
@ -124,6 +125,7 @@ impl ClientInfo {
match self { match self {
Self::Attach(ref name, _) => name, Self::Attach(ref name, _) => name,
Self::New(ref name) => name, Self::New(ref name) => name,
Self::Resurrect(ref name, _) => name,
} }
} }
} }
@ -212,7 +214,7 @@ pub fn start_client(
ipc_pipe, ipc_pipe,
) )
}, },
ClientInfo::New(name) => { ClientInfo::New(name) | ClientInfo::Resurrect(name, _) => {
envs::set_session_name(name.clone()); envs::set_session_name(name.clone());
os_input.update_session_name(name); os_input.update_session_name(name);
let ipc_pipe = create_ipc_pipe(); let ipc_pipe = create_ipc_pipe();

View file

@ -2,7 +2,7 @@ use super::{config_yaml_to_config_kdl, layout_yaml_to_layout_kdl};
use std::path::PathBuf; use std::path::PathBuf;
use zellij_utils::{ use zellij_utils::{
cli::CliArgs, 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"; const OLD_CONFIG_NAME: &str = "config.yaml";

View file

@ -1,5 +1,8 @@
use zellij_utils::async_std::task; use zellij_utils::async_std::task;
use zellij_utils::consts::{ZELLIJ_SESSION_INFO_CACHE_DIR, ZELLIJ_SOCK_DIR}; 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::data::SessionInfo;
use zellij_utils::errors::{prelude::*, BackgroundJobContext, ContextType}; use zellij_utils::errors::{prelude::*, BackgroundJobContext, ContextType};
@ -7,7 +10,6 @@ use std::collections::{BTreeMap, HashMap};
use std::fs; use std::fs;
use std::io::Write; use std::io::Write;
use std::os::unix::fs::FileTypeExt; use std::os::unix::fs::FileTypeExt;
use std::path::PathBuf;
use std::sync::{ use std::sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
Arc, Mutex, Arc, Mutex,
@ -25,6 +27,7 @@ pub enum BackgroundJob {
StopPluginLoadingAnimation(u32), // u32 - plugin_id StopPluginLoadingAnimation(u32), // u32 - plugin_id
ReadAllSessionInfosOnMachine, // u32 - plugin_id ReadAllSessionInfosOnMachine, // u32 - plugin_id
ReportSessionInfo(String, SessionInfo), // String - session name ReportSessionInfo(String, SessionInfo), // String - session name
ReportLayoutInfo((String, BTreeMap<String, String>)), // HashMap<file_name, pane_contents>
Exit, Exit,
} }
@ -40,6 +43,7 @@ impl From<&BackgroundJob> for BackgroundJobContext {
BackgroundJobContext::ReadAllSessionInfosOnMachine BackgroundJobContext::ReadAllSessionInfosOnMachine
}, },
BackgroundJob::ReportSessionInfo(..) => BackgroundJobContext::ReportSessionInfo, BackgroundJob::ReportSessionInfo(..) => BackgroundJobContext::ReportSessionInfo,
BackgroundJob::ReportLayoutInfo(..) => BackgroundJobContext::ReportLayoutInfo,
BackgroundJob::Exit => BackgroundJobContext::Exit, BackgroundJob::Exit => BackgroundJobContext::Exit,
} }
} }
@ -55,6 +59,7 @@ pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
let mut loading_plugins: HashMap<u32, Arc<AtomicBool>> = HashMap::new(); // u32 - plugin_id let mut loading_plugins: HashMap<u32, Arc<AtomicBool>> = HashMap::new(); // u32 - plugin_id
let current_session_name = Arc::new(Mutex::new(String::default())); let current_session_name = Arc::new(Mutex::new(String::default()));
let current_session_info = Arc::new(Mutex::new(SessionInfo::default())); let current_session_info = Arc::new(Mutex::new(SessionInfo::default()));
let current_session_layout = Arc::new(Mutex::new((String::new(), BTreeMap::new())));
loop { loop {
let (event, mut err_ctx) = bus.recv().with_context(err_context)?; let (event, mut err_ctx) = bus.recv().with_context(err_context)?;
@ -112,6 +117,9 @@ pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
*current_session_name.lock().unwrap() = session_name; *current_session_name.lock().unwrap() = session_name;
*current_session_info.lock().unwrap() = session_info; *current_session_info.lock().unwrap() = session_info;
}, },
BackgroundJob::ReportLayoutInfo(session_layout) => {
*current_session_layout.lock().unwrap() = session_layout;
},
BackgroundJob::ReadAllSessionInfosOnMachine => { BackgroundJob::ReadAllSessionInfosOnMachine => {
// this job should only be run once and it keeps track of other sessions (as well // 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 // as this one's) infos (metadata mostly) and sends it to the screen which in turn
@ -124,6 +132,7 @@ pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
let senders = bus.senders.clone(); let senders = bus.senders.clone();
let current_session_info = current_session_info.clone(); let current_session_info = current_session_info.clone();
let current_session_name = current_session_name.clone(); let current_session_name = current_session_name.clone();
let current_session_layout = current_session_layout.clone();
async move { async move {
loop { loop {
// write state of current session // write state of current session
@ -131,15 +140,48 @@ pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
// write it to disk // write it to disk
let current_session_name = let current_session_name =
current_session_name.lock().unwrap().to_string(); current_session_name.lock().unwrap().to_string();
let cache_file_name = let metadata_cache_file_name =
session_info_cache_file_name(&current_session_name); session_info_cache_file_name(&current_session_name);
let current_session_info = current_session_info.lock().unwrap().clone(); let current_session_info = current_session_info.lock().unwrap().clone();
let _wrote_file = let (current_session_layout, layout_files_to_write) =
std::fs::create_dir_all(ZELLIJ_SESSION_INFO_CACHE_DIR.as_path()) current_session_layout.lock().unwrap().clone();
.and_then(|_| std::fs::File::create(cache_file_name)) let _wrote_metadata_file = std::fs::create_dir_all(
.and_then(|mut f| { session_info_folder_for_session(&current_session_name).as_path(),
write!(f, "{}", current_session_info.to_string()) )
.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(&current_session_name);
let _wrote_layout_file = std::fs::create_dir_all(
session_info_folder_for_session(&current_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(&current_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 // start a background job (if not already running) that'll periodically read this and other
// sesion infos and report back // sesion infos and report back
@ -161,8 +203,8 @@ pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
} }
for session_name in other_session_names { for session_name in other_session_names {
let session_cache_file_name = ZELLIJ_SESSION_INFO_CACHE_DIR let session_cache_file_name =
.join(format!("{}.kdl", session_name)); session_info_cache_file_name(&session_name);
if let Ok(raw_session_info) = if let Ok(raw_session_info) =
fs::read_to_string(&session_cache_file_name) fs::read_to_string(&session_cache_file_name)
{ {
@ -177,6 +219,7 @@ pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
let _ = senders.send_to_screen(ScreenInstruction::UpdateSessionInfos( let _ = senders.send_to_screen(ScreenInstruction::UpdateSessionInfos(
session_infos_on_machine, session_infos_on_machine,
)); ));
let _ = senders.send_to_screen(ScreenInstruction::DumpLayoutToHd);
task::sleep(std::time::Duration::from_millis(SESSION_READ_DURATION)) task::sleep(std::time::Duration::from_millis(SESSION_READ_DURATION))
.await; .await;
} }
@ -216,7 +259,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))
}

View file

@ -10,6 +10,7 @@ mod pty;
mod pty_writer; mod pty_writer;
mod route; mod route;
mod screen; mod screen;
mod session_layout_metadata;
mod terminal_bytes; mod terminal_bytes;
mod thread_bus; mod thread_bus;
mod ui; mod ui;
@ -43,6 +44,7 @@ use zellij_utils::{
consts::{DEFAULT_SCROLL_BUFFER_SIZE, SCROLL_BUFFER_SIZE}, consts::{DEFAULT_SCROLL_BUFFER_SIZE, SCROLL_BUFFER_SIZE},
data::{ConnectToSession, Event, PluginCapabilities}, data::{ConnectToSession, Event, PluginCapabilities},
errors::{prelude::*, ContextType, ErrorInstruction, FatalError, ServerContext}, errors::{prelude::*, ContextType, ErrorInstruction, FatalError, ServerContext},
home::get_default_data_dir,
input::{ input::{
command::{RunCommand, TerminalAction}, command::{RunCommand, TerminalAction},
get_mode_info, get_mode_info,
@ -51,7 +53,6 @@ use zellij_utils::{
plugins::PluginsConfig, plugins::PluginsConfig,
}, },
ipc::{ClientAttributes, ExitReason, ServerToClientMsg}, ipc::{ClientAttributes, ExitReason, ServerToClientMsg},
setup::get_default_data_dir,
}; };
pub type ClientId = u16; pub type ClientId = u16;
@ -780,7 +781,7 @@ fn init_session(
config_options.scrollback_editor.clone(), config_options.scrollback_editor.clone(),
); );
move || pty_thread_main(pty, layout).fatal() move || pty_thread_main(pty, layout.clone()).fatal()
}) })
.unwrap(); .unwrap();
@ -801,6 +802,7 @@ fn init_session(
let client_attributes_clone = client_attributes.clone(); let client_attributes_clone = client_attributes.clone();
let debug = opts.debug; let debug = opts.debug;
let layout = layout.clone();
move || { move || {
screen_thread_main( screen_thread_main(
screen_bus, screen_bus,
@ -808,6 +810,7 @@ fn init_session(
client_attributes_clone, client_attributes_clone,
config_options, config_options,
debug, debug,
layout,
) )
.fatal(); .fatal();
} }

View file

@ -504,6 +504,14 @@ pub trait ServerOsApi: Send + Sync {
fn load_palette(&self) -> Palette; fn load_palette(&self) -> Palette;
/// Returns the current working directory for a given pid /// Returns the current working directory for a given pid
fn get_cwd(&self, pid: Pid) -> Option<PathBuf>; fn get_cwd(&self, pid: Pid) -> Option<PathBuf>;
/// Returns the current working directory for multiple pids
fn get_cwds(&self, _pids: Vec<Pid>) -> HashMap<Pid, PathBuf> {
HashMap::new()
}
/// Get a list of all running commands by their parent process id
fn get_all_cmds_by_ppid(&self) -> HashMap<String, Vec<String>> {
HashMap::new()
}
/// Writes the given buffer to a string /// Writes the given buffer to a string
fn write_to_file(&mut self, buf: String, file: Option<String>) -> Result<()>; fn write_to_file(&mut self, buf: String, file: Option<String>) -> Result<()>;
@ -756,6 +764,50 @@ impl ServerOsApi for ServerOsInputOutput {
None None
} }
fn get_cwds(&self, pids: Vec<Pid>) -> HashMap<Pid, PathBuf> {
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<String, Vec<String>> {
// 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<String> = 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<String>) -> Result<()> { fn write_to_file(&mut self, buf: String, name: Option<String>) -> Result<()> {
let err_context = || "failed to write to file".to_string(); let err_context = || "failed to write to file".to_string();

View file

@ -79,11 +79,47 @@ fn write_changed_styles(
Ok(()) Ok(())
} }
fn serialize_chunks_with_newlines(
character_chunks: Vec<CharacterChunk>,
_sixel_chunks: Option<&Vec<SixelImageChunk>>, // TODO: fix this sometime
link_handler: Option<&mut Rc<RefCell<LinkHandler>>>,
) -> Result<String> {
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( fn serialize_chunks(
character_chunks: Vec<CharacterChunk>, character_chunks: Vec<CharacterChunk>,
sixel_chunks: Option<&Vec<SixelImageChunk>>, sixel_chunks: Option<&Vec<SixelImageChunk>>,
link_handler: Option<&mut Rc<RefCell<LinkHandler>>>, link_handler: Option<&mut Rc<RefCell<LinkHandler>>>,
sixel_image_store: &mut SixelImageStore, sixel_image_store: Option<&mut SixelImageStore>,
) -> Result<String> { ) -> Result<String> {
let err_context = || "failed to serialize input chunks".to_string(); let err_context = || "failed to serialize input chunks".to_string();
@ -116,6 +152,7 @@ fn serialize_chunks(
} }
character_styles.clear(); character_styles.clear();
} }
if let Some(sixel_image_store) = sixel_image_store {
if let Some(sixel_chunks) = sixel_chunks { if let Some(sixel_chunks) = sixel_chunks {
for sixel_chunk in sixel_chunks { for sixel_chunk in sixel_chunks {
let serialized_sixel_image = sixel_image_store.serialize_image( let serialized_sixel_image = sixel_image_store.serialize_image(
@ -133,6 +170,7 @@ fn serialize_chunks(
} }
} }
} }
}
if let Some(ref sixel_vte) = sixel_vte { if let Some(ref sixel_vte) = sixel_vte {
// we do this at the end because of the implied z-index, // we do this at the end because of the implied z-index,
// images should be above text unless the text was explicitly inserted after them (the // images should be above text unless the text was explicitly inserted after them (the
@ -378,7 +416,7 @@ impl Output {
client_character_chunks, client_character_chunks,
self.sixel_chunks.get(&client_id), self.sixel_chunks.get(&client_id),
self.link_handler.as_mut(), self.link_handler.as_mut(),
&mut self.sixel_image_store.borrow_mut(), Some(&mut self.sixel_image_store.borrow_mut()),
) )
.with_context(err_context)?, .with_context(err_context)?,
); // TODO: less allocations? ); // TODO: less allocations?
@ -865,6 +903,18 @@ impl OutputBuffer {
self.changed_lines.clear(); self.changed_lines.clear();
self.should_update_all_lines = false; self.should_update_all_lines = false;
} }
pub fn serialize(&self, viewport: &[Row]) -> Result<String> {
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( pub fn changed_chunks_in_viewport(
&self, &self,
viewport: &[Row], viewport: &[Row],

View file

@ -157,7 +157,7 @@ impl FloatingPanes {
// move clients from the previously active pane to the new pane we just inserted // 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.move_clients_between_panes(pane_id, with_pane_id);
self.set_pane_frames(); let _ = self.set_pane_frames();
removed_pane removed_pane
} }
pub fn remove_pane(&mut self, pane_id: PaneId) -> Option<Box<dyn Pane>> { pub fn remove_pane(&mut self, pane_id: PaneId) -> Option<Box<dyn Pane>> {
@ -603,7 +603,7 @@ impl FloatingPanes {
pub fn move_active_pane( pub fn move_active_pane(
&mut self, &mut self,
search_backwards: bool, search_backwards: bool,
os_api: &mut Box<dyn ServerOsApi>, _os_api: &mut Box<dyn ServerOsApi>,
client_id: ClientId, client_id: ClientId,
) { ) {
let active_pane_id = self.get_active_pane_id(client_id).unwrap(); let active_pane_id = self.get_active_pane_id(client_id).unwrap();

View file

@ -1034,6 +1034,28 @@ impl Grid {
(changed_character_chunks, changed_sixel_image_chunks) (changed_character_chunks, changed_sixel_image_chunks)
} }
pub fn serialize(&self, scrollback_lines_to_serialize: Option<usize>) -> Option<String> {
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( pub fn render(
&mut self, &mut self,
content_x: usize, content_x: usize,

View file

@ -616,6 +616,13 @@ impl Pane for PluginPane {
self.pane_name.to_owned() self.pane_name.to_owned()
} }
} }
fn custom_title(&self) -> Option<String> {
if self.pane_name.is_empty() {
None
} else {
Some(self.pane_name.clone())
}
}
fn rename(&mut self, buf: Vec<u8>) { fn rename(&mut self, buf: Vec<u8>) {
self.pane_name = String::from_utf8_lossy(&buf).to_string(); self.pane_name = String::from_utf8_lossy(&buf).to_string();
self.set_should_render(true); self.set_should_render(true);

View file

@ -732,6 +732,13 @@ impl Pane for TerminalPane {
self.pane_name.to_owned() self.pane_name.to_owned()
} }
} }
fn custom_title(&self) -> Option<String> {
if self.pane_name.is_empty() {
None
} else {
Some(self.pane_name.clone())
}
}
fn exit_status(&self) -> Option<i32> { fn exit_status(&self) -> Option<i32> {
self.is_held self.is_held
.as_ref() .as_ref()
@ -750,6 +757,9 @@ impl Pane for TerminalPane {
self.pane_name = String::from_utf8_lossy(&buf).to_string(); self.pane_name = String::from_utf8_lossy(&buf).to_string();
self.set_should_render(true); self.set_should_render(true);
} }
fn serialize(&self, scrollback_lines_to_serialize: Option<usize>) -> Option<String> {
self.grid.serialize(scrollback_lines_to_serialize)
}
} }
impl TerminalPane { impl TerminalPane {

View file

@ -16,6 +16,7 @@ use wasmer::Store;
use crate::panes::PaneId; use crate::panes::PaneId;
use crate::screen::ScreenInstruction; use crate::screen::ScreenInstruction;
use crate::session_layout_metadata::SessionLayoutMetadata;
use crate::{pty::PtyInstruction, thread_bus::Bus, ClientId, ServerInstruction}; use crate::{pty::PtyInstruction, thread_bus::Bus, ClientId, ServerInstruction};
use wasm_bridge::WasmBridge; use wasm_bridge::WasmBridge;
@ -95,6 +96,8 @@ pub enum PluginInstruction {
PermissionStatus, PermissionStatus,
Option<PathBuf>, Option<PathBuf>,
), ),
DumpLayout(SessionLayoutMetadata, ClientId),
LogLayoutToHd(SessionLayoutMetadata),
Exit, Exit,
} }
@ -124,6 +127,8 @@ impl From<&PluginInstruction> for PluginContext {
PluginInstruction::PermissionRequestResult(..) => { PluginInstruction::PermissionRequestResult(..) => {
PluginContext::PermissionRequestResult PluginContext::PermissionRequestResult
}, },
PluginInstruction::DumpLayout(..) => PluginContext::DumpLayout,
PluginInstruction::LogLayoutToHd(..) => PluginContext::LogLayoutToHd,
} }
} }
} }
@ -356,6 +361,20 @@ pub(crate) fn plugin_thread_main(
)]; )];
wasm_bridge.update_plugins(updates, shutdown_send.clone())?; 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 => { PluginInstruction::Exit => {
break; break;
}, },
@ -388,6 +407,24 @@ pub(crate) fn plugin_thread_main(
.context("failed to cleanup plugin data directory") .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<u32, RunPlugin> = 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); const EXIT_TIMEOUT: Duration = Duration::from_secs(3);
#[path = "./unit/plugin_tests.rs"] #[path = "./unit/plugin_tests.rs"]

View file

@ -15,7 +15,7 @@ use zellij_utils::{
data::EventType, data::EventType,
data::PluginCapabilities, data::PluginCapabilities,
input::command::TerminalAction, input::command::TerminalAction,
input::layout::{Layout, RunPluginLocation}, input::layout::{Layout, RunPlugin, RunPluginLocation},
input::plugins::PluginConfig, input::plugins::PluginConfig,
ipc::ClientAttributes, ipc::ClientAttributes,
}; };
@ -185,6 +185,28 @@ impl PluginMap {
(running_plugin, subscriptions, running_workers), (running_plugin, subscriptions, running_workers),
); );
} }
pub fn run_plugin_of_plugin_id(&self, plugin_id: PluginId) -> Option<RunPlugin> {
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<EventType>; pub type Subscriptions = HashSet<EventType>;

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/plugins/./unit/plugin_tests.rs source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 735 assertion_line: 1002
expression: "format!(\"{:#?}\", second_new_tab_event)" expression: "format!(\"{:#?}\", second_new_tab_event)"
--- ---
Some( Some(
@ -25,6 +25,8 @@ Some(
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -39,6 +41,8 @@ Some(
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -50,6 +54,8 @@ Some(
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
), ),
[], [],

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/plugins/./unit/plugin_tests.rs source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 734 assertion_line: 1001
expression: "format!(\"{:#?}\", first_new_tab_event)" expression: "format!(\"{:#?}\", first_new_tab_event)"
--- ---
Some( Some(
@ -25,6 +25,8 @@ Some(
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -39,6 +41,8 @@ Some(
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -50,6 +54,8 @@ Some(
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
), ),
[], [],

View file

@ -520,7 +520,7 @@ impl WasmBridge {
let mut applied_plugin_paths = HashSet::new(); let mut applied_plugin_paths = HashSet::new();
for plugin_id in plugin_ids { for plugin_id in plugin_ids {
self.apply_cached_events_and_resizes_for_plugin(plugin_id, shutdown_sender.clone())?; 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()); applied_plugin_paths.insert(run_plugin.clone());
} }
self.loading_plugins self.loading_plugins
@ -551,12 +551,18 @@ impl WasmBridge {
watcher.stop_nonblocking(); 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 self.loading_plugins
.iter() .iter()
.find(|((p_id, _run_plugin), _)| p_id == &plugin_id) .find(|((p_id, _run_plugin), _)| p_id == &plugin_id)
.map(|((_p_id, run_plugin), _)| run_plugin) .map(|((_p_id, run_plugin), _)| run_plugin)
} }
pub fn run_plugin_of_plugin_id(&self, plugin_id: PluginId) -> Option<RunPlugin> {
self.plugin_map
.lock()
.unwrap()
.run_plugin_of_plugin_id(plugin_id)
}
fn apply_cached_events_and_resizes_for_plugin( fn apply_cached_events_and_resizes_for_plugin(
&mut self, &mut self,
plugin_id: PluginId, plugin_id: PluginId,

View file

@ -1,8 +1,10 @@
use crate::background_jobs::BackgroundJob;
use crate::terminal_bytes::TerminalBytes; use crate::terminal_bytes::TerminalBytes;
use crate::{ use crate::{
panes::PaneId, panes::PaneId,
plugins::PluginInstruction, plugins::PluginInstruction,
screen::ScreenInstruction, screen::ScreenInstruction,
session_layout_metadata::SessionLayoutMetadata,
thread_bus::{Bus, ThreadSenders}, thread_bus::{Bus, ThreadSenders},
ClientId, ServerInstruction, ClientId, ServerInstruction,
}; };
@ -20,6 +22,7 @@ use zellij_utils::{
TiledPaneLayout, TiledPaneLayout,
}, },
}, },
session_serialization,
}; };
pub type VteBytes = Vec<u8>; pub type VteBytes = Vec<u8>;
@ -68,6 +71,8 @@ pub enum PtyInstruction {
Option<String>, Option<String>,
ClientTabIndexOrPaneId, ClientTabIndexOrPaneId,
), // String is an optional pane name ), // String is an optional pane name
DumpLayout(SessionLayoutMetadata, ClientId),
LogLayoutToHd(SessionLayoutMetadata),
Exit, Exit,
} }
@ -85,6 +90,8 @@ impl From<&PtyInstruction> for PtyContext {
PtyInstruction::NewTab(..) => PtyContext::NewTab, PtyInstruction::NewTab(..) => PtyContext::NewTab,
PtyInstruction::ReRunCommandInPane(..) => PtyContext::ReRunCommandInPane, PtyInstruction::ReRunCommandInPane(..) => PtyContext::ReRunCommandInPane,
PtyInstruction::SpawnInPlaceTerminal(..) => PtyContext::SpawnInPlaceTerminal, PtyInstruction::SpawnInPlaceTerminal(..) => PtyContext::SpawnInPlaceTerminal,
PtyInstruction::DumpLayout(..) => PtyContext::DumpLayout,
PtyInstruction::LogLayoutToHd(..) => PtyContext::LogLayoutToHd,
PtyInstruction::Exit => PtyContext::Exit, PtyInstruction::Exit => PtyContext::Exit,
} }
} }
@ -121,12 +128,26 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
), ),
_ => (false, None, name), _ => (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 match pty
.spawn_terminal(terminal_action, client_or_tab_index) .spawn_terminal(terminal_action, client_or_tab_index)
.with_context(err_context) .with_context(err_context)
{ {
Ok((pid, starts_held)) => { 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 pty.bus
.senders .senders
.send_to_screen(ScreenInstruction::NewPane( .send_to_screen(ScreenInstruction::NewPane(
@ -134,6 +155,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
pane_title, pane_title,
should_float, should_float,
hold_for_command, hold_for_command,
invoked_with,
client_or_tab_index, client_or_tab_index,
)) ))
.with_context(err_context)?; .with_context(err_context)?;
@ -149,6 +171,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
pane_title, pane_title,
should_float, should_float,
hold_for_command, hold_for_command,
invoked_with,
client_or_tab_index, client_or_tab_index,
)) ))
.with_context(err_context)?; .with_context(err_context)?;
@ -190,6 +213,16 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
), ),
_ => (false, None, name), _ => (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 match pty
.spawn_terminal(terminal_action, client_id_tab_index_or_pane_id) .spawn_terminal(terminal_action, client_id_tab_index_or_pane_id)
.with_context(err_context) .with_context(err_context)
@ -202,6 +235,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
PaneId::Terminal(pid), PaneId::Terminal(pid),
hold_for_command, hold_for_command,
pane_title, pane_title,
invoked_with,
client_id_tab_index_or_pane_id, client_id_tab_index_or_pane_id,
)) ))
.with_context(err_context)?; .with_context(err_context)?;
@ -216,6 +250,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
PaneId::Terminal(*terminal_id), PaneId::Terminal(*terminal_id),
hold_for_command, hold_for_command,
pane_title, pane_title,
invoked_with,
client_id_tab_index_or_pane_id, client_id_tab_index_or_pane_id,
)) ))
.with_context(err_context)?; .with_context(err_context)?;
@ -497,6 +532,27 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> 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, PtyInstruction::Exit => break,
} }
} }
@ -1105,6 +1161,51 @@ impl Pty {
_ => Err(anyhow!("cannot respawn plugin panes")).with_context(err_context), _ => 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<u32, Vec<String>> = HashMap::new();
let mut terminal_ids_to_cwds: HashMap<u32, PathBuf> = 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 { impl Drop for Pty {

View file

@ -173,6 +173,15 @@ pub(crate) fn route_action(
.send_to_screen(ScreenInstruction::DumpScreen(val, client_id, full)) .send_to_screen(ScreenInstruction::DumpScreen(val, client_id, full))
.with_context(err_context)?; .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 => { Action::EditScrollback => {
senders senders
.send_to_screen(ScreenInstruction::EditScrollback(client_id)) .send_to_screen(ScreenInstruction::EditScrollback(client_id))

View file

@ -26,6 +26,7 @@ use crate::background_jobs::BackgroundJob;
use crate::os_input_output::ResizeCache; use crate::os_input_output::ResizeCache;
use crate::panes::alacritty_functions::xparse_color; use crate::panes::alacritty_functions::xparse_color;
use crate::panes::terminal_character::AnsiCode; use crate::panes::terminal_character::AnsiCode;
use crate::session_layout_metadata::{PaneLayoutMetadata, SessionLayoutMetadata};
use crate::{ use crate::{
output::Output, output::Output,
@ -141,6 +142,7 @@ pub enum ScreenInstruction {
Option<InitialTitle>, Option<InitialTitle>,
Option<ShouldFloat>, Option<ShouldFloat>,
HoldForCommand, HoldForCommand,
Option<Run>, // invoked with
ClientTabIndexOrPaneId, ClientTabIndexOrPaneId,
), ),
OpenInPlaceEditor(PaneId, ClientId), OpenInPlaceEditor(PaneId, ClientId),
@ -168,6 +170,8 @@ pub enum ScreenInstruction {
Exit, Exit,
ClearScreen(ClientId), ClearScreen(ClientId),
DumpScreen(String, ClientId, bool), DumpScreen(String, ClientId, bool),
DumpLayout(Option<PathBuf>, ClientId), // PathBuf is the default configured
// shell
EditScrollback(ClientId), EditScrollback(ClientId),
ScrollUp(ClientId), ScrollUp(ClientId),
ScrollUpAt(Position, ClientId), ScrollUpAt(Position, ClientId),
@ -302,8 +306,10 @@ pub enum ScreenInstruction {
PaneId, PaneId,
HoldForCommand, HoldForCommand,
Option<InitialTitle>, Option<InitialTitle>,
Option<Run>,
ClientTabIndexOrPaneId, ClientTabIndexOrPaneId,
), ),
DumpLayoutToHd,
} }
impl From<&ScreenInstruction> for ScreenContext { impl From<&ScreenInstruction> for ScreenContext {
@ -367,6 +373,7 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::Exit => ScreenContext::Exit, ScreenInstruction::Exit => ScreenContext::Exit,
ScreenInstruction::ClearScreen(..) => ScreenContext::ClearScreen, ScreenInstruction::ClearScreen(..) => ScreenContext::ClearScreen,
ScreenInstruction::DumpScreen(..) => ScreenContext::DumpScreen, ScreenInstruction::DumpScreen(..) => ScreenContext::DumpScreen,
ScreenInstruction::DumpLayout(..) => ScreenContext::DumpLayout,
ScreenInstruction::EditScrollback(..) => ScreenContext::EditScrollback, ScreenInstruction::EditScrollback(..) => ScreenContext::EditScrollback,
ScreenInstruction::ScrollUp(..) => ScreenContext::ScrollUp, ScreenInstruction::ScrollUp(..) => ScreenContext::ScrollUp,
ScreenInstruction::ScrollDown(..) => ScreenContext::ScrollDown, ScreenInstruction::ScrollDown(..) => ScreenContext::ScrollDown,
@ -480,6 +487,7 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::UpdateSessionInfos(..) => ScreenContext::UpdateSessionInfos, ScreenInstruction::UpdateSessionInfos(..) => ScreenContext::UpdateSessionInfos,
ScreenInstruction::ReplacePane(..) => ScreenContext::ReplacePane, ScreenInstruction::ReplacePane(..) => ScreenContext::ReplacePane,
ScreenInstruction::NewInPlacePluginPane(..) => ScreenContext::NewInPlacePluginPane, ScreenInstruction::NewInPlacePluginPane(..) => ScreenContext::NewInPlacePluginPane,
ScreenInstruction::DumpLayoutToHd => ScreenContext::DumpLayoutToHd,
} }
} }
} }
@ -541,12 +549,17 @@ pub(crate) struct Screen {
style: Style, style: Style,
draw_pane_frames: bool, draw_pane_frames: bool,
auto_layout: bool, auto_layout: bool,
session_serialization: bool,
serialize_pane_viewport: bool,
scrollback_lines_to_serialize: Option<usize>,
session_is_mirrored: bool, session_is_mirrored: bool,
copy_options: CopyOptions, copy_options: CopyOptions,
debug: bool, debug: bool,
session_name: String, session_name: String,
session_infos_on_machine: BTreeMap<String, SessionInfo>, // String is the session name, can session_infos_on_machine: BTreeMap<String, SessionInfo>, // String is the session name, can
// also be this session // also be this session
default_layout: Box<Layout>,
default_shell: Option<PathBuf>,
} }
impl Screen { impl Screen {
@ -561,6 +574,11 @@ impl Screen {
session_is_mirrored: bool, session_is_mirrored: bool,
copy_options: CopyOptions, copy_options: CopyOptions,
debug: bool, debug: bool,
default_layout: Box<Layout>,
default_shell: Option<PathBuf>,
session_serialization: bool,
serialize_pane_viewport: bool,
scrollback_lines_to_serialize: Option<usize>,
) -> Self { ) -> Self {
let session_name = mode_info.session_name.clone().unwrap_or_default(); let session_name = mode_info.session_name.clone().unwrap_or_default();
let session_info = SessionInfo::new(session_name.clone()); let session_info = SessionInfo::new(session_name.clone());
@ -590,6 +608,11 @@ impl Screen {
debug, debug,
session_name, session_name,
session_infos_on_machine, session_infos_on_machine,
default_layout,
default_shell,
session_serialization,
serialize_pane_viewport,
scrollback_lines_to_serialize,
} }
} }
@ -1369,10 +1392,20 @@ impl Screen {
session_info, session_info,
)) ))
.with_context(err_context)?; .with_context(err_context)?;
self.bus self.bus
.senders .senders
.send_to_background_jobs(BackgroundJob::ReadAllSessionInfosOnMachine) .send_to_background_jobs(BackgroundJob::ReadAllSessionInfosOnMachine)
.with_context(err_context)?; .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(()) Ok(())
} }
@ -1838,15 +1871,15 @@ impl Screen {
&mut self, &mut self,
new_pane_id: PaneId, new_pane_id: PaneId,
hold_for_command: HoldForCommand, hold_for_command: HoldForCommand,
run_plugin: Option<Run>, run: Option<Run>,
pane_title: Option<InitialTitle>, pane_title: Option<InitialTitle>,
client_id_tab_index_or_pane_id: ClientTabIndexOrPaneId, client_id_tab_index_or_pane_id: ClientTabIndexOrPaneId,
) -> Result<()> { ) -> Result<()> {
let err_context = || format!("failed to replace pane"); let err_context = || format!("failed to replace pane");
let suppress_pane = |tab: &mut Tab, pane_id: PaneId, new_pane_id: PaneId| { 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 { 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 { if let Some(hold_for_command) = hold_for_command {
let is_first_run = true; let is_first_run = true;
@ -1867,7 +1900,7 @@ impl Screen {
); );
}, },
} }
}) });
}, },
ClientTabIndexOrPaneId::PaneId(pane_id) => { ClientTabIndexOrPaneId::PaneId(pane_id) => {
let tab_index = self let tab_index = self
@ -1885,7 +1918,7 @@ impl Screen {
}, },
}; };
}, },
ClientTabIndexOrPaneId::TabIndex(tab_index) => { ClientTabIndexOrPaneId::TabIndex(_tab_index) => {
log::error!("Cannot replace pane with tab index"); log::error!("Cannot replace pane with tab index");
}, },
} }
@ -1897,6 +1930,96 @@ impl Screen {
.send_to_server(ServerInstruction::UnblockInputThread) .send_to_server(ServerInstruction::UnblockInputThread)
.context("failed to unblock input") .context("failed to unblock input")
} }
fn get_layout_metadata(&self, default_shell: Option<PathBuf>) -> 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<PaneLayoutMetadata> = 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<PaneLayoutMetadata> = 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 // The box is here in order to make the
@ -1908,11 +2031,16 @@ pub(crate) fn screen_thread_main(
client_attributes: ClientAttributes, client_attributes: ClientAttributes,
config_options: Box<Options>, config_options: Box<Options>,
debug: bool, debug: bool,
default_layout: Box<Layout>,
) -> Result<()> { ) -> Result<()> {
let capabilities = config_options.simplified_ui; let capabilities = config_options.simplified_ui;
let draw_pane_frames = config_options.pane_frames.unwrap_or(true); let draw_pane_frames = config_options.pane_frames.unwrap_or(true);
let auto_layout = config_options.auto_layout.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 session_is_mirrored = config_options.mirror_session.unwrap_or(false);
let default_shell = config_options.default_shell;
let copy_options = CopyOptions::new( let copy_options = CopyOptions::new(
config_options.copy_command, config_options.copy_command,
config_options.copy_clipboard.unwrap_or_default(), config_options.copy_clipboard.unwrap_or_default(),
@ -1936,6 +2064,11 @@ pub(crate) fn screen_thread_main(
session_is_mirrored, session_is_mirrored,
copy_options, copy_options,
debug, debug,
default_layout,
default_shell,
session_serialization,
serialize_pane_viewport,
scrollback_lines_to_serialize,
); );
let mut pending_tab_ids: HashSet<usize> = HashSet::new(); let mut pending_tab_ids: HashSet<usize> = HashSet::new();
@ -1984,6 +2117,7 @@ pub(crate) fn screen_thread_main(
initial_pane_title, initial_pane_title,
should_float, should_float,
hold_for_command, hold_for_command,
invoked_with,
client_or_tab_index, client_or_tab_index,
) => { ) => {
match client_or_tab_index { match client_or_tab_index {
@ -1992,7 +2126,7 @@ pub(crate) fn screen_thread_main(
tab.new_pane(pid, tab.new_pane(pid,
initial_pane_title, initial_pane_title,
should_float, should_float,
None, invoked_with,
Some(client_id) Some(client_id)
) )
}, ?); }, ?);
@ -2016,7 +2150,7 @@ pub(crate) fn screen_thread_main(
pid, pid,
initial_pane_title, initial_pane_title,
should_float, should_float,
None, invoked_with,
None, None,
)?; )?;
if let Some(hold_for_command) = hold_for_command { if let Some(hold_for_command) = hold_for_command {
@ -2027,7 +2161,7 @@ pub(crate) fn screen_thread_main(
log::error!("Tab index not found: {:?}", tab_index); 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??"); log::error!("cannot open a pane with a pane id??");
}, },
}; };
@ -2261,6 +2395,18 @@ pub(crate) fn screen_thread_main(
screen.render()?; screen.render()?;
screen.unblock_input()?; 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) => { ScreenInstruction::EditScrollback(client_id) => {
active_tab_and_connected_client_id!( active_tab_and_connected_client_id!(
screen, screen,
@ -3275,13 +3421,13 @@ pub(crate) fn screen_thread_main(
new_pane_id, new_pane_id,
hold_for_command, hold_for_command,
pane_title, pane_title,
invoked_with,
client_id_tab_index_or_pane_id, client_id_tab_index_or_pane_id,
) => { ) => {
let err_context = || format!("Failed to replace pane");
screen.replace_pane( screen.replace_pane(
new_pane_id, new_pane_id,
hold_for_command, hold_for_command,
None, invoked_with,
pane_title, pane_title,
client_id_tab_index_or_pane_id, client_id_tab_index_or_pane_id,
)?; )?;
@ -3291,6 +3437,11 @@ pub(crate) fn screen_thread_main(
screen.render()?; screen.render()?;
}, },
ScreenInstruction::DumpLayoutToHd => {
if screen.session_serialization {
screen.dump_layout_to_hd()?;
}
},
} }
} }
Ok(()) Ok(())

View file

@ -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<Layout>,
global_cwd: Option<PathBuf>,
pub default_shell: Option<PathBuf>,
tabs: Vec<TabLayoutMetadata>,
}
impl SessionLayoutMetadata {
pub fn new(default_layout: Box<Layout>) -> 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<String>,
) -> 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<PaneLayoutMetadata>,
floating_panes: Vec<PaneLayoutMetadata>,
) {
self.tabs.push(TabLayoutMetadata {
name: Some(name),
is_focused,
hide_floating_panes,
tiled_panes,
floating_panes,
})
}
pub fn all_terminal_ids(&self) -> Vec<u32> {
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<u32> {
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<u32, Vec<String>>,
) {
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<String> = 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<u32, PathBuf>) {
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<u32, RunPlugin>) {
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<GlobalLayoutManifest> 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<TabLayoutManifest> 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<PaneLayoutManifest> 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<String>,
tiled_panes: Vec<PaneLayoutMetadata>,
floating_panes: Vec<PaneLayoutMetadata>,
is_focused: bool,
hide_floating_panes: bool,
}
#[derive(Debug, Clone)]
pub struct PaneLayoutMetadata {
id: PaneId,
geom: PaneGeom,
run: Option<Run>,
cwd: Option<PathBuf>,
is_borderless: bool,
title: Option<String>,
is_focused: bool,
pane_contents: Option<String>,
}
impl PaneLayoutMetadata {
pub fn new(
id: PaneId,
geom: PaneGeom,
is_borderless: bool,
run: Option<Run>,
title: Option<String>,
is_focused: bool,
pane_contents: Option<String>,
) -> Self {
PaneLayoutMetadata {
id,
geom,
run,
cwd: None,
is_borderless,
title,
is_focused,
pane_contents,
}
}
}

View file

@ -101,8 +101,9 @@ impl<'a> LayoutApplier<'a> {
mut new_plugin_ids: HashMap<(RunPluginLocation, PluginUserConfiguration), Vec<u32>>, mut new_plugin_ids: HashMap<(RunPluginLocation, PluginUserConfiguration), Vec<u32>>,
client_id: ClientId, client_id: ClientId,
) -> Result<bool> { ) -> Result<bool> {
// true => layout has floating panes // true => should_show_floating_panes
let layout_name = layout.name.clone(); 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)?; 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( let layout_has_floating_panes = self.apply_floating_panes_layout(
floating_panes_layout, floating_panes_layout,
@ -110,7 +111,8 @@ impl<'a> LayoutApplier<'a> {
&mut new_plugin_ids, &mut new_plugin_ids,
layout_name, 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( pub fn apply_tiled_panes_layout_to_existing_panes(
&mut self, &mut self,
@ -255,6 +257,11 @@ impl<'a> LayoutApplier<'a> {
layout.run.clone(), layout.run.clone(),
self.debug, 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); new_plugin.set_borderless(layout.borderless);
if let Some(exclude_from_sync) = layout.exclude_from_sync { if let Some(exclude_from_sync) = layout.exclude_from_sync {
new_plugin.set_exclude_from_sync(exclude_from_sync); new_plugin.set_exclude_from_sync(exclude_from_sync);
@ -286,6 +293,10 @@ impl<'a> LayoutApplier<'a> {
layout.run.clone(), layout.run.clone(),
self.debug, 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); new_pane.set_borderless(layout.borderless);
if let Some(exclude_from_sync) = layout.exclude_from_sync { if let Some(exclude_from_sync) = layout.exclude_from_sync {
new_pane.set_exclude_from_sync(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(), floating_pane_layout.run.clone(),
self.debug, 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_borderless(false);
new_pane.set_content_offset(Offset::frame(1)); new_pane.set_content_offset(Offset::frame(1));
resize_pty!( resize_pty!(
@ -406,6 +421,10 @@ impl<'a> LayoutApplier<'a> {
floating_pane_layout.run.clone(), floating_pane_layout.run.clone(),
self.debug, 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_borderless(false);
new_pane.set_content_offset(Offset::frame(1)); new_pane.set_content_offset(Offset::frame(1));
if let Some(held_command) = hold_for_command { if let Some(held_command) = hold_for_command {

View file

@ -147,7 +147,7 @@ pub(crate) struct Tab {
pub prev_name: String, pub prev_name: String,
tiled_panes: TiledPanes, tiled_panes: TiledPanes,
floating_panes: FloatingPanes, floating_panes: FloatingPanes,
suppressed_panes: HashMap<PaneId, Box<dyn Pane>>, suppressed_panes: HashMap<PaneId, (bool, Box<dyn Pane>)>, // bool => is scrollback editor
max_panes: Option<usize>, max_panes: Option<usize>,
viewport: Rc<RefCell<Viewport>>, // includes all non-UI panes viewport: Rc<RefCell<Viewport>>, // includes all non-UI panes
display_area: Rc<RefCell<Size>>, // includes all panes (including eg. the status bar and tab bar in the default layout) display_area: Rc<RefCell<Size>>, // includes all panes (including eg. the status bar and tab bar in the default layout)
@ -460,6 +460,7 @@ pub trait Pane {
fn start_loading_indication(&mut self, _loading_indication: LoadingIndication) {} // only relevant for plugins fn start_loading_indication(&mut self, _loading_indication: LoadingIndication) {} // only relevant for plugins
fn progress_animation_offset(&mut self) {} // only relevant for plugins fn progress_animation_offset(&mut self) {} // only relevant for plugins
fn current_title(&self) -> String; fn current_title(&self) -> String;
fn custom_title(&self) -> Option<String>;
fn is_held(&self) -> bool { fn is_held(&self) -> bool {
false false
} }
@ -470,6 +471,9 @@ pub trait Pane {
None None
} }
fn rename(&mut self, _buf: Vec<u8>) {} fn rename(&mut self, _buf: Vec<u8>) {}
fn serialize(&self, _scrollback_lines_to_serialize: Option<usize>) -> Option<String> {
None
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -626,7 +630,7 @@ impl Tab {
) -> Result<()> { ) -> Result<()> {
self.swap_layouts self.swap_layouts
.set_base_layout((layout.clone(), floating_panes_layout.clone())); .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.viewport,
&self.senders, &self.senders,
&self.sixel_image_store, &self.sixel_image_store,
@ -652,10 +656,11 @@ impl Tab {
new_plugin_ids, new_plugin_ids,
client_id, client_id,
)?; )?;
if layout_has_floating_panes { #[allow(clippy::if_same_then_else)]
if !self.floating_panes.panes_are_visible() { 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.toggle_floating_panes(Some(client_id), None)?;
}
} }
self.tiled_panes.reapply_pane_frames(); self.tiled_panes.reapply_pane_frames();
self.is_pending = false; self.is_pending = false;
@ -1015,7 +1020,7 @@ impl Tab {
pid: PaneId, pid: PaneId,
initial_pane_title: Option<String>, initial_pane_title: Option<String>,
should_float: Option<bool>, should_float: Option<bool>,
run_plugin: Option<Run>, // only relevant if this is a plugin pane invoked_with: Option<Run>,
client_id: Option<ClientId>, client_id: Option<ClientId>,
) -> Result<()> { ) -> Result<()> {
let err_context = || format!("failed to create new pane with id {pid:?}"); let err_context = || format!("failed to create new pane with id {pid:?}");
@ -1041,7 +1046,7 @@ impl Tab {
self.terminal_emulator_colors.clone(), self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(), self.terminal_emulator_color_codes.clone(),
initial_pane_title, initial_pane_title,
None, invoked_with,
self.debug, self.debug,
)) as Box<dyn Pane> )) as Box<dyn Pane>
}, },
@ -1063,7 +1068,7 @@ impl Tab {
self.character_cell_size.clone(), self.character_cell_size.clone(),
self.connected_clients.borrow().iter().copied().collect(), self.connected_clients.borrow().iter().copied().collect(),
self.style, self.style,
run_plugin, invoked_with,
self.debug, self.debug,
)) as Box<dyn Pane> )) as Box<dyn Pane>
}, },
@ -1115,8 +1120,9 @@ impl Tab {
}; };
match replaced_pane { match replaced_pane {
Some(replaced_pane) => { Some(replaced_pane) => {
let is_scrollback_editor = true;
self.suppressed_panes self.suppressed_panes
.insert(PaneId::Terminal(pid), replaced_pane); .insert(PaneId::Terminal(pid), (is_scrollback_editor, replaced_pane));
self.get_active_pane(client_id) self.get_active_pane(client_id)
.with_context(|| format!("no active pane found for client {client_id}")) .with_context(|| format!("no active pane found for client {client_id}"))
.and_then(|current_active_pane| { .and_then(|current_active_pane| {
@ -1148,7 +1154,7 @@ impl Tab {
&mut self, &mut self,
old_pane_id: PaneId, old_pane_id: PaneId,
new_pane_id: PaneId, new_pane_id: PaneId,
run_plugin: Option<Run>, run: Option<Run>,
) -> Result<()> { ) -> Result<()> {
// this method creates a new pane from pid and replaces it with the active pane // 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 // the active pane is then suppressed (hidden and not rendered) until the current
@ -1158,7 +1164,7 @@ impl Tab {
match new_pane_id { match new_pane_id {
PaneId::Terminal(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 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, new_pane_id,
PaneGeom::default(), // the initial size will be set later PaneGeom::default(), // the initial size will be set later
self.style, self.style,
@ -1170,7 +1176,7 @@ impl Tab {
self.terminal_emulator_colors.clone(), self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(), self.terminal_emulator_color_codes.clone(),
None, None,
None, run,
self.debug, self.debug,
); );
let replaced_pane = if self.floating_panes.panes_contain(&old_pane_id) { let replaced_pane = if self.floating_panes.panes_contain(&old_pane_id) {
@ -1183,14 +1189,17 @@ impl Tab {
}; };
match replaced_pane { match replaced_pane {
Some(replaced_pane) => { Some(replaced_pane) => {
resize_pty!( let _ = resize_pty!(
replaced_pane, replaced_pane,
self.os_api, self.os_api,
self.senders, self.senders,
self.character_cell_size self.character_cell_size
); );
self.suppressed_panes let is_scrollback_editor = false;
.insert(PaneId::Terminal(new_pane_id), replaced_pane); self.suppressed_panes.insert(
PaneId::Terminal(new_pane_id),
(is_scrollback_editor, replaced_pane),
);
}, },
None => { None => {
Err::<(), _>(anyhow!( Err::<(), _>(anyhow!(
@ -1202,8 +1211,7 @@ impl Tab {
} }
}, },
PaneId::Plugin(plugin_pid) => { PaneId::Plugin(plugin_pid) => {
// TBD, currently unsupported let new_pane = PluginPane::new(
let mut new_pane = PluginPane::new(
plugin_pid, plugin_pid,
PaneGeom::default(), // this will be filled out later PaneGeom::default(), // this will be filled out later
self.senders self.senders
@ -1220,7 +1228,7 @@ impl Tab {
self.character_cell_size.clone(), self.character_cell_size.clone(),
self.connected_clients.borrow().iter().copied().collect(), self.connected_clients.borrow().iter().copied().collect(),
self.style, self.style,
run_plugin, run,
self.debug, self.debug,
); );
let replaced_pane = if self.floating_panes.panes_contain(&old_pane_id) { let replaced_pane = if self.floating_panes.panes_contain(&old_pane_id) {
@ -1233,14 +1241,17 @@ impl Tab {
}; };
match replaced_pane { match replaced_pane {
Some(replaced_pane) => { Some(replaced_pane) => {
resize_pty!( let _ = resize_pty!(
replaced_pane, replaced_pane,
self.os_api, self.os_api,
self.senders, self.senders,
self.character_cell_size self.character_cell_size
); );
self.suppressed_panes let is_scrollback_editor = false;
.insert(PaneId::Plugin(plugin_pid), replaced_pane); self.suppressed_panes.insert(
PaneId::Plugin(plugin_pid),
(is_scrollback_editor, replaced_pane),
);
}, },
None => { None => {
Err::<(), _>(anyhow!( Err::<(), _>(anyhow!(
@ -1417,7 +1428,7 @@ impl Tab {
|| self || self
.suppressed_panes .suppressed_panes
.values() .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 { pub fn has_plugin(&self, plugin_id: u32) -> bool {
self.tiled_panes.panes_contain(&PaneId::Plugin(plugin_id)) self.tiled_panes.panes_contain(&PaneId::Plugin(plugin_id))
@ -1427,12 +1438,15 @@ impl Tab {
|| self || self
.suppressed_panes .suppressed_panes
.values() .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 { pub fn has_pane_with_pid(&self, pid: &PaneId) -> bool {
self.tiled_panes.panes_contain(pid) self.tiled_panes.panes_contain(pid)
|| self.floating_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 { pub fn has_non_suppressed_pane_with_pid(&self, pid: &PaneId) -> bool {
self.tiled_panes.panes_contain(pid) || self.floating_panes.panes_contain(pid) self.tiled_panes.panes_contain(pid) || self.floating_panes.panes_contain(pid)
@ -1451,7 +1465,8 @@ impl Tab {
.or_else(|| { .or_else(|| {
self.suppressed_panes self.suppressed_panes
.values_mut() .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 // If the pane is scrolled buffer the vte events
@ -1483,7 +1498,8 @@ impl Tab {
.or_else(|| { .or_else(|| {
self.suppressed_panes self.suppressed_panes
.values_mut() .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); plugin_pane.handle_plugin_bytes(client_id, bytes);
@ -1510,7 +1526,8 @@ impl Tab {
.or_else(|| { .or_else(|| {
self.suppressed_panes self.suppressed_panes
.values_mut() .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) { if self.pids_waiting_resize.remove(&pid) {
@ -1638,7 +1655,7 @@ impl Tab {
.floating_panes .floating_panes
.get_mut(&pane_id) .get_mut(&pane_id)
.or_else(|| self.tiled_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(|p| &mut p.1))
.ok_or_else(|| anyhow!(format!("failed to find pane with id {pane_id:?}"))) .ok_or_else(|| anyhow!(format!("failed to find pane with id {pane_id:?}")))
.with_context(err_context)?; .with_context(err_context)?;
@ -1913,12 +1930,18 @@ impl Tab {
} }
} }
} }
fn get_tiled_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> { pub(crate) fn get_tiled_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> {
self.tiled_panes.get_panes() self.tiled_panes.get_panes()
} }
fn get_floating_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> { pub(crate) fn get_floating_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> {
self.floating_panes.get_panes() self.floating_panes.get_panes()
} }
pub(crate) fn get_suppressed_panes(
&self,
) -> impl Iterator<Item = (&PaneId, &(bool, Box<dyn Pane>))> {
// bool => is_scrollback_editor
self.suppressed_panes.iter()
}
fn get_selectable_tiled_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> { fn get_selectable_tiled_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> {
self.get_tiled_panes().filter(|(_, p)| p.selectable()) self.get_tiled_panes().filter(|(_, p)| p.selectable())
} }
@ -2397,7 +2420,7 @@ impl Tab {
} }
closed_pane closed_pane
} else if self.suppressed_panes.contains_key(&id) { } 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 { } else {
None None
} }
@ -2439,7 +2462,7 @@ impl Tab {
pane_id pane_id
) )
}) })
.and_then(|suppressed_pane| { .and_then(|(_is_scrollback_editor, suppressed_pane)| {
let suppressed_pane_id = suppressed_pane.pid(); let suppressed_pane_id = suppressed_pane.pid();
let replaced_pane = if self.are_floating_panes_visible() { let replaced_pane = if self.are_floating_panes_visible() {
Some(self.floating_panes.replace_pane(pane_id, suppressed_pane)).transpose()? Some(self.floating_panes.replace_pane(pane_id, suppressed_pane)).transpose()?
@ -3297,7 +3320,11 @@ impl Tab {
.floating_panes .floating_panes
.get_pane_mut(pane_id) .get_pane_mut(pane_id)
.or_else(|| self.tiled_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)?; .with_context(err_context)?;
pane.rename(buf); pane.rename(buf);
Ok(()) Ok(())
@ -3415,7 +3442,8 @@ impl Tab {
.or_else(|| { .or_else(|| {
self.suppressed_panes self.suppressed_panes
.values_mut() .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); pane.add_red_pane_frame_color_override(error_text);
@ -3429,7 +3457,8 @@ impl Tab {
.or_else(|| { .or_else(|| {
self.suppressed_panes self.suppressed_panes
.values_mut() .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(); pane.clear_pane_frame_color_override();
@ -3443,7 +3472,8 @@ impl Tab {
.or_else(|| { .or_else(|| {
self.suppressed_panes self.suppressed_panes
.values_mut() .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); plugin_pane.update_loading_indication(loading_indication);
@ -3461,7 +3491,8 @@ impl Tab {
.or_else(|| { .or_else(|| {
self.suppressed_panes self.suppressed_panes
.values_mut() .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); plugin_pane.start_loading_indication(loading_indication);
@ -3475,7 +3506,8 @@ impl Tab {
.or_else(|| { .or_else(|| {
self.suppressed_panes self.suppressed_panes
.values_mut() .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(); plugin_pane.progress_animation_offset();
@ -3504,7 +3536,7 @@ impl Tab {
let run = Some(Run::Plugin(run_plugin.clone())); let run = Some(Run::Plugin(run_plugin.clone()));
self.suppressed_panes self.suppressed_panes
.iter() .iter()
.find(|(_id, s_p)| s_p.invoked_with() == &run) .find(|(_id, s_p)| s_p.1.invoked_with() == &run)
.map(|(id, _)| *id) .map(|(id, _)| *id)
}) })
} }
@ -3531,10 +3563,10 @@ impl Tab {
Some(pane) => { Some(pane) => {
if should_float { if should_float {
self.show_floating_panes(); 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 { } else {
self.hide_floating_panes(); 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(()), None => Ok(()),
@ -3546,7 +3578,9 @@ impl Tab {
// scrollback editor), but it has to take itself out on its own (eg. a plugin using the // scrollback editor), but it has to take itself out on its own (eg. a plugin using the
// show_self() method) // show_self() method)
if let Some(pane) = self.close_pane(pane_id, true, Some(client_id)) { 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<PaneInfo> { pub fn pane_infos(&self) -> Vec<PaneInfo> {
@ -3555,7 +3589,7 @@ impl Tab {
let mut floating_pane_info = self.floating_panes.pane_info(); let mut floating_pane_info = self.floating_panes.pane_info();
pane_info.append(&mut tiled_pane_info); pane_info.append(&mut tiled_pane_info);
pane_info.append(&mut floating_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); 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_floating = false;
pane_info_for_suppressed_pane.is_suppressed = true; pane_info_for_suppressed_pane.is_suppressed = true;
@ -3631,7 +3665,8 @@ impl Tab {
.or_else(|| { .or_else(|| {
self.suppressed_panes self.suppressed_panes
.values_mut() .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); plugin_pane.request_permissions_from_user(permissions);

View file

@ -234,6 +234,11 @@ fn create_new_screen(size: Size) -> Screen {
let auto_layout = true; let auto_layout = true;
let session_is_mirrored = true; let session_is_mirrored = true;
let copy_options = CopyOptions::default(); 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 debug = false;
let screen = Screen::new( let screen = Screen::new(
@ -246,6 +251,11 @@ fn create_new_screen(size: Size) -> Screen {
session_is_mirrored, session_is_mirrored,
copy_options, copy_options,
debug, debug,
default_layout,
default_shell,
session_serialization,
serialize_pane_viewport,
scrollback_lines_to_serialize,
); );
screen screen
} }
@ -301,6 +311,7 @@ impl MockScreen {
client_attributes, client_attributes,
Box::new(config_options), Box::new(config_options),
debug, debug,
Box::new(Layout::default()),
) )
.expect("TEST") .expect("TEST")
}) })

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 2217 assertion_line: 2246
expression: "format!(\"{:#?}\", new_tab_action)" expression: "format!(\"{:#?}\", new_tab_action)"
--- ---
Some( Some(
@ -25,6 +25,8 @@ Some(
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -39,6 +41,8 @@ Some(
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -50,6 +54,8 @@ Some(
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
), ),
[], [],

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 2263 assertion_line: 2292
expression: "format!(\"{:#?}\", new_tab_instruction)" expression: "format!(\"{:#?}\", new_tab_instruction)"
--- ---
NewTab( NewTab(
@ -28,6 +28,8 @@ NewTab(
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -46,6 +48,8 @@ NewTab(
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -64,6 +68,8 @@ NewTab(
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -75,6 +81,8 @@ NewTab(
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
), ),
[], [],

View file

@ -43,6 +43,7 @@ uuid = { version = "1.4.1", features = ["serde", "v4"] }
async-channel = "1.8.0" async-channel = "1.8.0"
include_dir = "0.7.3" include_dir = "0.7.3"
prost = "0.11.9" prost = "0.11.9"
common-path = "1.0.0"
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
[target.'cfg(not(target_family = "wasm"))'.dependencies] [target.'cfg(not(target_family = "wasm"))'.dependencies]
@ -52,9 +53,11 @@ signal-hook = "0.3"
interprocess = "1.2.1" interprocess = "1.2.1"
async-std = { version = "1.3.0", features = ["unstable"] } async-std = { version = "1.3.0", features = ["unstable"] }
notify-debouncer-full = "0.1.0" notify-debouncer-full = "0.1.0"
humantime = "2.1.0"
[dev-dependencies] [dev-dependencies]
insta = { version = "1.6.0", features = ["backtrace"] } insta = { version = "1.6.0", features = ["backtrace"] }
expect-test = "1.4.1"
[build-dependencies] [build-dependencies]
prost-build = "0.11.9" prost-build = "0.11.9"

View file

@ -229,6 +229,25 @@ plugins {
// //
// auto_layout true // 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 // Define color themes for Zellij
// For more examples, see: https://github.com/zellij-org/zellij/tree/main/example/themes // 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 // Once these themes are defined, one of them should to be selected in the "theme" section of this file

View file

@ -98,7 +98,11 @@ pub enum SessionCommand {
pub enum Sessions { pub enum Sessions {
/// List active sessions /// List active sessions
#[clap(visible_alias = "ls")] #[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 /// Attach to a session
#[clap(visible_alias = "a")] #[clap(visible_alias = "a")]
@ -118,9 +122,13 @@ pub enum Sessions {
/// Change the behaviour of zellij /// Change the behaviour of zellij
#[clap(subcommand, name = "options")] #[clap(subcommand, name = "options")]
options: Option<Box<SessionCommand>>, options: Option<Box<SessionCommand>>,
/// 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")] #[clap(visible_alias = "k")]
KillSession { KillSession {
/// Name of target session /// Name of target session
@ -128,6 +136,17 @@ pub enum Sessions {
target_session: Option<String>, target_session: Option<String>,
}, },
/// Delete a specific session
#[clap(visible_alias = "d")]
DeleteSession {
/// Name of target session
#[clap(value_parser)]
target_session: Option<String>,
/// 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 /// Kill all sessions
#[clap(visible_alias = "ka")] #[clap(visible_alias = "ka")]
KillAllSessions { KillAllSessions {
@ -135,6 +154,18 @@ pub enum Sessions {
#[clap(short, long, value_parser)] #[clap(short, long, value_parser)]
yes: bool, 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 /// Send actions to a specific session
#[clap(visible_alias = "ac")] #[clap(visible_alias = "ac")]
#[clap(subcommand)] #[clap(subcommand)]
@ -271,6 +302,8 @@ pub enum CliAction {
#[clap(short, long, value_parser, default_value("false"), takes_value(false))] #[clap(short, long, value_parser, default_value("false"), takes_value(false))]
full: bool, full: bool,
}, },
/// Dump current layout to stdout
DumpLayout,
/// Open the pane scrollback in your default editor /// Open the pane scrollback in your default editor
EditScrollback, EditScrollback,
/// Scroll up in the focused pane /// Scroll up in the focused pane

View file

@ -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 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 { const fn system_default_data_dir() -> &'static str {
if let Some(data_dir) = std::option_env!("PREFIX") { if let Some(data_dir) = std::option_env!("PREFIX") {
data_dir data_dir

View file

@ -257,6 +257,7 @@ pub enum ScreenContext {
Exit, Exit,
ClearScreen, ClearScreen,
DumpScreen, DumpScreen,
DumpLayout,
EditScrollback, EditScrollback,
ScrollUp, ScrollUp,
ScrollUpAt, ScrollUpAt,
@ -345,6 +346,7 @@ pub enum ScreenContext {
UpdateSessionInfos, UpdateSessionInfos,
ReplacePane, ReplacePane,
NewInPlacePluginPane, NewInPlacePluginPane,
DumpLayoutToHd,
} }
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s. /// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
@ -361,6 +363,8 @@ pub enum PtyContext {
CloseTab, CloseTab,
ReRunCommandInPane, ReRunCommandInPane,
SpawnInPlaceTerminal, SpawnInPlaceTerminal,
DumpLayout,
LogLayoutToHd,
Exit, Exit,
} }
@ -383,6 +387,8 @@ pub enum PluginContext {
PostMessageToPlugin, PostMessageToPlugin,
PluginSubscribedToEvents, PluginSubscribedToEvents,
PermissionRequestResult, PermissionRequestResult,
DumpLayout,
LogLayoutToHd,
} }
/// Stack call representations corresponding to the different types of [`ClientInstruction`]s. /// Stack call representations corresponding to the different types of [`ClientInstruction`]s.
@ -437,6 +443,7 @@ pub enum BackgroundJobContext {
StopPluginLoadingAnimation, StopPluginLoadingAnimation,
ReadAllSessionInfosOnMachine, ReadAllSessionInfosOnMachine,
ReportSessionInfo, ReportSessionInfo,
ReportLayoutInfo,
Exit, Exit,
} }

84
zellij-utils/src/home.rs Normal file
View file

@ -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<PathBuf> {
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<PathBuf> {
None
}
/// Order in which config directories are checked
#[cfg(not(test))]
pub(crate) fn default_config_dirs() -> Vec<Option<PathBuf>> {
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<PathBuf> {
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<PathBuf>) -> Option<PathBuf> {
config_dir.map(|dir| dir.join("layouts"))
}
pub fn default_layout_dir() -> Option<PathBuf> {
find_default_config_dir().map(|dir| dir.join("layouts"))
}
pub fn get_theme_dir(config_dir: Option<PathBuf>) -> Option<PathBuf> {
config_dir.map(|dir| dir.join("themes"))
}
pub fn default_theme_dir() -> Option<PathBuf> {
find_default_config_dir().map(|dir| dir.join("themes"))
}

View file

@ -8,9 +8,9 @@ use super::layout::{
use crate::cli::CliAction; use crate::cli::CliAction;
use crate::data::InputMode; use crate::data::InputMode;
use crate::data::{Direction, Resize}; 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::config::{Config, ConfigError, KdlError};
use crate::input::options::OnForceClose; use crate::input::options::OnForceClose;
use crate::setup::{find_default_config_dir, get_layout_dir};
use miette::{NamedSource, Report}; use miette::{NamedSource, Report};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -124,6 +124,8 @@ pub enum Action {
ClearScreen, ClearScreen,
/// Dumps the screen to a file /// Dumps the screen to a file
DumpScreen(String, bool), DumpScreen(String, bool),
/// Dumps
DumpLayout,
/// Scroll up in focus pane. /// Scroll up in focus pane.
EditScrollback, EditScrollback,
ScrollUp, ScrollUp,
@ -280,6 +282,7 @@ impl Action {
path.as_os_str().to_string_lossy().into(), path.as_os_str().to_string_lossy().into(),
full, full,
)]), )]),
CliAction::DumpLayout => Ok(vec![Action::DumpLayout]),
CliAction::EditScrollback => Ok(vec![Action::EditScrollback]), CliAction::EditScrollback => Ok(vec![Action::EditScrollback]),
CliAction::ScrollUp => Ok(vec![Action::ScrollUp]), CliAction::ScrollUp => Ok(vec![Action::ScrollUp]),
CliAction::ScrollDown => Ok(vec![Action::ScrollDown]), CliAction::ScrollDown => Ok(vec![Action::ScrollDown]),

View file

@ -13,7 +13,7 @@ use super::plugins::{PluginsConfig, PluginsConfigError};
use super::theme::{Themes, UiConfig}; use super::theme::{Themes, UiConfig};
use crate::cli::{CliArgs, Command}; use crate::cli::{CliArgs, Command};
use crate::envs::EnvironmentVariables; use crate::envs::EnvironmentVariables;
use crate::setup; use crate::{home, setup};
const DEFAULT_CONFIG_FILE_NAME: &str = "config.kdl"; const DEFAULT_CONFIG_FILE_NAME: &str = "config.kdl";
@ -143,7 +143,7 @@ impl TryFrom<&CliArgs> for Config {
let config_dir = opts let config_dir = opts
.config_dir .config_dir
.clone() .clone()
.or_else(setup::find_default_config_dir); .or_else(home::find_default_config_dir);
if let Some(ref config) = config_dir { if let Some(ref config) = config_dir {
let path = config.join(DEFAULT_CONFIG_FILE_NAME); let path = config.join(DEFAULT_CONFIG_FILE_NAME);

View file

@ -10,14 +10,16 @@
// then [`zellij-utils`] could be a proper place. // then [`zellij-utils`] could be a proper place.
use crate::{ use crate::{
data::Direction, data::Direction,
home::find_default_config_dir,
input::{ input::{
command::RunCommand, command::RunCommand,
config::{Config, ConfigError}, config::{Config, ConfigError},
}, },
pane_size::{Dimension, PaneGeom}, pane_size::{Constraint, Dimension, PaneGeom},
setup, setup::{self},
}; };
use std::fmt::{Display, Formatter};
use std::str::FromStr; use std::str::FromStr;
use super::plugins::{PluginTag, PluginsConfigError}; use super::plugins::{PluginTag, PluginsConfigError};
@ -205,6 +207,14 @@ impl Run {
_ => false, _ => false,
} }
} }
pub fn get_cwd(&self) -> Option<PathBuf> {
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)] #[allow(clippy::derive_hash_xor_eq)]
@ -325,6 +335,12 @@ impl RunPluginLocation {
_ => Err(PluginsConfigError::InvalidUrlScheme(url)), _ => 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 { impl From<&RunPluginLocation> for Url {
@ -362,6 +378,17 @@ pub enum LayoutConstraint {
NoConstraint, 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<LayoutConstraint, TiledPaneLayout>, Option<String>); // Option<String> is the swap layout name pub type SwapTiledLayout = (BTreeMap<LayoutConstraint, TiledPaneLayout>, Option<String>); // Option<String> is the swap layout name
pub type SwapFloatingLayout = ( pub type SwapFloatingLayout = (
BTreeMap<LayoutConstraint, Vec<FloatingPaneLayout>>, BTreeMap<LayoutConstraint, Vec<FloatingPaneLayout>>,
@ -384,6 +411,15 @@ pub enum PercentOrFixed {
Fixed(usize), // An absolute number of columns or rows Fixed(usize), // An absolute number of columns or rows
} }
impl From<Dimension> 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 { impl PercentOrFixed {
pub fn to_position(&self, whole: usize) -> usize { pub fn to_position(&self, whole: usize) -> usize {
match self { match self {
@ -438,6 +474,7 @@ pub struct FloatingPaneLayout {
pub run: Option<Run>, pub run: Option<Run>,
pub focus: Option<bool>, pub focus: Option<bool>,
pub already_running: bool, pub already_running: bool,
pub pane_initial_contents: Option<String>,
} }
impl FloatingPaneLayout { impl FloatingPaneLayout {
@ -449,6 +486,11 @@ impl FloatingPaneLayout {
}, },
} }
} }
pub fn add_start_suspended(&mut self, start_suspended: Option<bool>) {
if let Some(run) = self.run.as_mut() {
run.add_start_suspended(start_suspended);
}
}
} }
impl From<&TiledPaneLayout> for FloatingPaneLayout { impl From<&TiledPaneLayout> for FloatingPaneLayout {
@ -476,6 +518,8 @@ pub struct TiledPaneLayout {
pub is_expanded_in_stack: bool, pub is_expanded_in_stack: bool,
pub exclude_from_sync: Option<bool>, pub exclude_from_sync: Option<bool>,
pub run_instructions_to_ignore: Vec<Option<Run>>, pub run_instructions_to_ignore: Vec<Option<Run>>,
pub hide_floating_panes: bool, // only relevant if this is the base layout
pub pane_initial_contents: Option<String>,
} }
impl TiledPaneLayout { impl TiledPaneLayout {
@ -723,6 +767,14 @@ impl TiledPaneLayout {
} }
false false
} }
pub fn recursively_add_start_suspended(&mut self, start_suspended: Option<bool>) {
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)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
@ -824,7 +876,15 @@ impl Layout {
Layout::stringified_from_default_assets(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( pub fn stringified_from_path(
@ -936,6 +996,15 @@ impl Layout {
self.focused_tab_index self.focused_tab_index
} }
pub fn recursively_add_start_suspended(&mut self, start_suspended: Option<bool>) {
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)> { fn swap_layout_and_path(path: &Path) -> Option<(String, String)> {
// Option<path, stringified_swap_layout> // Option<path, stringified_swap_layout>
let mut swap_layout_path = PathBuf::from(path); let mut swap_layout_path = PathBuf::from(path);
@ -975,7 +1044,6 @@ fn split_space(
layout: &TiledPaneLayout, layout: &TiledPaneLayout,
total_space_to_split: &PaneGeom, total_space_to_split: &PaneGeom,
) -> Result<Vec<(TiledPaneLayout, PaneGeom)>, &'static str> { ) -> Result<Vec<(TiledPaneLayout, PaneGeom)>, &'static str> {
let mut pane_positions = Vec::new();
let sizes: Vec<Option<SplitSize>> = if layout.children_are_stacked { let sizes: Vec<Option<SplitSize>> = if layout.children_are_stacked {
let index_of_expanded_pane = layout.children.iter().position(|p| p.is_expanded_in_stack); let index_of_expanded_pane = layout.children.iter().position(|p| p.is_expanded_in_stack);
let mut sizes: Vec<Option<SplitSize>> = layout let mut sizes: Vec<Option<SplitSize>> = layout
@ -1051,6 +1119,7 @@ fn split_space(
Dimension::percent(free_percent / flex_parts as f64) Dimension::percent(free_percent / flex_parts as f64)
}, },
}; };
split_dimension.adjust_inner( split_dimension.adjust_inner(
total_split_dimension_space total_split_dimension_space
.as_usize() .as_usize()
@ -1077,26 +1146,13 @@ fn split_space(
split_geom.push(geom); split_geom.push(geom);
current_position += split_dimension.as_usize(); current_position += split_dimension.as_usize();
} }
adjust_geoms_for_rounding_errors(
if total_pane_size < split_dimension_space.as_usize() { total_pane_size,
// add extra space from rounding errors to the last pane &mut split_geom,
let increase_by = split_dimension_space.as_usize() - total_pane_size; split_dimension_space,
if let Some(last_geom) = split_geom.last_mut() { layout.children_split_direction,
match layout.children_split_direction { );
SplitDirection::Vertical => last_geom.cols.increase_inner(increase_by), let mut pane_positions = Vec::new();
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),
}
}
}
for (i, part) in layout.children.iter().enumerate() { for (i, part) in layout.children.iter().enumerate() {
let part_position_and_size = split_geom.get(i).unwrap(); let part_position_and_size = split_geom.get(i).unwrap();
if !part.children.is_empty() { if !part.children.is_empty() {
@ -1115,6 +1171,74 @@ fn split_space(
Ok(pane_positions) Ok(pane_positions)
} }
fn adjust_geoms_for_rounding_errors(
total_pane_size: usize,
split_geoms: &mut Vec<PaneGeom>,
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 { impl Default for SplitDirection {
fn default() -> Self { fn default() -> Self {
SplitDirection::Horizontal SplitDirection::Horizontal

View file

@ -124,6 +124,24 @@ pub struct Options {
#[clap(long, value_parser)] #[clap(long, value_parser)]
#[serde(default)] #[serde(default)]
pub auto_layout: Option<bool>, pub auto_layout: Option<bool>,
/// 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<bool>,
/// Whether pane viewports are serialized along with the session, default is false
#[clap(long, value_parser)]
#[serde(default)]
pub serialize_pane_viewport: Option<bool>,
/// 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<usize>,
} }
#[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, Copy, PartialEq)] #[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, Copy, PartialEq)]
@ -187,6 +205,13 @@ impl Options {
let attach_to_session = other let attach_to_session = other
.attach_to_session .attach_to_session
.or_else(|| self.attach_to_session.clone()); .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 { Options {
simplified_ui, simplified_ui,
@ -209,6 +234,9 @@ impl Options {
session_name, session_name,
attach_to_session, attach_to_session,
auto_layout, 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 pane_frames = merge_bool(other.pane_frames, self.pane_frames);
let auto_layout = merge_bool(other.auto_layout, self.auto_layout); let auto_layout = merge_bool(other.auto_layout, self.auto_layout);
let mirror_session = merge_bool(other.mirror_session, self.mirror_session); 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_mode = other.default_mode.or(self.default_mode);
let default_shell = other.default_shell.or_else(|| self.default_shell.clone()); let default_shell = other.default_shell.or_else(|| self.default_shell.clone());
@ -252,6 +284,9 @@ impl Options {
let attach_to_session = other let attach_to_session = other
.attach_to_session .attach_to_session
.or_else(|| self.attach_to_session.clone()); .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 { Options {
simplified_ui, simplified_ui,
@ -274,6 +309,9 @@ impl Options {
session_name, session_name,
attach_to_session, attach_to_session,
auto_layout, auto_layout,
session_serialization,
serialize_pane_viewport,
scrollback_lines_to_serialize,
} }
} }
@ -333,6 +371,9 @@ impl From<CliOptions> for Options {
session_name: opts.session_name, session_name: opts.session_name,
attach_to_session: opts.attach_to_session, attach_to_session: opts.attach_to_session,
auto_layout: opts.auto_layout, 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() ..Default::default()
} }
} }

View file

@ -173,6 +173,36 @@ fn layout_with_mixed_panes_and_floating_panes() {
assert_eq!(layout, expected_layout); 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] #[test]
fn layout_with_floating_panes_template() { fn layout_with_floating_panes_template() {
let kdl_layout = r#" let kdl_layout = r#"
@ -786,6 +816,31 @@ fn layout_with_default_tab_template() {
assert_snapshot!(format!("{:#?}", layout)); 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] #[test]
fn layout_with_pane_templates() { fn layout_with_pane_templates() {
let kdl_layout = r#" let kdl_layout = r#"

View file

@ -35,6 +35,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -62,6 +64,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -73,6 +77,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -38,6 +38,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -65,6 +67,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -76,6 +80,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -29,6 +29,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -43,6 +45,8 @@ Layout {
is_expanded_in_stack: true, is_expanded_in_stack: true,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -57,6 +61,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -68,6 +74,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -79,6 +87,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -29,6 +29,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -43,6 +45,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -54,6 +58,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -65,6 +71,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -33,6 +33,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -47,6 +49,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -58,6 +62,8 @@ Layout {
is_expanded_in_stack: true, is_expanded_in_stack: true,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -69,6 +75,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -80,6 +88,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -25,6 +25,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -36,6 +38,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),
@ -81,6 +85,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -103,6 +109,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -119,6 +127,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -130,6 +140,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -141,6 +153,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -173,6 +187,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -184,6 +200,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
MaxPanes( MaxPanes(
8, 8,
@ -222,6 +240,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -246,6 +266,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -264,6 +286,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -278,6 +302,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -292,6 +318,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -306,6 +334,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -317,6 +347,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -328,6 +360,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -339,6 +373,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -371,6 +407,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -382,6 +420,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
MaxPanes( MaxPanes(
12, 12,
@ -420,6 +460,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -444,6 +486,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -462,6 +506,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -476,6 +522,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -490,6 +538,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -504,6 +554,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -515,6 +567,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -533,6 +587,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -547,6 +603,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -561,6 +619,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -575,6 +635,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -586,6 +648,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -597,6 +661,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -608,6 +674,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -640,6 +708,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -651,6 +721,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
}, },
Some( Some(

View file

@ -35,6 +35,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -53,6 +55,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -67,6 +71,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -78,6 +84,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -89,6 +97,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -103,6 +113,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -114,6 +126,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -136,6 +150,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -149,6 +165,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -163,6 +181,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -174,6 +194,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -185,6 +207,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -30,6 +30,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -48,6 +50,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -62,6 +66,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -73,6 +79,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -84,6 +92,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -98,6 +108,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -109,6 +121,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),
@ -135,6 +149,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -148,6 +164,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -162,6 +180,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -173,6 +193,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),
@ -193,6 +215,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -35,6 +35,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -59,6 +61,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -70,6 +74,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -35,6 +35,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -59,6 +61,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -70,6 +74,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -30,6 +30,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -50,6 +52,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -61,6 +65,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -75,6 +81,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -86,6 +94,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -104,6 +114,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -118,6 +130,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -129,6 +143,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -140,6 +156,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),
@ -166,6 +184,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -186,6 +206,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -197,6 +219,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -211,6 +235,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -222,6 +248,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -236,6 +264,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -247,6 +277,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),
@ -267,6 +299,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -35,6 +35,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -61,6 +63,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -72,6 +76,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -37,6 +37,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -63,6 +65,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -74,6 +78,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -29,6 +29,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -47,6 +49,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -65,6 +69,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -83,6 +89,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -109,6 +117,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -131,6 +141,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -153,6 +165,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -179,6 +193,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -201,6 +217,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -212,6 +230,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -28,6 +28,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -54,6 +56,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -69,6 +73,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),
@ -89,6 +95,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -32,6 +32,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -58,6 +60,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -80,6 +84,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -95,6 +101,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -110,6 +118,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -125,6 +135,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),
@ -145,6 +157,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -28,6 +28,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -54,6 +56,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -76,6 +80,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -91,6 +97,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -106,6 +114,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),
@ -126,6 +136,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -29,6 +29,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -55,6 +57,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -66,6 +70,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -29,6 +29,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -55,6 +57,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -66,6 +70,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -29,6 +29,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -55,6 +57,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -66,6 +70,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -29,6 +29,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -55,6 +57,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -66,6 +70,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -28,6 +28,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -54,6 +56,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -69,6 +73,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),
@ -89,6 +95,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -35,6 +35,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -46,6 +48,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -35,6 +35,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -46,6 +48,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -26,6 +26,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Vertical, children_split_direction: Vertical,
@ -44,6 +46,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -58,6 +62,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -69,6 +75,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -83,6 +91,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -94,6 +104,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),
@ -118,6 +130,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -136,6 +150,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -150,6 +166,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -161,6 +179,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -175,6 +195,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -186,6 +208,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),
@ -208,6 +232,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -222,6 +248,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -236,6 +264,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -247,6 +277,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),
@ -271,6 +303,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -285,6 +319,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -299,6 +335,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -310,6 +348,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -29,6 +29,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -43,6 +45,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -61,6 +65,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -75,6 +81,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -86,6 +94,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -104,6 +114,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -118,6 +130,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -132,6 +146,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -143,6 +159,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -154,6 +172,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -165,6 +185,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -29,6 +29,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -43,6 +45,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -61,6 +65,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -75,6 +81,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -86,6 +94,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -100,6 +110,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -111,6 +123,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -122,6 +136,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -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: [],
}

View file

@ -27,6 +27,8 @@ Layout {
true, true,
), ),
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -38,6 +40,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -29,6 +29,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -47,6 +49,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -58,6 +62,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -72,6 +78,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -83,6 +91,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Vertical, children_split_direction: Vertical,
@ -101,6 +111,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -119,6 +131,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -133,6 +147,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -144,6 +160,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -158,6 +176,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -169,6 +189,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Vertical, children_split_direction: Vertical,
@ -187,6 +209,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Vertical, children_split_direction: Vertical,
@ -205,6 +229,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -219,6 +245,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -230,6 +258,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -244,6 +274,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -255,6 +287,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Vertical, children_split_direction: Vertical,
@ -273,6 +307,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -287,6 +323,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -301,6 +339,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -312,6 +352,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -323,6 +365,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -28,6 +28,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -56,6 +58,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -67,6 +71,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -81,6 +87,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -92,6 +100,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -103,6 +113,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),
@ -123,6 +135,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -24,6 +24,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -35,6 +37,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[ [
FloatingPaneLayout { FloatingPaneLayout {
@ -46,6 +50,7 @@ Layout {
run: None, run: None,
focus: None, focus: None,
already_running: false, already_running: false,
pane_initial_contents: None,
}, },
], ],
), ),
@ -68,6 +73,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -79,6 +86,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[ [
FloatingPaneLayout { FloatingPaneLayout {
@ -90,6 +99,7 @@ Layout {
run: None, run: None,
focus: None, focus: None,
already_running: false, already_running: false,
pane_initial_contents: None,
}, },
FloatingPaneLayout { FloatingPaneLayout {
name: None, name: None,
@ -100,6 +110,7 @@ Layout {
run: None, run: None,
focus: None, focus: None,
already_running: false, already_running: false,
pane_initial_contents: None,
}, },
], ],
), ),
@ -120,6 +131,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -37,6 +37,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -48,6 +50,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -37,6 +37,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -48,6 +50,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -37,6 +37,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -48,6 +50,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -37,6 +37,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -48,6 +50,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -37,6 +37,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -48,6 +50,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -29,6 +29,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -40,6 +42,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -37,6 +37,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -48,6 +50,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -37,6 +37,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -48,6 +50,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -33,6 +33,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -44,6 +46,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -33,6 +33,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -44,6 +46,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -28,6 +28,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -54,6 +56,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -69,6 +73,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),
@ -89,6 +95,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -28,6 +28,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
TiledPaneLayout { TiledPaneLayout {
children_split_direction: Horizontal, children_split_direction: Horizontal,
@ -54,6 +56,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
], ],
split_size: None, split_size: None,
@ -69,6 +73,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),
@ -89,6 +95,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -39,16 +39,20 @@ pub struct KdlLayoutParser<'a> {
tab_templates: HashMap<String, (TiledPaneLayout, Vec<FloatingPaneLayout>, KdlNode)>, tab_templates: HashMap<String, (TiledPaneLayout, Vec<FloatingPaneLayout>, KdlNode)>,
pane_templates: HashMap<String, (PaneOrFloatingPane, KdlNode)>, pane_templates: HashMap<String, (PaneOrFloatingPane, KdlNode)>,
default_tab_template: Option<(TiledPaneLayout, Vec<FloatingPaneLayout>, KdlNode)>, default_tab_template: Option<(TiledPaneLayout, Vec<FloatingPaneLayout>, KdlNode)>,
new_tab_template: Option<(TiledPaneLayout, Vec<FloatingPaneLayout>)>,
file_name: PathBuf,
} }
impl<'a> KdlLayoutParser<'a> { impl<'a> KdlLayoutParser<'a> {
pub fn new(raw_layout: &'a str, global_cwd: Option<PathBuf>) -> Self { pub fn new(raw_layout: &'a str, global_cwd: Option<PathBuf>, file_name: String) -> Self {
KdlLayoutParser { KdlLayoutParser {
raw_layout, raw_layout,
tab_templates: HashMap::new(), tab_templates: HashMap::new(),
pane_templates: HashMap::new(), pane_templates: HashMap::new(),
default_tab_template: None, default_tab_template: None,
new_tab_template: None,
global_cwd, global_cwd,
file_name: PathBuf::from(file_name),
} }
} }
fn is_a_reserved_word(&self, word: &str) -> bool { fn is_a_reserved_word(&self, word: &str) -> bool {
@ -59,6 +63,7 @@ impl<'a> KdlLayoutParser<'a> {
|| word == "pane_template" || word == "pane_template"
|| word == "tab_template" || word == "tab_template"
|| word == "default_tab_template" || word == "default_tab_template"
|| word == "new_tab_template"
|| word == "command" || word == "command"
|| word == "edit" || word == "edit"
|| word == "plugin" || word == "plugin"
@ -75,6 +80,8 @@ impl<'a> KdlLayoutParser<'a> {
|| word == "split_direction" || word == "split_direction"
|| word == "swap_tiled_layout" || word == "swap_tiled_layout"
|| word == "swap_floating_layout" || word == "swap_floating_layout"
|| word == "hide_floating_panes"
|| word == "contents_file"
} }
fn is_a_valid_pane_property(&self, property_name: &str) -> bool { fn is_a_valid_pane_property(&self, property_name: &str) -> bool {
property_name == "borderless" property_name == "borderless"
@ -94,6 +101,7 @@ impl<'a> KdlLayoutParser<'a> {
|| property_name == "stacked" || property_name == "stacked"
|| property_name == "expanded" || property_name == "expanded"
|| property_name == "exclude_from_sync" || property_name == "exclude_from_sync"
|| property_name == "contents_file"
} }
fn is_a_valid_floating_pane_property(&self, property_name: &str) -> bool { fn is_a_valid_floating_pane_property(&self, property_name: &str) -> bool {
property_name == "borderless" property_name == "borderless"
@ -110,6 +118,7 @@ impl<'a> KdlLayoutParser<'a> {
|| property_name == "y" || property_name == "y"
|| property_name == "width" || property_name == "width"
|| property_name == "height" || property_name == "height"
|| property_name == "contents_file"
} }
fn is_a_valid_tab_property(&self, property_name: &str) -> bool { fn is_a_valid_tab_property(&self, property_name: &str) -> bool {
property_name == "focus" property_name == "focus"
@ -121,6 +130,7 @@ impl<'a> KdlLayoutParser<'a> {
|| property_name == "max_panes" || property_name == "max_panes"
|| property_name == "min_panes" || property_name == "min_panes"
|| property_name == "exact_panes" || property_name == "exact_panes"
|| property_name == "hide_floating_panes"
} }
pub fn is_a_reserved_plugin_property(property_name: &str) -> bool { pub fn is_a_reserved_plugin_property(property_name: &str) -> bool {
property_name == "location" property_name == "location"
@ -511,6 +521,8 @@ impl<'a> KdlLayoutParser<'a> {
.map(|name| name.to_string()); .map(|name| name.to_string());
let exclude_from_sync = let exclude_from_sync =
kdl_get_bool_property_or_child_value_with_error!(kdl_node, "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 split_size = self.parse_split_size(kdl_node)?;
let run = self.parse_command_plugin_or_edit_block(kdl_node)?; let run = self.parse_command_plugin_or_edit_block(kdl_node)?;
let children_split_direction = self.parse_split_direction(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)?; 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 { Ok(TiledPaneLayout {
borderless: borderless.unwrap_or_default(), borderless: borderless.unwrap_or_default(),
focus, focus,
@ -553,6 +570,7 @@ impl<'a> KdlLayoutParser<'a> {
children, children,
children_are_stacked, children_are_stacked,
is_expanded_in_stack, is_expanded_in_stack,
pane_initial_contents,
..Default::default() ..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 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") let name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name")
.map(|name| name.to_string()); .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)?; 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 { Ok(FloatingPaneLayout {
name, name,
height, height,
@ -578,6 +603,7 @@ impl<'a> KdlLayoutParser<'a> {
y, y,
run, run,
focus, focus,
pane_initial_contents,
..Default::default() ..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()); 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 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 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 children_split_direction = self.parse_split_direction(kdl_node)?;
let mut child_floating_panes = vec![]; let mut child_floating_panes = vec![];
let children = match kdl_children_nodes!(kdl_node) { let children = match kdl_children_nodes!(kdl_node) {
@ -1128,6 +1156,7 @@ impl<'a> KdlLayoutParser<'a> {
let mut pane_layout = TiledPaneLayout { let mut pane_layout = TiledPaneLayout {
children_split_direction, children_split_direction,
children, children,
hide_floating_panes,
..Default::default() ..Default::default()
}; };
if let Some(cwd_prefix) = &self.cwd_prefix(tab_cwd.as_ref())? { 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())); Some((tab_template, tab_template_floating_panes, kdl_node.clone()));
Ok(()) 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( fn parse_tab_template_node(
&self, &self,
kdl_node: &KdlNode, kdl_node: &KdlNode,
@ -1788,6 +1823,8 @@ impl<'a> KdlLayoutParser<'a> {
self.populate_one_tab_template(child)?; self.populate_one_tab_template(child)?;
} else if child_name == "default_tab_template" { } else if child_name == "default_tab_template" {
self.populate_default_tab_template(child)?; self.populate_default_tab_template(child)?;
} else if child_name == "new_tab_template" {
self.populate_new_tab_template(child)?;
} }
} }
Ok(()) Ok(())
@ -2031,13 +2068,18 @@ impl<'a> KdlLayoutParser<'a> {
swap_tiled_layouts: Vec<SwapTiledLayout>, swap_tiled_layouts: Vec<SwapTiledLayout>,
swap_floating_layouts: Vec<SwapFloatingLayout>, swap_floating_layouts: Vec<SwapFloatingLayout>,
) -> Result<Layout, ConfigError> { ) -> Result<Layout, ConfigError> {
let template = self 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()? .default_template()?
.unwrap_or_else(|| TiledPaneLayout::default()); .unwrap_or_else(|| TiledPaneLayout::default());
Some((default_tab_tiled_panes_template, vec![]))
};
Ok(Layout { Ok(Layout {
tabs, tabs,
template: Some((template, vec![])), template,
focused_tab_index, focused_tab_index,
swap_tiled_layouts, swap_tiled_layouts,
swap_floating_layouts, swap_floating_layouts,
@ -2056,18 +2098,21 @@ impl<'a> KdlLayoutParser<'a> {
..Default::default() ..Default::default()
}; };
let default_template = self.default_template()?; 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 // 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 // to explicitly place it in the first tab
vec![] vec![]
} else { } else {
vec![(None, main_tab_layout.clone(), floating_panes.clone())] 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 // create a layout with one tab that has these child panes
Ok(Layout { Ok(Layout {
tabs, tabs,
template: Some((template, floating_panes)), template: Some(template),
swap_tiled_layouts, swap_tiled_layouts,
swap_floating_layouts, swap_floating_layouts,
..Default::default() ..Default::default()
@ -2079,11 +2124,16 @@ impl<'a> KdlLayoutParser<'a> {
swap_tiled_layouts: Vec<SwapTiledLayout>, swap_tiled_layouts: Vec<SwapTiledLayout>,
swap_floating_layouts: Vec<SwapFloatingLayout>, swap_floating_layouts: Vec<SwapFloatingLayout>,
) -> Result<Layout, ConfigError> { ) -> Result<Layout, ConfigError> {
let template = self 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()? .default_template()?
.unwrap_or_else(|| TiledPaneLayout::default()); .unwrap_or_else(|| TiledPaneLayout::default());
Some((default_tab_tiled_panes_template, child_floating_panes))
};
Ok(Layout { Ok(Layout {
template: Some((template, child_floating_panes)), template,
swap_tiled_layouts, swap_tiled_layouts,
swap_floating_layouts, swap_floating_layouts,
..Default::default() ..Default::default()

View file

@ -4,6 +4,7 @@ use crate::data::{
Resize, SessionInfo, TabInfo, Resize, SessionInfo, TabInfo,
}; };
use crate::envs::EnvironmentVariables; use crate::envs::EnvironmentVariables;
use crate::home::{find_default_config_dir, get_layout_dir};
use crate::input::config::{Config, ConfigError, KdlError}; use crate::input::config::{Config, ConfigError, KdlError};
use crate::input::keybinds::Keybinds; use crate::input::keybinds::Keybinds;
use crate::input::layout::{Layout, PluginUserConfiguration, RunPlugin, RunPluginLocation}; 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::permission::{GrantedPermission, PermissionCache};
use crate::input::plugins::{PluginConfig, PluginTag, PluginType, PluginsConfig}; use crate::input::plugins::{PluginConfig, PluginTag, PluginType, PluginsConfig};
use crate::input::theme::{FrameConfig, Theme, Themes, UiConfig}; use crate::input::theme::{FrameConfig, Theme, Themes, UiConfig};
use crate::setup::{find_default_config_dir, get_layout_dir};
use kdl_layout_parser::KdlLayoutParser; use kdl_layout_parser::KdlLayoutParser;
use std::collections::{BTreeMap, HashMap, HashSet}; use std::collections::{BTreeMap, HashMap, HashSet};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@ -469,6 +469,7 @@ impl Action {
}, },
"MovePaneBackwards" => Ok(Action::MovePaneBackwards), "MovePaneBackwards" => Ok(Action::MovePaneBackwards),
"DumpScreen" => Ok(Action::DumpScreen(string, false)), "DumpScreen" => Ok(Action::DumpScreen(string, false)),
"DumpLayout" => Ok(Action::DumpLayout),
"NewPane" => { "NewPane" => {
if string.is_empty() { if string.is_empty() {
return Ok(Action::NewPane(None, None)); return Ok(Action::NewPane(None, None));
@ -756,6 +757,11 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
action_arguments, action_arguments,
kdl_action kdl_action
), ),
"DumpLayout" => parse_kdl_action_char_or_string_arguments!(
action_name,
action_arguments,
kdl_action
),
"NewPane" => parse_kdl_action_char_or_string_arguments!( "NewPane" => parse_kdl_action_char_or_string_arguments!(
action_name, action_name,
action_arguments, action_arguments,
@ -1423,6 +1429,15 @@ impl Options {
let attach_to_session = let attach_to_session =
kdl_property_first_arg_as_bool_or_error!(kdl_options, "attach_to_session") kdl_property_first_arg_as_bool_or_error!(kdl_options, "attach_to_session")
.map(|(v, _)| v); .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 { Ok(Options {
simplified_ui, simplified_ui,
theme, theme,
@ -1444,6 +1459,9 @@ impl Options {
session_name, session_name,
attach_to_session, attach_to_session,
auto_layout, 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 raw_swap_layouts: Option<(&str, &str)>, // raw_swap_layouts swap_layouts_file_name
cwd: Option<PathBuf>, cwd: Option<PathBuf>,
) -> Result<Self, ConfigError> { ) -> Result<Self, ConfigError> {
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 { let layout = kdl_layout_parser.parse().map_err(|e| match e {
ConfigError::KdlError(kdl_error) => { ConfigError::KdlError(kdl_error) => {
ConfigError::KdlError(kdl_error.add_src(file_name, String::from(raw_layout))) ConfigError::KdlError(kdl_error.add_src(file_name, String::from(raw_layout)))

View file

@ -3,11 +3,13 @@ pub mod consts;
pub mod data; pub mod data;
pub mod envs; pub mod envs;
pub mod errors; pub mod errors;
pub mod home;
pub mod input; pub mod input;
pub mod kdl; pub mod kdl;
pub mod pane_size; pub mod pane_size;
pub mod plugin_api; pub mod plugin_api;
pub mod position; pub mod position;
pub mod session_serialization;
pub mod setup; pub mod setup;
pub mod shared; pub mod shared;
@ -21,8 +23,8 @@ pub mod logging; // Requires log4rs
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
pub use ::{ pub use ::{
anyhow, async_channel, async_std, clap, interprocess, lazy_static, libc, miette, nix, anyhow, async_channel, async_std, clap, common_path, humantime, interprocess, lazy_static,
notify_debouncer_full, regex, serde, signal_hook, tempfile, termwiz, vte, libc, miette, nix, notify_debouncer_full, regex, serde, signal_hook, tempfile, termwiz, vte,
}; };
pub use ::prost; pub use ::prost;

View file

@ -1,6 +1,10 @@
use serde::{Deserialize, Serialize}; 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; use crate::position::Position;
/// Contains the position and size of a [`Pane`], or more generally of any terminal, measured /// 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)] #[derive(Eq, Clone, Copy, PartialEq, Debug, Serialize, Deserialize, Hash)]
pub struct Dimension { pub struct Dimension {
pub constraint: Constraint, pub constraint: Constraint,
inner: usize, pub(crate) inner: usize,
} }
impl Default for Dimension { impl Default for Dimension {
@ -106,7 +110,6 @@ impl Dimension {
let leftover = rounded - new_inner; let leftover = rounded - new_inner;
self.set_inner(rounded as usize); self.set_inner(rounded as usize);
leftover leftover
// self.set_inner(((percent / 100.0) * full_size as f64).round() as usize);
}, },
Constraint::Fixed(fixed_size) => { Constraint::Fixed(fixed_size) => {
self.set_inner(fixed_size); self.set_inner(fixed_size);
@ -137,6 +140,17 @@ pub enum Constraint {
Percent(f64), 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)] #[allow(clippy::derive_hash_xor_eq)]
impl Hash for Constraint { impl Hash for Constraint {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
@ -161,6 +175,26 @@ impl PaneGeom {
pub fn is_at_least_minimum_size(&self) -> bool { pub fn is_at_least_minimum_size(&self) -> bool {
self.rows.as_usize() > 0 && self.cols.as_usize() > 0 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 { impl Offset {

View file

@ -1170,6 +1170,7 @@ impl TryFrom<Action> for ProtobufAction {
| Action::NewInPlacePluginPane(..) | Action::NewInPlacePluginPane(..)
| Action::Deny | Action::Deny
| Action::Copy | Action::Copy
| Action::DumpLayout
| Action::SkipConfirm(..) => Err("Unsupported action"), | Action::SkipConfirm(..) => Err("Unsupported action"),
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,7 @@ use crate::{
ZELLIJ_DEFAULT_THEMES, ZELLIJ_PROJ_DIR, ZELLIJ_DEFAULT_THEMES, ZELLIJ_PROJ_DIR,
}, },
errors::prelude::*, errors::prelude::*,
home::*,
input::{ input::{
config::{Config, ConfigError}, config::{Config, ConfigError},
layout::Layout, layout::Layout,
@ -17,12 +18,17 @@ use crate::{
use clap::{Args, IntoApp}; use clap::{Args, IntoApp};
use clap_complete::Shell; use clap_complete::Shell;
use directories::BaseDirs; use directories::BaseDirs;
use log::info;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ 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"; const CONFIG_NAME: &str = "config.kdl";
static ARROW_SEPARATOR: &str = ""; static ARROW_SEPARATOR: &str = "";
@ -107,6 +113,7 @@ pub fn get_layout_dir(config_dir: Option<PathBuf>) -> Option<PathBuf> {
pub fn get_theme_dir(config_dir: Option<PathBuf>) -> Option<PathBuf> { pub fn get_theme_dir(config_dir: Option<PathBuf>) -> Option<PathBuf> {
config_dir.map(|dir| dir.join("themes")) config_dir.map(|dir| dir.join("themes"))
} }
pub fn dump_asset(asset: &[u8]) -> std::io::Result<()> { pub fn dump_asset(asset: &[u8]) -> std::io::Result<()> {
std::io::stdout().write_all(asset)?; std::io::stdout().write_all(asset)?;
Ok(()) Ok(())
@ -196,6 +203,17 @@ pub const ZSH_AUTO_START_SCRIPT: &[u8] = include_bytes!(concat!(
"assets/shell/auto-start.zsh" "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<()> { pub fn dump_default_config() -> std::io::Result<()> {
dump_asset(DEFAULT_CONFIG) dump_asset(DEFAULT_CONFIG)
} }
@ -206,10 +224,24 @@ pub fn dump_specified_layout(layout: &str) -> std::io::Result<()> {
"default" => dump_asset(DEFAULT_LAYOUT), "default" => dump_asset(DEFAULT_LAYOUT),
"compact" => dump_asset(COMPACT_BAR_LAYOUT), "compact" => dump_asset(COMPACT_BAR_LAYOUT),
"disable-status" => dump_asset(NO_STATUS_LAYOUT), "disable-status" => dump_asset(NO_STATUS_LAYOUT),
not_found => Err(std::io::Error::new( custom => {
std::io::ErrorKind::Other, info!("Dump {custom} layout");
format!("Layout: {} not found", not_found), 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)] #[clap(long, value_parser)]
pub check: bool, pub check: bool,
/// Dump the specified layout file to stdout /// Dump specified layout to stdout
#[clap(long, value_parser)] #[clap(long, value_parser)]
pub dump_layout: Option<String>, pub dump_layout: Option<String>,
@ -376,7 +408,7 @@ impl Setup {
} }
if let Some(layout) = &self.dump_layout { if let Some(layout) = &self.dump_layout {
dump_specified_layout(layout)?; dump_specified_layout(&layout)?;
std::process::exit(0); std::process::exit(0);
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/setup.rs source: zellij-utils/src/setup.rs
assertion_line: 597 assertion_line: 686
expression: "format!(\"{:#?}\", options)" expression: "format!(\"{:#?}\", options)"
--- ---
Options { Options {
@ -26,4 +26,7 @@ Options {
session_name: None, session_name: None,
attach_to_session: None, attach_to_session: None,
auto_layout: None, auto_layout: None,
session_serialization: None,
serialize_pane_viewport: None,
scrollback_lines_to_serialize: None,
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/setup.rs source: zellij-utils/src/setup.rs
assertion_line: 683 assertion_line: 715
expression: "format!(\"{:#?}\", layout)" expression: "format!(\"{:#?}\", layout)"
--- ---
Layout { Layout {
@ -21,6 +21,8 @@ Layout {
is_expanded_in_stack: false, is_expanded_in_stack: false,
exclude_from_sync: None, exclude_from_sync: None,
run_instructions_to_ignore: [], run_instructions_to_ignore: [],
hide_floating_panes: false,
pane_initial_contents: None,
}, },
[], [],
), ),

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/setup.rs source: zellij-utils/src/setup.rs
assertion_line: 625 assertion_line: 714
expression: "format!(\"{:#?}\", options)" expression: "format!(\"{:#?}\", options)"
--- ---
Options { Options {
@ -26,4 +26,7 @@ Options {
session_name: None, session_name: None,
attach_to_session: None, attach_to_session: None,
auto_layout: None, auto_layout: None,
session_serialization: None,
serialize_pane_viewport: None,
scrollback_lines_to_serialize: None,
} }

Some files were not shown because too many files have changed in this diff Show more