diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eaba314b..a20f5d0a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] * fix: forward OSC7 (cwd changes) to host terminal (https://github.com/zellij-org/zellij/pull/4867) * performance: reduce server memory usage by not copying the builtin plugins onto the heap (https://github.com/zellij-org/zellij/pull/5622) +* feat: upstream Yazelix graphics and stacked-pane fixes, named swap-layout selection and configurable pane-border styles (https://github.com/zellij-org/zellij/pull/5630) +* feat: distributions -> allow using Zellij as a library (https://github.com/zellij-org/zellij/pull/5630) +* fix: do not reuse client ids (https://github.com/zellij-org/zellij/pull/5630) ## [0.45.1] - 2026-08-28 * fix: nested-session detection over SSH (https://github.com/zellij-org/zellij/pull/5522) diff --git a/default-plugins/compact-bar/src/main.rs b/default-plugins/compact-bar/src/main.rs index 29975d9af..4ddf5ee24 100644 --- a/default-plugins/compact-bar/src/main.rs +++ b/default-plugins/compact-bar/src/main.rs @@ -60,7 +60,7 @@ struct State { persist: bool, is_first_run: bool, own_tab_index: Option, - own_client_id: u16, + own_client_id: ClientId, // Keybinding cache cached_keybinds: KeybindsVec, @@ -593,7 +593,7 @@ impl State { } } -fn bind_toggle_key_config(toggle_key: &str, client_id: u16) -> String { +fn bind_toggle_key_config(toggle_key: &str, client_id: ClientId) -> String { format!( r#" keybinds {{ diff --git a/default-plugins/compact-bar/src/tab.rs b/default-plugins/compact-bar/src/tab.rs index ac1602c39..f3a0f5dde 100644 --- a/default-plugins/compact-bar/src/tab.rs +++ b/default-plugins/compact-bar/src/tab.rs @@ -5,14 +5,14 @@ use zellij_tile::prelude::*; use zellij_tile_utils::style; fn cursors<'a>( - focused_clients: &'a [ClientId], + focused_client_slots: &'a [usize], colors: MultiplayerColors, ) -> (Vec>, usize) { // cursor section, text length let mut len = 0; let mut cursors = vec![]; - for client_id in focused_clients.iter() { - if let Some(color) = client_id_to_colors(*client_id, colors) { + for display_slot in focused_client_slots.iter() { + if let Some(color) = client_slot_to_colors(*display_slot, colors) { cursors.push(style!(color.1, color.0).paint(" ")); len += 1; } @@ -29,7 +29,7 @@ pub fn render_tab( separator: &str, dimmed: bool, ) -> LinePart { - let focused_clients = tab.other_focused_clients.as_slice(); + let focused_clients = tab.other_focused_client_slots.as_slice(); let separator_width = separator.width(); let alternate_tab_color = if is_alternate_tab { palette.ribbon_unselected.emphasis_1 diff --git a/default-plugins/tab-bar/src/tab.rs b/default-plugins/tab-bar/src/tab.rs index df44dbadb..119d4e5dd 100644 --- a/default-plugins/tab-bar/src/tab.rs +++ b/default-plugins/tab-bar/src/tab.rs @@ -5,14 +5,14 @@ use zellij_tile::prelude::*; use zellij_tile_utils::style; fn cursors<'a>( - focused_clients: &'a [ClientId], + focused_client_slots: &'a [usize], multiplayer_colors: MultiplayerColors, ) -> (Vec>, usize) { // cursor section, text length let mut len = 0; let mut cursors = vec![]; - for client_id in focused_clients.iter() { - if let Some(color) = client_id_to_colors(*client_id, multiplayer_colors) { + for display_slot in focused_client_slots.iter() { + if let Some(color) = client_slot_to_colors(*display_slot, multiplayer_colors) { cursors.push(style!(color.1, color.0).paint(" ")); len += 1; } @@ -29,7 +29,7 @@ pub fn render_tab( separator: &str, dimmed: bool, ) -> LinePart { - let focused_clients = tab.other_focused_clients.as_slice(); + let focused_clients = tab.other_focused_client_slots.as_slice(); let separator_width = separator.width(); let alternate_tab_color = if is_alternate_tab { diff --git a/example/default.kdl b/example/default.kdl index 2c5b2b236..a88e74bd8 100644 --- a/example/default.kdl +++ b/example/default.kdl @@ -293,6 +293,35 @@ load_plugins { // // pane_frames true +// pane_frame_style "titles" + +// Configure the look of the pane frame borders +// +// ui { +// pane_frames { +// // round the corners of single-line borders +// rounded_corners true +// // line style for all four sides of tiled pane frames +// // one of: single, double, heavy, dashed, heavy_dashed +// border_style "single" +// // per-side overrides, eg. a double top border with single sides +// border_top "double" +// border_right "single" +// border_bottom "single" +// border_left "single" +// // when two neighbouring panes disagree about a shared grid line +// // (pane frames off), that line falls back to border_style above +// // the same keys prefixed with "floating_" apply to floating panes, +// // they default to the values above +// floating_border_style "double" +// floating_border_top "double" +// floating_border_right "double" +// floating_border_bottom "double" +// floating_border_left "double" +// floating_border_rounded_corners false +// } +// } + // Toggle between having Zellij lay out panes according to a predefined set of layouts whenever possible // Options: // - true (default) diff --git a/example/distribution/Cargo.lock b/example/distribution/Cargo.lock new file mode 100644 index 000000000..d95991d3d --- /dev/null +++ b/example/distribution/Cargo.lock @@ -0,0 +1,4573 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy 0.7.34", +] + +[[package]] +name = "aho-corasick" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "ambient-authority" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +dependencies = [ + "backtrace", +] + +[[package]] +name = "arc-swap" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d78ce20460b82d3fa150275ed9d55e21064fc7951177baacf86a145c4a4b1f" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-channel" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-trait" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "aws-lc-rs" +version = "1.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core", + "base64", + "bytes", + "form_urlencoded", + "futures-util", + "http 1.3.1", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-extra" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef252edff26ddba56bbcdf2ee3307b8129acb86f5749b68990c168a6fcc9c76" +dependencies = [ + "axum", + "axum-core", + "bytes", + "cookie", + "futures-core", + "futures-util", + "http 1.3.1", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-server" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1df331683d982a0b9492b38127151e6453639cd34926eb9c07d4cd8c6d22bfc" +dependencies = [ + "arc-swap", + "bytes", + "either", + "fs-err", + "http 1.3.1", + "http-body", + "hyper", + "hyper-util", + "pin-project-lite", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cap-fs-ext" +version = "3.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e41cc18551193fe8fa6f15c1e3c799bc5ec9e2cfbfaa8ed46f37013e3e6c173c" +dependencies = [ + "cap-primitives", + "cap-std", + "io-lifetimes", + "windows-sys 0.59.0", +] + +[[package]] +name = "cap-primitives" +version = "3.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a1e394ed14f39f8bc26f59d4c0c010dbe7f0a1b9bafff451b1f98b67c8af62a" +dependencies = [ + "ambient-authority", + "fs-set-times", + "io-extras", + "io-lifetimes", + "ipnet", + "maybe-owned", + "rustix 1.1.2", + "rustix-linux-procfs", + "windows-sys 0.59.0", + "winx", +] + +[[package]] +name = "cap-rand" +version = "3.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0acb89ccf798a28683f00089d0630dfaceec087234eae0d308c05ddeaa941b40" +dependencies = [ + "ambient-authority", + "rand 0.8.5", +] + +[[package]] +name = "cap-std" +version = "3.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c0355ca583dd58f176c3c12489d684163861ede3c9efa6fd8bba314c984189" +dependencies = [ + "cap-primitives", + "io-extras", + "io-lifetimes", + "rustix 1.1.2", +] + +[[package]] +name = "cap-time-ext" +version = "3.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "491af520b8770085daa0466978c75db90368c71896523f2464214e38359b1a5b" +dependencies = [ + "ambient-authority", + "cap-primitives", + "iana-time-zone", + "once_cell", + "rustix 1.1.2", + "winx", +] + +[[package]] +name = "castaway" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" + +[[package]] +name = "cc" +version = "1.2.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "clap" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size 0.4.4", +] + +[[package]] +name = "clap_complete" +version = "4.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f84a88507dbd05c695f2cb5e8558e747179134005e9893882dec964190ed89" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "close_fds" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bc416f33de9d59e79e57560f450d21ff8393adcf1cdfc3e6d8fb93d5f88a2ed" +dependencies = [ + "cfg-if", + "libc", +] + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "colorsys" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dfdf9179d546b55ff3f88c9d93ecfaa3e9760163da5a1080af5243230dbbb70" + +[[package]] +name = "concurrent-queue" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "regex", + "terminal_size 0.1.17", + "unicode-width 0.1.14", + "winapi", +] + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845" +dependencies = [ + "cfg-if", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.11.0", + "crossterm_winapi", + "document-features", + "mio", + "parking_lot", + "rustix 1.1.2", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "curl" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe 0.1.5", + "openssl-sys", + "schannel", + "socket2 0.4.9", + "winapi", +] + +[[package]] +name = "curl-sys" +version = "0.4.68+curl-8.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a0d18d88360e374b16b2273c832b5e57258ffc1d4aa4f96b108e0738d5752f" +dependencies = [ + "cc", + "libc", + "libnghttp2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "windows-sys 0.48.0", +] + +[[package]] +name = "daemonix" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0b747381562a10fd2104e9333ad72fe5158d79de81b64426fc9e229c6d3fb38" +dependencies = [ + "libc", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.96", +] + +[[package]] +name = "dialoguer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" +dependencies = [ + "console", + "shell-words", + "thiserror 1.0.61", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.6", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", +] + +[[package]] +name = "directories" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" +dependencies = [ + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys 0.3.7", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users 0.4.3", + "winapi", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "doctest-file" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + +[[package]] +name = "embed-resource" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml", + "vswhom", + "winreg", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "event-listener" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" + +[[package]] +name = "example-distribution" +version = "0.1.0" +dependencies = [ + "zellij", +] + +[[package]] +name = "example-distribution-plugin" +version = "0.1.0" +dependencies = [ + "zellij-tile", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "fd-lock" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" +dependencies = [ + "cfg-if", + "rustix 0.38.44", + "windows-sys 0.52.0", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "file-id" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bc904b9bbefcadbd8e3a9fb0d464a9b979de6324c03b3c663e8994f46a5be36" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs-err" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f89bda4c2a21204059a977ed3bfe746677dfd137b83c339e702b0ac91d482aa" +dependencies = [ + "autocfg", + "tokio", +] + +[[package]] +name = "fs-set-times" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "033b337d725b97690d86893f9de22b67b80dcc4e9ad815f348254c38119db8fb" +dependencies = [ + "io-lifetimes", + "rustix 0.38.44", + "windows-sys 0.52.0", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.7.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "h2" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.3.1", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash 0.1.4", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "highway" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c706f1711006204c2ba8fb1a7bd55f689bbf7feca9ff40325206b5e140cff6df" + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.3.1", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http 1.3.1", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "http 1.3.1", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core 0.52.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "include_dir" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "inotify" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "153be1941a183ec9ccd095ddbe17a8b8d435ef6c76e9e02451b933c3999af2c8" +dependencies = [ + "bitflags 2.11.0", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" +dependencies = [ + "libc", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "interprocess" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53bf2b0e0785c5394a7392f66d7c4fb9c653633c29b27a932280da3cb344c66a" +dependencies = [ + "doctest-file", + "futures-core", + "libc", + "recvmsg", + "tokio", + "widestring", + "windows-sys 0.52.0", +] + +[[package]] +name = "io-extras" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" +dependencies = [ + "io-lifetimes", + "windows-sys 0.59.0", +] + +[[package]] +name = "io-lifetimes" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a611371471e98973dbcab4e0ec66c31a10bc356eeb4d54a0e05eac8158fe38c" + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "isahc" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" +dependencies = [ + "async-channel", + "castaway", + "crossbeam-utils", + "curl", + "curl-sys", + "encoding_rs", + "event-listener", + "futures-lite", + "http 0.2.9", + "log", + "mime", + "once_cell", + "polling", + "slab", + "sluice", + "tracing", + "tracing-futures", + "url", + "waker-fn", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kasuari" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" +dependencies = [ + "hashbrown 0.16.1", + "thiserror 2.0.19", +] + +[[package]] +name = "kdl" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8388a371e0e2ede18bbd94e476fcd45b4ac65cefcedf0c06fd13bd8389574a6" +dependencies = [ + "miette 5.8.0", + "nom", + "thiserror 1.0.61", +] + +[[package]] +name = "kqueue" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags 2.11.0", + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "lev_distance" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d9d1bd215936bc8647ad92986bb56f3f216550b53c44ab785e3217ae33625e" + +[[package]] +name = "libc" +version = "0.2.176" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libnghttp2-sys" +version = "0.1.8+1.55.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fae956c192dadcdb5dace96db71fa0b827333cce7c7b38dc71446f024d8a340" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.11.0", + "libc", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "log-mdc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" + +[[package]] +name = "log4rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d36ca1786d9e79b8193a68d480a0907b612f109537115c6ff655a3a1967533fd" +dependencies = [ + "anyhow", + "arc-swap", + "chrono", + "derivative", + "fnv", + "log", + "log-mdc", + "parking_lot", + "thiserror 1.0.61", + "thread-id", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miette" +version = "5.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92a992891d5579caa9efd8e601f82e30a1caa79a27a5db075dde30ecb9eab357" +dependencies = [ + "miette-derive 5.8.0", + "once_cell", + "thiserror 1.0.61", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "backtrace", + "backtrace-ext", + "cfg-if", + "miette-derive 7.6.0", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size 0.4.4", + "textwrap", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "5.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c65c625186a9bcce6699394bee511e1b1aec689aa7e3be1bf4e996e75834153" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "names" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bddcd3bf5144b6392de80e04c347cd7fab2508f6df16a85fc496ecd5cec39bc" +dependencies = [ + "rand 0.8.5", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.11.0", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-debouncer-full" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d88b1a7538054351c8258338df7c931a590513fb3745e8c15eb9ff4199b8d1" +dependencies = [ + "file-id", + "log", + "notify", + "notify-types", + "walkdir", +] + +[[package]] +name = "notify-types" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "ntapi" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-src" +version = "300.5.4+3.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "owo-colors" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "smallvec", + "windows-targets 0.48.0", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +dependencies = [ + "cfg-if", + "libc", + "log", + "wepoll-ffi", + "winapi", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.0", + "zerocopy 0.8.17", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.0", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.10", +] + +[[package]] +name = "rand_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +dependencies = [ + "getrandom 0.3.1", + "zerocopy 0.8.17", +] + +[[package]] +name = "recvmsg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom 0.2.10", + "redox_syscall 0.2.13", + "thiserror 1.0.61", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.10", + "libredox", + "thiserror 2.0.19", +] + +[[package]] +name = "regex" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.10", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rusqlite" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78046161564f5e7cd9008aff3b2990b3850dc8e0349119b98e8f251e099f24d" +dependencies = [ + "bitflags 2.11.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustix-linux-procfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056" +dependencies = [ + "once_cell", + "rustix 1.1.2", +] + +[[package]] +name = "rustls" +version = "0.23.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe 0.2.1", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "shellexpand" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd1c7ddea665294d484c39fd0c0d2b7e35bbfe10035c5fe1854741a57f6880e1" +dependencies = [ + "dirs", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "sixel-image" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0351d4051c51d3d2900b26b61b87b0f142579c0d2c1bb09ec53d46b28370049f" +dependencies = [ + "sixel-tokenizer", +] + +[[package]] +name = "sixel-tokenizer" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71b7f6629ac964d60179c1fb00dfb80da265fc3465a17245e26c1eaf678d476" +dependencies = [ + "arrayvec", + "thiserror 1.0.61", +] + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "sluice" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" +dependencies = [ + "async-channel", + "futures-core", + "futures-io", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "string-interner" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23de088478b31c349c9ba67816fa55d9355232d63c3afea8bf513e31f0f1d2c0" +dependencies = [ + "hashbrown 0.15.2", + "serde", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte 0.14.1", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "suggest" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea0e0ed5f30fcbe4c59fa9d387c9e853c964109e6d046d2e56c8960d3969fb8" +dependencies = [ + "lev_distance", +] + +[[package]] +name = "supports-color" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" +dependencies = [ + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91" + +[[package]] +name = "supports-unicode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" + +[[package]] +name = "syn" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "sysinfo" +version = "0.39.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2071df9448915b71c4fe6d25deaf1c22f12bd234f01540b77312bb8e41361e6" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows", +] + +[[package]] +name = "system-interface" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b858526d22750088a9b3cf2e3c2aacebd5377f13adeec02860c30d09113010a6" +dependencies = [ + "bitflags 2.11.0", + "cap-fs-ext", + "cap-std", + "fd-lock", + "io-lifetimes", + "rustix 0.38.44", + "windows-sys 0.52.0", + "winx", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand 2.1.0", + "rustix 0.38.44", + "windows-sys 0.52.0", +] + +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix 1.1.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "unicode-linebreak", + "unicode-width 0.2.2", +] + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl 1.0.61", +] + +[[package]] +name = "thiserror" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl 2.0.19", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "thread-id" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fdfe0627923f7411a43ec9ec9c39c3a9b4151be313e0922042581fb6c9b717f" +dependencies = [ + "libc", + "redox_syscall 0.2.13", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.5.7", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae2a4cf385da23d1d53bc15cdfa5c2109e93d8d362393c801e87da2f72f0e201" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "http 1.3.1", + "pin-project-lite", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http 1.3.1", + "httparse", + "log", + "rand 0.9.0", + "sha1", + "thiserror 2.0.19", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +dependencies = [ + "getrandom 0.3.1", + "serde", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "vte" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5924018406ce0063cd67f8e008104968b74b563ee1b85dde3ed1f7cb87d3dbd" +dependencies = [ + "arrayvec", + "memchr", +] + +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasi-common" +version = "36.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a5eb67546a23b30761dc1e30325984e166e8f6961ae97e3e82d5a7570255838" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "cap-fs-ext", + "cap-rand", + "cap-std", + "cap-time-ext", + "fs-set-times", + "io-extras", + "io-lifetimes", + "log", + "rustix 1.1.2", + "system-interface", + "thiserror 2.0.19", + "tracing", + "wiggle", + "windows-sys 0.60.2", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.96", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wasmi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2300d0f78cba12f14e29e8dd157ea64050c0a688179aefdb2050105805594a0c" +dependencies = [ + "spin", + "wasmi_collections", + "wasmi_core", + "wasmi_ir", + "wasmparser", +] + +[[package]] +name = "wasmi_collections" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a8c42a2a76148d43097b1d7cc2a5bf33d5c23bd4dd69015fc887e311767884" +dependencies = [ + "string-interner", +] + +[[package]] +name = "wasmi_core" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9013136083d988725953390bf668b64b7a218fabf26f8b913bbc59546b97ee27" +dependencies = [ + "libm", +] + +[[package]] +name = "wasmi_ir" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1fa003f79156f406d62ef0e1464dc03e11ace37170e9fa7524299a75ad8f68" +dependencies = [ + "wasmi_core", +] + +[[package]] +name = "wasmi_wasi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2f1828ab16dcd236544fdd8fd8d9dab77de4b4413503fcb20fe59e69e27c34" +dependencies = [ + "wasi-common", + "wasmi", + "wiggle", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags 2.11.0", + "indexmap", +] + +[[package]] +name = "wast" +version = "35.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef140f1b49946586078353a453a1d28ba90adfc54dde75710bc1931de204d68" +dependencies = [ + "leb128", +] + +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "wiggle" +version = "36.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55a0751406b641ff50ef42d4a1ca843a03040c488c0c27f92093633447464013" +dependencies = [ + "anyhow", + "async-trait", + "bitflags 2.11.0", + "thiserror 2.0.19", + "tracing", + "wiggle-macro", +] + +[[package]] +name = "wiggle-generate" +version = "36.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab62083fdcecdd0cac61b8c46e7de4f2629ebe8699fd9ce790d922cc89d50f5f" +dependencies = [ + "anyhow", + "heck", + "proc-macro2", + "quote", + "syn 2.0.96", + "witx", +] + +[[package]] +name = "wiggle-macro" +version = "36.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756b7a4a7f57ee2f53e9ef3501ed0faacda4b8dcb169a921cddc8bc09ebd199e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "wiggle-generate", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core 0.62.2", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "winx" +version = "0.36.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9643b83820c0cd246ecabe5fa454dd04ba4fa67996369466d0747472d337346" +dependencies = [ + "bitflags 2.11.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "witx" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" +dependencies = [ + "anyhow", + "log", + "thiserror 1.0.61", + "wast", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + +[[package]] +name = "zellij" +version = "0.46.0" +dependencies = [ + "anyhow", + "clap", + "dialoguer", + "embed-resource", + "humantime", + "interprocess", + "isahc", + "log", + "miette 7.6.0", + "names", + "suggest", + "thiserror 2.0.19", + "zellij-client", + "zellij-server", + "zellij-utils", +] + +[[package]] +name = "zellij-client" +version = "0.46.0" +dependencies = [ + "anyhow", + "async-trait", + "axum", + "axum-extra", + "axum-server", + "crossterm", + "daemonix", + "dialoguer", + "futures", + "futures-util", + "include_dir", + "interprocess", + "isahc", + "lazy_static", + "log", + "nix", + "num_cpus", + "regex", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "signal-hook", + "time", + "tokio", + "tokio-rustls", + "tokio-tungstenite", + "tokio-util", + "tower-http", + "url", + "urlencoding", + "uuid", + "windows-sys 0.59.0", + "zellij-utils", +] + +[[package]] +name = "zellij-server" +version = "0.46.0" +dependencies = [ + "ansi_term", + "anyhow", + "arrayvec", + "async-trait", + "base64", + "byteorder", + "bytes", + "chrono", + "close_fds", + "crossbeam", + "daemonix", + "flate2", + "highway", + "interprocess", + "isahc", + "kasuari", + "kdl", + "lazy_static", + "libc", + "log", + "nix", + "notify-debouncer-full", + "num_cpus", + "once_cell", + "png", + "prost", + "regex", + "serde", + "serde_json", + "signal-hook", + "sixel-image", + "sixel-tokenizer", + "sysinfo", + "tempfile", + "tokio", + "unicode-width 0.2.2", + "url", + "uuid", + "vte 0.15.0", + "wasmi", + "wasmi_wasi", + "windows-sys 0.59.0", + "zellij-utils", +] + +[[package]] +name = "zellij-tile" +version = "0.46.0" +dependencies = [ + "prost", + "serde", + "serde_json", + "strum", + "strum_macros", + "zellij-utils", +] + +[[package]] +name = "zellij-utils" +version = "0.46.0" +dependencies = [ + "anyhow", + "backtrace", + "base64", + "bitflags 2.11.0", + "clap", + "clap_complete", + "colored", + "colorsys", + "crossbeam", + "crossterm", + "curl-sys", + "directories", + "dunce", + "humantime", + "include_dir", + "interprocess", + "isahc", + "kdl", + "lazy_static", + "libc", + "log", + "log4rs", + "miette 7.6.0", + "names", + "nix", + "notify", + "openssl-sys", + "percent-encoding", + "prost", + "rusqlite", + "serde", + "serde_json", + "sha2", + "shellexpand", + "strip-ansi-escapes", + "strum", + "strum_macros", + "suggest", + "tempfile", + "thiserror 2.0.19", + "tokio", + "tokio-stream", + "tokio-util", + "unicode-width 0.2.2", + "url", + "uuid", + "winapi", + "windows-sys 0.59.0", +] + +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive 0.7.34", +] + +[[package]] +name = "zerocopy" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713" +dependencies = [ + "zerocopy-derive 0.8.17", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] diff --git a/example/distribution/Cargo.toml b/example/distribution/Cargo.toml new file mode 100644 index 000000000..6ef1b2e2f --- /dev/null +++ b/example/distribution/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "example-distribution" +version = "0.1.0" +edition = "2021" +license = "MIT" +publish = false +build = "build.rs" + +[dependencies] +zellij = { path = "../.." } + +[workspace] +members = ["plugin"] diff --git a/example/distribution/assets/config.kdl b/example/distribution/assets/config.kdl new file mode 100644 index 000000000..73bde2033 --- /dev/null +++ b/example/distribution/assets/config.kdl @@ -0,0 +1,17 @@ +plugins { + example-plugin location="zellij:example-plugin" +} + +load_plugins { + "zellij:example-plugin" +} + +keybinds { + session { + unbind "s" + } +} + +default_layout "example" + +theme "gruvbox-dark" diff --git a/example/distribution/assets/layout.kdl b/example/distribution/assets/layout.kdl new file mode 100644 index 000000000..8c79c1ed6 --- /dev/null +++ b/example/distribution/assets/layout.kdl @@ -0,0 +1,14 @@ +layout { + pane size=1 borderless=true { + plugin location="tab-bar" + } + pane split_direction="vertical" { + pane + pane size="45%" { + plugin location="example-plugin" + } + } + pane size=1 borderless=true { + plugin location="status-bar" + } +} diff --git a/example/distribution/build.rs b/example/distribution/build.rs new file mode 100644 index 000000000..32c5b2b4d --- /dev/null +++ b/example/distribution/build.rs @@ -0,0 +1,71 @@ +use std::path::PathBuf; +use std::process::Command; + +const PLUGIN: &str = "example-distribution-plugin"; +const TARGET: &str = "wasm32-wasip1"; + +fn main() { + println!("cargo:rerun-if-changed=plugin/src"); + println!("cargo:rerun-if-changed=plugin/Cargo.toml"); + println!("cargo:rerun-if-env-changed=EXAMPLE_DISTRIBUTION_PLUGIN_WASM"); + + if let Ok(prebuilt) = std::env::var("EXAMPLE_DISTRIBUTION_PLUGIN_WASM") { + println!("cargo:rustc-env=EXAMPLE_DISTRIBUTION_PLUGIN_WASM={}", prebuilt); + return; + } + + let out_dir = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR is set by cargo")); + let target_dir = out_dir.join("plugin-target"); + let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned()); + + let mut command = Command::new(cargo); + command + .arg("build") + .arg("--release") + .args(["--target", TARGET]) + .args(["--package", PLUGIN]) + .arg("--target-dir") + .arg(&target_dir) + .env_remove("CARGO_MAKEFLAGS") + .env_remove("CARGO_ENCODED_RUSTFLAGS") + .env_remove("RUSTFLAGS") + .env_remove("CARGO_TARGET_DIR") + .env_remove("CARGO_BUILD_TARGET"); + + let status = command + .status() + .unwrap_or_else(|e| panic!("failed to run cargo to build '{}': {}", PLUGIN, e)); + + if !status.success() { + panic!( + "failed to build '{}' for {}. If the target is missing, install it with:\n \ + rustup target add {}", + PLUGIN, TARGET, TARGET + ); + } + + let wasm = target_dir + .join(TARGET) + .join("release") + .join(format!("{}.wasm", PLUGIN.replace('-', "_"))); + let wasm = if wasm.exists() { + wasm + } else { + target_dir + .join(TARGET) + .join("release") + .join(format!("{}.wasm", PLUGIN)) + }; + + assert!( + wasm.exists(), + "'{}' built successfully but no wasm artifact was found at '{}'", + PLUGIN, + wasm.display() + ); + + println!( + "cargo:rustc-env=EXAMPLE_DISTRIBUTION_PLUGIN_WASM={}", + wasm.display() + ); +} diff --git a/example/distribution/plugin/Cargo.toml b/example/distribution/plugin/Cargo.toml new file mode 100644 index 000000000..5fdc99b4c --- /dev/null +++ b/example/distribution/plugin/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "example-distribution-plugin" +version = "0.1.0" +edition = "2021" +license = "MIT" +publish = false + +[dependencies] +zellij-tile = { path = "../../../zellij-tile" } diff --git a/example/distribution/plugin/src/main.rs b/example/distribution/plugin/src/main.rs new file mode 100644 index 000000000..0eb9fa285 --- /dev/null +++ b/example/distribution/plugin/src/main.rs @@ -0,0 +1,103 @@ +use std::collections::BTreeMap; +use zellij_tile::prelude::*; + +#[derive(Default)] +struct State { + session_name: String, + connected_clients: usize, + tabs: usize, + panes: usize, + updates_received: usize, + permission_prompts_seen: usize, +} + +register_plugin!(State); + +impl ZellijPlugin for State { + fn load(&mut self, _configuration: BTreeMap) { + subscribe(&[ + EventType::SessionUpdate, + EventType::PermissionRequestResult, + ]); + } + + fn update(&mut self, event: Event) -> bool { + match event { + Event::SessionUpdate(sessions, _) => { + if let Some(session) = sessions.iter().find(|s| s.is_current_session) { + self.session_name = session.name.clone(); + self.connected_clients = session.connected_clients; + self.tabs = session.tabs.len(); + self.panes = session.panes.panes.values().map(|p| p.len()).sum(); + } + self.updates_received += 1; + true + }, + Event::PermissionRequestResult(_) => { + self.permission_prompts_seen += 1; + true + }, + _ => false, + } + } + + fn render(&mut self, _rows: usize, cols: usize) { + let mut row = 0; + let line = |text: Text, row: &mut usize| { + print_text_with_coordinates(text, 0, *row, Some(cols), None); + *row += 1; + }; + + line( + Text::new("Example Distribution plugin").color_range(2, ..), + &mut row, + ); + row += 1; + + line(Text::new("bundled as: zellij:example-plugin"), &mut row); + line( + Text::new(format!( + "permission prompts: {}", + self.permission_prompts_seen + )) + .color_range( + if self.permission_prompts_seen == 0 { 3 } else { 1 }, + .., + ), + &mut row, + ); + row += 1; + + if self.updates_received == 0 { + line(Text::new("waiting for SessionUpdate..."), &mut row); + } else { + line( + Text::new(format!("session: {}", self.session_name)), + &mut row, + ); + line( + Text::new(format!( + "clients: {} tabs: {} panes: {}", + self.connected_clients, self.tabs, self.panes + )), + &mut row, + ); + line( + Text::new(format!("SessionUpdate events: {}", self.updates_received)), + &mut row, + ); + } + row += 1; + + for note in [ + "SessionUpdate is gated behind the", + "ReadApplicationState permission.", + "This plugin never called", + "request_permission() and was never", + "prompted, because the distribution", + "bundles it into the executable.", + ] { + line(Text::new(note), &mut row); + } + } +} diff --git a/example/distribution/src/main.rs b/example/distribution/src/main.rs new file mode 100644 index 000000000..e9e747d78 --- /dev/null +++ b/example/distribution/src/main.rs @@ -0,0 +1,18 @@ +use zellij::{Distribution, DistributionLayout}; + +fn main() { + zellij::run( + Distribution::new("example-distribution", env!("CARGO_PKG_VERSION")) + .with_display_name("Example Distribution") + .with_plugin( + "example-plugin", + include_bytes!(env!("EXAMPLE_DISTRIBUTION_PLUGIN_WASM")), + ) + .without_plugin("share") + .with_config(include_str!("../assets/config.kdl")) + .with_layout(DistributionLayout::new( + "example", + include_str!("../assets/layout.kdl"), + )), + ) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..40a93e984 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,442 @@ +pub mod commands; +#[cfg(test)] +mod tests; + +use zellij_utils::{ + cli::{CliAction, CliArgs, Command, Sessions}, + consts::{create_config_and_cache_folders, VERSION}, + data::UnblockCondition, + distribution::set_distribution, + envs, + input::config::Config, + logging::*, + setup::Setup, + shared::web_server_base_url_from_config, +}; + +pub use zellij_utils::distribution::{Distribution, DistributionLayout, DistributionPlugin}; + +/// Run Zellij as the given distribution. +/// +/// A distribution bundles its own plugins, configuration and layouts into the executable. Its +/// plugins are referenced as `zellij:`, are loaded from memory rather than from disk, and +/// are not subject to the plugin permission system, exactly as Zellij's own builtin plugins are. +/// +/// Must be called at most once, before anything else in the process reads configuration. +pub fn run(distribution: Distribution) { + if let Err(e) = set_distribution(distribution) { + eprintln!("Failed to start: {}", e); + std::process::exit(2); + } + main() +} + +/// Run stock Zellij +pub fn run_zellij() { + main() +} + +fn main() { + configure_logger(); + create_config_and_cache_folders(); + let opts = CliArgs::parse_for_distribution(); + + { + let config = Config::try_from(&opts).ok(); + if let Some(Command::Action(cli_action)) = opts.command { + commands::send_action_to_session(*cli_action, opts.session, config); + std::process::exit(0); + } + if let Some(Command::Subscribe(subscribe_cli)) = opts.command { + commands::subscribe_to_session(subscribe_cli, opts.session, config); + std::process::exit(0); + } + if let Some(Command::Sessions(Sessions::Run { + command, + direction, + cwd, + floating, + in_place, + close_replaced_pane, + name, + close_on_exit, + start_suspended, + x, + y, + width, + height, + pinned, + stacked, + blocking, + block_until_exit_success, + block_until_exit_failure, + block_until_exit, + near_current_pane, + no_focus, + borderless, + border_style, + tab_id, + })) = opts.command + { + let cwd = cwd.or_else(|| std::env::current_dir().ok()); + let skip_plugin_cache = false; // N/A for this action + + // Compute the unblock condition + let unblock_condition = if block_until_exit_success { + Some(UnblockCondition::OnExitSuccess) + } else if block_until_exit_failure { + Some(UnblockCondition::OnExitFailure) + } else if block_until_exit { + Some(UnblockCondition::OnAnyExit) + } else { + None + }; + + let command_cli_action = CliAction::NewPane { + command, + plugin: None, + direction, + cwd, + floating, + in_place, + close_replaced_pane, + pane_id: None, + name, + close_on_exit, + start_suspended, + configuration: None, + skip_plugin_cache, + x, + y, + width, + height, + pinned, + stacked, + blocking, + block_until_exit_success: false, + block_until_exit_failure: false, + block_until_exit: false, + unblock_condition, + near_current_pane, + no_focus, + borderless, + border_style, + tab_id, + }; + commands::send_action_to_session(command_cli_action, opts.session, config); + std::process::exit(0); + } + if let Some(Command::Sessions(Sessions::Plugin { + url, + floating, + in_place, + close_replaced_pane, + configuration, + skip_plugin_cache, + x, + y, + width, + height, + pinned, + no_focus, + borderless, + border_style, + tab_id, + })) = opts.command + { + let cwd = None; + let stacked = false; + let blocking = false; + let unblock_condition = None; + let command_cli_action = CliAction::NewPane { + command: vec![], + plugin: Some(url), + direction: None, + cwd, + floating, + in_place, + close_replaced_pane, + pane_id: None, + name: None, + close_on_exit: false, + start_suspended: false, + configuration, + skip_plugin_cache, + x, + y, + width, + height, + pinned, + stacked, + blocking, + block_until_exit_success: false, + block_until_exit_failure: false, + block_until_exit: false, + unblock_condition, + near_current_pane: false, + no_focus, + borderless, + border_style, + tab_id, + }; + commands::send_action_to_session(command_cli_action, opts.session, config); + std::process::exit(0); + } + if let Some(Command::Sessions(Sessions::Edit { + file, + direction, + line_number, + floating, + in_place, + close_replaced_pane, + cwd, + x, + y, + width, + height, + pinned, + near_current_pane, + no_focus, + borderless, + border_style, + tab_id, + })) = opts.command + { + let mut file = file; + let cwd = cwd.or_else(|| std::env::current_dir().ok()); + if file.is_relative() { + if let Some(cwd) = cwd.as_ref() { + file = cwd.join(file); + } + } + let command_cli_action = CliAction::Edit { + file, + direction, + line_number, + floating, + in_place, + close_replaced_pane, + cwd, + x, + y, + width, + height, + pinned, + near_current_pane, + no_focus, + borderless, + border_style, + tab_id, + }; + commands::send_action_to_session(command_cli_action, opts.session, config); + std::process::exit(0); + } + if let Some(Command::Sessions(Sessions::Pipe { + name, + payload, + args, + plugin, + plugin_configuration, + })) = opts.command + { + let command_cli_action = CliAction::Pipe { + name, + payload, + args, + plugin, + plugin_configuration, + + force_launch_plugin: false, + skip_plugin_cache: false, + floating_plugin: None, + in_place_plugin: None, + plugin_cwd: None, + plugin_title: None, + }; + commands::send_action_to_session(command_cli_action, opts.session, config); + std::process::exit(0); + } + } + + if let Some(Command::Sessions(Sessions::ListSessions { + no_formatting, + short, + reverse, + })) = opts.command + { + commands::list_sessions(no_formatting, short, reverse); + } else if let Some(Command::Sessions(Sessions::ListAliases)) = opts.command { + commands::list_aliases(opts); + } else if let Some(Command::Sessions(Sessions::Watch { ref session_name })) = opts.command { + commands::watch_session(session_name.clone(), opts); + } else if let Some(Command::Sessions(Sessions::KillAllSessions { yes })) = opts.command { + commands::kill_all_sessions(yes); + } else if let Some(Command::Sessions(Sessions::KillSession { ref target_session })) = + opts.command + { + commands::kill_session(target_session); + } else if let Some(Command::Sessions(Sessions::DeleteAllSessions { yes, force })) = opts.command + { + commands::delete_all_sessions(yes, force); + } else if let Some(Command::Sessions(Sessions::DeleteSession { + ref target_session, + force, + })) = opts.command + { + commands::delete_session(target_session, force); + } else if let Some(path) = opts.server { + commands::start_server(path, opts.debug); + } else if opts.layout.is_some() || opts.layout_string.is_some() { + if let Some(session_name) = opts + .session + .as_ref() + .cloned() + .or_else(|| envs::get_session_name().ok()) + { + let config = Config::try_from(&opts).ok(); + let options = Setup::from_cli_args(&opts).ok().map(|r| r.2); + let new_layout_cli_action = CliAction::NewTab { + layout: opts.layout.clone(), + layout_string: opts.layout_string.clone(), + layout_dir: options.as_ref().and_then(|o| o.layout_dir.clone()), + name: None, + cwd: options.as_ref().and_then(|o| o.default_cwd.clone()), + initial_command: vec![], + initial_plugin: None, + close_on_exit: Default::default(), + start_suspended: Default::default(), + block_until_exit_success: false, + block_until_exit_failure: false, + block_until_exit: false, + no_focus: false, + }; + commands::send_action_to_session(new_layout_cli_action, Some(session_name), config); + } else { + commands::start_client(opts); + } + } else if let Some(layout_for_new_session) = &opts.new_session_with_layout { + let mut opts = opts.clone(); + opts.new_session_with_layout = None; + opts.layout = Some(layout_for_new_session.clone()); + commands::start_client(opts); + } else if let Some(Command::Web(web_opts)) = &opts.command { + if web_opts.get_start() { + let daemonize = web_opts.daemonize; + commands::start_web_server( + opts.clone(), + daemonize, + web_opts.ip, + web_opts.port, + web_opts.cert.clone(), + web_opts.key.clone(), + web_opts.server_startup_timeout, + ); + } else if web_opts.stop { + match commands::stop_web_server() { + Ok(()) => { + println!("Stopped web server."); + }, + Err(e) => { + eprintln!("Failed to stop web server: {}", e); + std::process::exit(2) + }, + } + } else if web_opts.status { + let mut config_options = commands::get_config_options_from_cli_args(&opts) + .expect("Can't find config options"); + if let Some(ip) = web_opts.ip { + config_options.web_server_ip = Some(ip); + } + if let Some(port) = web_opts.port { + config_options.web_server_port = Some(port); + } + let web_server_base_url = web_server_base_url_from_config(config_options); + match commands::web_server_status(&web_server_base_url, web_opts.timeout) { + Ok(version) => { + let version = version.trim(); + println!( + "Web server online with version: {}. Checked: {}", + version, web_server_base_url + ); + if version != VERSION { + println!(""); + println!( + "Note: this version differs from the current Zellij version: {}.", + VERSION + ); + println!("Consider stopping the server with: zellij web --stop"); + println!("And then restarting it with: zellij web --start"); + } + }, + Err(_e) => { + println!("Web server is offline, checked: {}", web_server_base_url); + }, + } + } else if web_opts.create_token { + let read_only = false; + match commands::create_auth_token(web_opts.token_name.clone(), read_only) { + Ok(token_and_name) => { + println!("Created token successfully"); + println!(""); + println!("{}", token_and_name); + }, + Err(e) => { + eprintln!("Failed to create token: {}", e); + std::process::exit(2) + }, + } + } else if web_opts.create_read_only_token { + let read_only = true; + match commands::create_auth_token(web_opts.token_name.clone(), read_only) { + Ok(token_and_name) => { + println!("Created token successfully"); + println!(""); + println!("{}", token_and_name); + }, + Err(e) => { + eprintln!("Failed to create token: {}", e); + std::process::exit(2) + }, + } + } else if let Some(token_name_to_revoke) = &web_opts.revoke_token { + match commands::revoke_auth_token(token_name_to_revoke) { + Ok(revoked) => { + if revoked { + println!("Successfully revoked token."); + } else { + eprintln!("Token by that name does not exist."); + std::process::exit(2) + } + }, + Err(e) => { + eprintln!("Failed to revoke token: {}", e); + std::process::exit(2) + }, + } + } else if web_opts.revoke_all_tokens { + match commands::revoke_all_auth_tokens() { + Ok(_) => { + println!("Successfully revoked all auth tokens"); + }, + Err(e) => { + eprintln!("Failed to revoke all auth tokens: {}", e); + std::process::exit(2) + }, + } + } else if web_opts.list_tokens { + match commands::list_auth_tokens() { + Ok(token_list) => { + for item in token_list { + println!("{}", item); + } + }, + Err(e) => { + eprintln!("Failed to list tokens: {}", e); + std::process::exit(2) + }, + } + } + } else { + commands::start_client(opts); + } +} diff --git a/src/main.rs b/src/main.rs index 15ed027a1..7aad30477 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,414 +1,3 @@ -mod commands; -#[cfg(test)] -mod tests; - -use clap::Parser; -use zellij_utils::{ - cli::{CliAction, CliArgs, Command, Sessions}, - consts::{create_config_and_cache_folders, VERSION}, - data::UnblockCondition, - envs, - input::config::Config, - logging::*, - setup::Setup, - shared::web_server_base_url_from_config, -}; - fn main() { - configure_logger(); - create_config_and_cache_folders(); - let opts = CliArgs::parse(); - - { - let config = Config::try_from(&opts).ok(); - if let Some(Command::Action(cli_action)) = opts.command { - commands::send_action_to_session(*cli_action, opts.session, config); - std::process::exit(0); - } - if let Some(Command::Subscribe(subscribe_cli)) = opts.command { - commands::subscribe_to_session(subscribe_cli, opts.session, config); - std::process::exit(0); - } - if let Some(Command::Sessions(Sessions::Run { - command, - direction, - cwd, - floating, - in_place, - close_replaced_pane, - name, - close_on_exit, - start_suspended, - x, - y, - width, - height, - pinned, - stacked, - blocking, - block_until_exit_success, - block_until_exit_failure, - block_until_exit, - near_current_pane, - no_focus, - borderless, - tab_id, - })) = opts.command - { - let cwd = cwd.or_else(|| std::env::current_dir().ok()); - let skip_plugin_cache = false; // N/A for this action - - // Compute the unblock condition - let unblock_condition = if block_until_exit_success { - Some(UnblockCondition::OnExitSuccess) - } else if block_until_exit_failure { - Some(UnblockCondition::OnExitFailure) - } else if block_until_exit { - Some(UnblockCondition::OnAnyExit) - } else { - None - }; - - let command_cli_action = CliAction::NewPane { - command, - plugin: None, - direction, - cwd, - floating, - in_place, - close_replaced_pane, - pane_id: None, - name, - close_on_exit, - start_suspended, - configuration: None, - skip_plugin_cache, - x, - y, - width, - height, - pinned, - stacked, - blocking, - block_until_exit_success: false, - block_until_exit_failure: false, - block_until_exit: false, - unblock_condition, - near_current_pane, - no_focus, - borderless, - tab_id, - }; - commands::send_action_to_session(command_cli_action, opts.session, config); - std::process::exit(0); - } - if let Some(Command::Sessions(Sessions::Plugin { - url, - floating, - in_place, - close_replaced_pane, - configuration, - skip_plugin_cache, - x, - y, - width, - height, - pinned, - no_focus, - borderless, - tab_id, - })) = opts.command - { - let cwd = None; - let stacked = false; - let blocking = false; - let unblock_condition = None; - let command_cli_action = CliAction::NewPane { - command: vec![], - plugin: Some(url), - direction: None, - cwd, - floating, - in_place, - close_replaced_pane, - pane_id: None, - name: None, - close_on_exit: false, - start_suspended: false, - configuration, - skip_plugin_cache, - x, - y, - width, - height, - pinned, - stacked, - blocking, - block_until_exit_success: false, - block_until_exit_failure: false, - block_until_exit: false, - unblock_condition, - near_current_pane: false, - no_focus, - borderless, - tab_id, - }; - commands::send_action_to_session(command_cli_action, opts.session, config); - std::process::exit(0); - } - if let Some(Command::Sessions(Sessions::Edit { - file, - direction, - line_number, - floating, - in_place, - close_replaced_pane, - cwd, - x, - y, - width, - height, - pinned, - near_current_pane, - no_focus, - borderless, - tab_id, - })) = opts.command - { - let mut file = file; - let cwd = cwd.or_else(|| std::env::current_dir().ok()); - if file.is_relative() { - if let Some(cwd) = cwd.as_ref() { - file = cwd.join(file); - } - } - let command_cli_action = CliAction::Edit { - file, - direction, - line_number, - floating, - in_place, - close_replaced_pane, - cwd, - x, - y, - width, - height, - pinned, - near_current_pane, - no_focus, - borderless, - tab_id, - }; - commands::send_action_to_session(command_cli_action, opts.session, config); - std::process::exit(0); - } - if let Some(Command::Sessions(Sessions::Pipe { - name, - payload, - args, - plugin, - plugin_configuration, - })) = opts.command - { - let command_cli_action = CliAction::Pipe { - name, - payload, - args, - plugin, - plugin_configuration, - - force_launch_plugin: false, - skip_plugin_cache: false, - floating_plugin: None, - in_place_plugin: None, - plugin_cwd: None, - plugin_title: None, - }; - commands::send_action_to_session(command_cli_action, opts.session, config); - std::process::exit(0); - } - } - - if let Some(Command::Sessions(Sessions::ListSessions { - no_formatting, - short, - reverse, - })) = opts.command - { - commands::list_sessions(no_formatting, short, reverse); - } else if let Some(Command::Sessions(Sessions::ListAliases)) = opts.command { - commands::list_aliases(opts); - } else if let Some(Command::Sessions(Sessions::Watch { ref session_name })) = opts.command { - commands::watch_session(session_name.clone(), opts); - } else if let Some(Command::Sessions(Sessions::KillAllSessions { yes })) = opts.command { - commands::kill_all_sessions(yes); - } else if let Some(Command::Sessions(Sessions::KillSession { ref target_session })) = - opts.command - { - commands::kill_session(target_session); - } else if let Some(Command::Sessions(Sessions::DeleteAllSessions { yes, force })) = opts.command - { - commands::delete_all_sessions(yes, force); - } else if let Some(Command::Sessions(Sessions::DeleteSession { - ref target_session, - force, - })) = opts.command - { - commands::delete_session(target_session, force); - } else if let Some(path) = opts.server { - commands::start_server(path, opts.debug); - } else if opts.layout.is_some() || opts.layout_string.is_some() { - if let Some(session_name) = opts - .session - .as_ref() - .cloned() - .or_else(|| envs::get_session_name().ok()) - { - let config = Config::try_from(&opts).ok(); - let options = Setup::from_cli_args(&opts).ok().map(|r| r.2); - let new_layout_cli_action = CliAction::NewTab { - layout: opts.layout.clone(), - layout_string: opts.layout_string.clone(), - layout_dir: options.as_ref().and_then(|o| o.layout_dir.clone()), - name: None, - cwd: options.as_ref().and_then(|o| o.default_cwd.clone()), - initial_command: vec![], - initial_plugin: None, - close_on_exit: Default::default(), - start_suspended: Default::default(), - block_until_exit_success: false, - block_until_exit_failure: false, - block_until_exit: false, - no_focus: false, - }; - commands::send_action_to_session(new_layout_cli_action, Some(session_name), config); - } else { - commands::start_client(opts); - } - } else if let Some(layout_for_new_session) = &opts.new_session_with_layout { - let mut opts = opts.clone(); - opts.new_session_with_layout = None; - opts.layout = Some(layout_for_new_session.clone()); - commands::start_client(opts); - } else if let Some(Command::Web(web_opts)) = &opts.command { - if web_opts.get_start() { - let daemonize = web_opts.daemonize; - commands::start_web_server( - opts.clone(), - daemonize, - web_opts.ip, - web_opts.port, - web_opts.cert.clone(), - web_opts.key.clone(), - web_opts.server_startup_timeout, - ); - } else if web_opts.stop { - match commands::stop_web_server() { - Ok(()) => { - println!("Stopped web server."); - }, - Err(e) => { - eprintln!("Failed to stop web server: {}", e); - std::process::exit(2) - }, - } - } else if web_opts.status { - let mut config_options = commands::get_config_options_from_cli_args(&opts) - .expect("Can't find config options"); - if let Some(ip) = web_opts.ip { - config_options.web_server_ip = Some(ip); - } - if let Some(port) = web_opts.port { - config_options.web_server_port = Some(port); - } - let web_server_base_url = web_server_base_url_from_config(config_options); - match commands::web_server_status(&web_server_base_url, web_opts.timeout) { - Ok(version) => { - let version = version.trim(); - println!( - "Web server online with version: {}. Checked: {}", - version, web_server_base_url - ); - if version != VERSION { - println!(""); - println!( - "Note: this version differs from the current Zellij version: {}.", - VERSION - ); - println!("Consider stopping the server with: zellij web --stop"); - println!("And then restarting it with: zellij web --start"); - } - }, - Err(_e) => { - println!("Web server is offline, checked: {}", web_server_base_url); - }, - } - } else if web_opts.create_token { - let read_only = false; - match commands::create_auth_token(web_opts.token_name.clone(), read_only) { - Ok(token_and_name) => { - println!("Created token successfully"); - println!(""); - println!("{}", token_and_name); - }, - Err(e) => { - eprintln!("Failed to create token: {}", e); - std::process::exit(2) - }, - } - } else if web_opts.create_read_only_token { - let read_only = true; - match commands::create_auth_token(web_opts.token_name.clone(), read_only) { - Ok(token_and_name) => { - println!("Created token successfully"); - println!(""); - println!("{}", token_and_name); - }, - Err(e) => { - eprintln!("Failed to create token: {}", e); - std::process::exit(2) - }, - } - } else if let Some(token_name_to_revoke) = &web_opts.revoke_token { - match commands::revoke_auth_token(token_name_to_revoke) { - Ok(revoked) => { - if revoked { - println!("Successfully revoked token."); - } else { - eprintln!("Token by that name does not exist."); - std::process::exit(2) - } - }, - Err(e) => { - eprintln!("Failed to revoke token: {}", e); - std::process::exit(2) - }, - } - } else if web_opts.revoke_all_tokens { - match commands::revoke_all_auth_tokens() { - Ok(_) => { - println!("Successfully revoked all auth tokens"); - }, - Err(e) => { - eprintln!("Failed to revoke all auth tokens: {}", e); - std::process::exit(2) - }, - } - } else if web_opts.list_tokens { - match commands::list_auth_tokens() { - Ok(token_list) => { - for item in token_list { - println!("{}", item); - } - }, - Err(e) => { - eprintln!("Failed to list tokens: {}", e); - std::process::exit(2) - }, - } - } - } else { - commands::start_client(opts); - } + zellij::run_zellij() } diff --git a/src/tests/cli.rs b/src/tests/cli.rs index ba0ea6680..ef98f8256 100644 --- a/src/tests/cli.rs +++ b/src/tests/cli.rs @@ -3,14 +3,30 @@ use std::net::{IpAddr, Ipv4Addr}; use clap::{CommandFactory, Parser}; use zellij_utils::cli::{CliArgs, Command}; +const CLI_PARSE_TEST_STACK_SIZE: usize = 16 * 1024 * 1024; + +fn on_large_stack(f: impl FnOnce() -> T + Send + 'static) -> T { + std::thread::Builder::new() + .stack_size(CLI_PARSE_TEST_STACK_SIZE) + .spawn(f) + .expect("failed to spawn cli parse test thread") + .join() + .expect("cli parse test thread panicked") +} + +fn try_parse(args: &[&str]) -> Result { + let args: Vec = args.iter().map(|arg| arg.to_string()).collect(); + on_large_stack(move || CliArgs::try_parse_from(args)) +} + #[test] fn verify_cli() { - CliArgs::command().debug_assert(); + on_large_stack(|| CliArgs::command().debug_assert()); } #[test] fn web_cli_status_alone_works() { - let args = CliArgs::try_parse_from(["zellij", "web", "--status"]); + let args = try_parse(&["zellij", "web", "--status"]); assert!(args.is_ok()); if let Ok(CliArgs { command: Some(Command::Web(web)), @@ -26,7 +42,7 @@ fn web_cli_status_alone_works() { #[test] fn web_cli_status_with_timeout_works() { - let args = CliArgs::try_parse_from(["zellij", "web", "--status", "--timeout", "5"]); + let args = try_parse(&["zellij", "web", "--status", "--timeout", "5"]); assert!(args.is_ok()); if let Ok(CliArgs { command: Some(Command::Web(web)), @@ -43,7 +59,7 @@ fn web_cli_status_with_timeout_works() { #[test] fn web_cli_timeout_with_status_works() { // Test with --timeout before --status (order shouldn't matter) - let args = CliArgs::try_parse_from(["zellij", "web", "--timeout", "10", "--status"]); + let args = try_parse(&["zellij", "web", "--timeout", "10", "--status"]); assert!(args.is_ok()); if let Ok(CliArgs { command: Some(Command::Web(web)), @@ -59,25 +75,25 @@ fn web_cli_timeout_with_status_works() { #[test] fn web_cli_timeout_without_status_fails() { - let args = CliArgs::try_parse_from(["zellij", "web", "--timeout", "5"]); + let args = try_parse(&["zellij", "web", "--timeout", "5"]); assert!(args.is_err()); } #[test] fn web_cli_status_with_start_fails() { - let args = CliArgs::try_parse_from(["zellij", "web", "--status", "--start"]); + let args = try_parse(&["zellij", "web", "--status", "--start"]); assert!(args.is_err()); } #[test] fn web_cli_status_with_stop_fails() { - let args = CliArgs::try_parse_from(["zellij", "web", "--status", "--stop"]); + let args = try_parse(&["zellij", "web", "--status", "--stop"]); assert!(args.is_err()); } #[test] fn web_cli_status_with_ip_works() { - let args = CliArgs::try_parse_from(["zellij", "web", "--status", "--ip", "127.0.0.1"]); + let args = try_parse(&["zellij", "web", "--status", "--ip", "127.0.0.1"]); assert!(args.is_ok()); if let Ok(CliArgs { command: Some(Command::Web(web)), @@ -93,7 +109,7 @@ fn web_cli_status_with_ip_works() { #[test] fn web_cli_status_with_port_works() { - let args = CliArgs::try_parse_from(["zellij", "web", "--status", "--port", "9000"]); + let args = try_parse(&["zellij", "web", "--status", "--port", "9000"]); assert!(args.is_ok()); if let Ok(CliArgs { command: Some(Command::Web(web)), @@ -109,7 +125,7 @@ fn web_cli_status_with_port_works() { #[test] fn web_cli_status_with_ip_and_port_works() { - let args = CliArgs::try_parse_from([ + let args = try_parse(&[ "zellij", "web", "--status", "--ip", "0.0.0.0", "--port", "9000", ]); assert!(args.is_ok()); diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index ef41e9606..8e512a562 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -362,10 +362,12 @@ fn check_ipc_pipe_length(ipc_pipe: &Path) { "Error: the IPC socket path is too long ({} bytes, max {}):\n {}\n\n\ This is usually caused by a long $TMPDIR path.\n\ To fix this, set a shorter socket directory, eg.:\n \ - ZELLIJ_SOCKET_DIR=/tmp/zellij zellij", + ZELLIJ_SOCKET_DIR=/tmp/{} {}", path_len, ZELLIJ_SOCK_MAX_LENGTH - 1, - ipc_pipe.display() + ipc_pipe.display(), + zellij_utils::distribution::name(), + zellij_utils::distribution::name() ); std::process::exit(1); } @@ -408,14 +410,18 @@ fn exit_after_startup_error(teardown: Option, message: String) fn spawn_server_error_message(e: io::Error) -> String { format!( - "Error: failed to start the Zellij server process:\n\n\ + "Error: failed to start the {} server process:\n\n\ Reason: {}\n\n\ - This can happen if the Zellij binary cannot be executed, or if the server \ + This can happen if the {} binary cannot be executed, or if the server \ could not create its session socket - for example due to a permission issue \ in the socket directory.\n\ To fix a socket directory issue, set a writable socket directory, eg.:\n \ - ZELLIJ_SOCKET_DIR=/tmp/zellij-$USER zellij", - e + ZELLIJ_SOCKET_DIR=/tmp/{}-$USER {}", + zellij_utils::distribution::display_name(), + e, + zellij_utils::distribution::display_name(), + zellij_utils::distribution::name(), + zellij_utils::distribution::name() ) } @@ -425,15 +431,19 @@ fn create_ipc_pipe(teardown: Option) -> PathBuf { exit_after_startup_error( teardown, format!( - "Error: failed to create the Zellij socket directory:\n {}\n\n\ + "Error: failed to create the {} socket directory:\n {}\n\n\ Reason: {}\n\n\ This usually means the directory (or one of its parents) is owned by \ - another user or is not writable - for example if Zellij was previously \ + another user or is not writable - for example if {} was previously \ run with `sudo`, or if $XDG_RUNTIME_DIR points to a directory you do not \ own.\nTo fix this, remove or correct the offending directory, or set a \ - writable socket directory, eg.:\n ZELLIJ_SOCKET_DIR=/tmp/zellij-$USER zellij", + writable socket directory, eg.:\n ZELLIJ_SOCKET_DIR=/tmp/{}-$USER {}", + zellij_utils::distribution::display_name(), sock_dir.display(), - e + e, + zellij_utils::distribution::display_name(), + zellij_utils::distribution::name(), + zellij_utils::distribution::name() ), ); } @@ -441,13 +451,16 @@ fn create_ipc_pipe(teardown: Option) -> PathBuf { exit_after_startup_error( teardown, format!( - "Error: failed to set permissions (0700) on the Zellij socket directory:\n {}\n\n\ + "Error: failed to set permissions (0700) on the {} socket directory:\n {}\n\n\ Reason: {}\n\n\ This usually means the directory is owned by another user.\n\ To fix this, remove or correct the offending directory, or set a writable \ - socket directory, eg.:\n ZELLIJ_SOCKET_DIR=/tmp/zellij-$USER zellij", + socket directory, eg.:\n ZELLIJ_SOCKET_DIR=/tmp/{}-$USER {}", + zellij_utils::distribution::display_name(), sock_dir.display(), - e + e, + zellij_utils::distribution::name(), + zellij_utils::distribution::name() ), ); } diff --git a/zellij-client/src/web_client/server_listener.rs b/zellij-client/src/web_client/server_listener.rs index 585f31738..937db28a0 100644 --- a/zellij-client/src/web_client/server_listener.rs +++ b/zellij-client/src/web_client/server_listener.rs @@ -97,6 +97,11 @@ pub fn zellij_server_listener( colors: palette, rounded_corners: config.ui.pane_frames.rounded_corners, hide_session_name: config.ui.pane_frames.hide_session_name, + border_style: config.ui.pane_frames.resolved_border_style(), + floating_border_style: config + .ui + .pane_frames + .resolved_floating_border_style(), }, }; diff --git a/zellij-client/src/web_client/websocket_handlers.rs b/zellij-client/src/web_client/websocket_handlers.rs index 88be7b90f..330368373 100644 --- a/zellij-client/src/web_client/websocket_handlers.rs +++ b/zellij-client/src/web_client/websocket_handlers.rs @@ -368,6 +368,7 @@ fn control_payload_to_server_msg( no_focus: false, borderless: None, tab_id: None, + border_style: None, }, terminal_id: None, client_id: None, diff --git a/zellij-integration-tests/src/fake_server_os_api.rs b/zellij-integration-tests/src/fake_server_os_api.rs index 30111f19a..52ebebd1a 100644 --- a/zellij-integration-tests/src/fake_server_os_api.rs +++ b/zellij-integration-tests/src/fake_server_os_api.rs @@ -164,25 +164,23 @@ impl ServerOsApi for FakeServerOsApi { } Ok(()) } - fn new_client( + fn register_client( &mut self, client_id: ClientId, - stream: LocalSocketStream, - ) -> Result> { - let ipc_receiver = IpcReceiverWithContext::new(stream); - let non_blocking_client_sender = NonBlockingClientSender::new(ipc_receiver.get_sender()); + receiver: &IpcReceiverWithContext, + ) -> Result<()> { + let non_blocking_client_sender = NonBlockingClientSender::new(receiver.get_sender()); self.non_blocking_client_senders .lock() .unwrap() .insert(client_id, non_blocking_client_sender); - Ok(ipc_receiver) + Ok(()) } - fn new_client_with_reply( + fn register_client_with_reply( &mut self, _client_id: ClientId, - _stream: LocalSocketStream, _reply_stream: LocalSocketStream, - ) -> Result> { + ) -> Result<()> { unimplemented!("windows dual-pipe IPC is not used by the test harness") } fn remove_client(&mut self, client_id: ClientId) -> Result<()> { diff --git a/zellij-integration-tests/src/runner.rs b/zellij-integration-tests/src/runner.rs index c9d481ca4..15a02ee82 100644 --- a/zellij-integration-tests/src/runner.rs +++ b/zellij-integration-tests/src/runner.rs @@ -320,6 +320,7 @@ fn new_pane_cli_action( no_focus: false, borderless: None, tab_id: None, + border_style: None, } } diff --git a/zellij-integration-tests/tests/distribution.rs b/zellij-integration-tests/tests/distribution.rs new file mode 100644 index 000000000..fb28c56b4 --- /dev/null +++ b/zellij-integration-tests/tests/distribution.rs @@ -0,0 +1,134 @@ +#![cfg(unix)] + +use std::sync::Once; + +use zellij_integration_tests::{normalized, LayoutInfo, TestRunner, TERMINAL_SIZE}; +use zellij_utils::distribution::{self, Distribution, DistributionLayout}; + +const FIXTURE_PLUGIN: &[u8] = include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm" +)); + +const FIXTURE_PLUGIN_PATH: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm" +); + +const PERMISSION_PROMPT: &str = "Allow? (y/n)"; + +const BUNDLED_LAYOUT: &str = r#" +layout { + pane size=1 borderless=true { + plugin location="tab-bar" + } + pane + pane size=10 borderless=true { + plugin location="zellij:bundled-plugin" + } + pane size=2 borderless=true { + plugin location="status-bar" + } +} +"#; + +static INSTALL: Once = Once::new(); + +fn install_distribution() { + INSTALL.call_once(|| { + distribution::set_distribution( + Distribution::new("testdist", "9.9.9") + .with_display_name("Test Distribution") + .with_plugin("bundled-plugin", FIXTURE_PLUGIN) + .with_layout(DistributionLayout::new("bundled", BUNDLED_LAYOUT)), + ) + .expect("the test distribution should install"); + }); +} + +fn start_with_bundled_layout() -> zellij_integration_tests::TestSession { + install_distribution(); + TestRunner::new(TERMINAL_SIZE) + .with_layout(LayoutInfo::BuiltIn("bundled".to_owned())) + .start() +} + +#[test] +fn a_bundled_plugin_runs_without_asking_for_permissions() { + let mut zellij = start_with_bundled_layout(); + let terminal = zellij.expect_pty_spawn(); + terminal.output(b"$ "); + + let grid_snapshot = zellij.wait_until("bundled plugin rendered its own output", |grid| { + grid.contains("Received events:") && grid.tab_bar_appears() && grid.status_bar_appears() + }); + assert!( + !grid_snapshot.contains(PERMISSION_PROMPT), + "a bundled plugin must not ask for permissions, got:\n{}", + grid_snapshot.text + ); + zellij.quit(); +} + +#[test] +fn the_same_plugin_loaded_from_a_file_still_asks_for_permissions() { + install_distribution(); + let file_plugin_layout = format!( + r#" + layout {{ + pane size=1 borderless=true {{ + plugin location="tab-bar" + }} + pane + pane size=10 borderless=true {{ + plugin location="file:{}" + }} + pane size=2 borderless=true {{ + plugin location="status-bar" + }} + }} + "#, + FIXTURE_PLUGIN_PATH + ); + let mut zellij = TestRunner::new(TERMINAL_SIZE) + .with_layout(LayoutInfo::Stringified(file_plugin_layout)) + .start(); + let terminal = zellij.expect_pty_spawn(); + terminal.output(b"$ "); + + zellij.wait_until("file plugin stops on the permission prompt", |grid| { + grid.contains(PERMISSION_PROMPT) + }); + zellij.quit(); +} + +#[test] +fn a_bundled_layout_is_used_to_lay_out_the_session() { + let mut zellij = start_with_bundled_layout(); + let terminal = zellij.expect_pty_spawn(); + terminal.output(b"$ "); + + let grid_snapshot = zellij.wait_until("bundled layout rendered", |grid| { + grid.contains("Received events:") && grid.tab_bar_appears() && grid.status_bar_appears() + }); + insta::assert_snapshot!(normalized(&grid_snapshot)); + zellij.quit(); +} + +#[test] +fn the_session_runs_under_the_distributions_own_directories() { + let mut zellij = start_with_bundled_layout(); + let terminal = zellij.expect_pty_spawn(); + terminal.output(b"$ "); + zellij.wait_until("app loaded", |grid| { + grid.tab_bar_appears() && grid.status_bar_appears() + }); + + let cache_dir = zellij_utils::consts::ZELLIJ_CACHE_DIR.display().to_string(); + assert!( + cache_dir.contains("testdist"), + "the running session should use the distribution's cache dir, got '{}'", + cache_dir + ); + zellij.quit(); +} diff --git a/zellij-integration-tests/tests/no_focus.rs b/zellij-integration-tests/tests/no_focus.rs index 5722a019b..85c06b2ad 100644 --- a/zellij-integration-tests/tests/no_focus.rs +++ b/zellij-integration-tests/tests/no_focus.rs @@ -36,6 +36,7 @@ fn no_focus_new_pane_action(command: &[&str]) -> CliAction { no_focus: true, borderless: None, tab_id: None, + border_style: None, } } diff --git a/zellij-integration-tests/tests/single_pane_tab_title.rs b/zellij-integration-tests/tests/single_pane_tab_title.rs index cc70a1632..2d134e3dc 100644 --- a/zellij-integration-tests/tests/single_pane_tab_title.rs +++ b/zellij-integration-tests/tests/single_pane_tab_title.rs @@ -40,6 +40,7 @@ fn replace_pane_in_place_with_held_command( no_focus: false, borderless: None, tab_id: None, + border_style: None, }); zellij.expect_pty_spawn() } diff --git a/zellij-integration-tests/tests/snapshots/distribution__a_bundled_layout_is_used_to_lay_out_the_session.snap b/zellij-integration-tests/tests/snapshots/distribution__a_bundled_layout_is_used_to_lay_out_the_session.snap new file mode 100644 index 000000000..49042c6ef --- /dev/null +++ b/zellij-integration-tests/tests/snapshots/distribution__a_bundled_layout_is_used_to_lay_out_the_session.snap @@ -0,0 +1,9 @@ +--- +source: zellij-integration-tests/tests/distribution.rs +expression: normalized(&grid_snapshot) +--- + Zellij (test)  Tab #1  +  +$ █ +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── +Rows: 10, Cols: 120, Received events: [] + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  BASE  diff --git a/zellij-integration-tests/tests/snapshots/title_frame_mouse__mouse_click_new_tab_button_in_tab_bar_opens_a_new_tab.snap b/zellij-integration-tests/tests/snapshots/title_frame_mouse__mouse_click_new_tab_button_in_tab_bar_opens_a_new_tab.snap index f05439e37..8d9892dfe 100644 --- a/zellij-integration-tests/tests/snapshots/title_frame_mouse__mouse_click_new_tab_button_in_tab_bar_opens_a_new_tab.snap +++ b/zellij-integration-tests/tests/snapshots/title_frame_mouse__mouse_click_new_tab_button_in_tab_bar_opens_a_new_tab.snap @@ -4,4 +4,4 @@ expression: normalized(&grid_snapshot) --- Zellij (test)  Tab #1  Tab #2  +  $ █ - + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  ... diff --git a/zellij-integration-tests/tests/title_frame_mouse.rs b/zellij-integration-tests/tests/title_frame_mouse.rs index 68467068e..1dae13744 100644 --- a/zellij-integration-tests/tests/title_frame_mouse.rs +++ b/zellij-integration-tests/tests/title_frame_mouse.rs @@ -151,7 +151,10 @@ fn mouse_click_new_tab_button_in_tab_bar_opens_a_new_tab() { new_tab_terminal.output(PROMPT); let grid_snapshot = zellij.wait_until("a second tab opened", |grid_snapshot| { - grid_snapshot.contains("Tab #1") && grid_snapshot.contains("Tab #2") + grid_snapshot.contains("Tab #1") + && grid_snapshot.contains("Tab #2") + && grid_snapshot.contains("$ ") + && grid_snapshot.status_bar_appears() }); assert_snapshot!(normalized(&grid_snapshot)); zellij.quit(); diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index c86de1231..da900a867 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -75,11 +75,17 @@ use zellij_utils::{ options::Options, plugins::PluginAliases, }, - ipc::{ClientAttributes, ExitReason, ServerToClientMsg}, + ipc::{ + ClientAttributes, ClientToServerMsg, ExitReason, IpcReceiverWithContext, ServerToClientMsg, + }, shared::{default_palette, web_server_base_url}, }; -pub type ClientId = u16; +pub use zellij_utils::data::ClientId; + +const ACCEPT_ERROR_BACKOFF: std::time::Duration = std::time::Duration::from_millis(100); + +const PRE_HANDSHAKE_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30); /// Instructions related to server-side application #[derive(Debug, Clone)] @@ -464,6 +470,11 @@ impl SessionMetaData { auto_layout: new_config.options.auto_layout.unwrap_or(true), rounded_corners: new_config.ui.pane_frames.rounded_corners, hide_session_name: new_config.ui.pane_frames.hide_session_name, + border_style: new_config.ui.pane_frames.resolved_border_style(), + floating_border_style: new_config + .ui + .pane_frames + .resolved_floating_border_style(), stacked_resize: new_config.options.stacked_resize.unwrap_or(true), stacked_pane_list: new_config.options.stacked_pane_list.unwrap_or(true), default_editor: new_config.options.scrollback_editor.clone(), @@ -640,6 +651,7 @@ pub(crate) struct SessionState { /// empty synthetic reply so `Screen`'s `forward_in_flight` slot /// releases and the queued forwards keep moving. forwards_in_flight: HashMap, + next_client_id: ClientId, } impl SessionState { @@ -650,26 +662,33 @@ impl SessionState { watchers: HashMap::new(), last_active_client: None, forwards_in_flight: HashMap::new(), + next_client_id: 1, } } pub fn new_client(&mut self) -> ClientId { - let all_ids: HashSet = self + let client_id = match self.next_client_id.checked_add(1) { + Some(following_client_id) => { + let client_id = self.next_client_id; + self.next_client_id = following_client_id; + client_id + }, + None => self.lowest_unused_client_id(), + }; + self.clients.insert(client_id, None); + client_id + } + fn lowest_unused_client_id(&self) -> ClientId { + let taken: HashSet = self .clients .keys() .copied() .chain(self.watchers.keys().copied()) .collect(); - - let mut next_client_id = 1; - loop { - if all_ids.contains(&next_client_id) { - next_client_id += 1; - } else { - break; - } + let mut client_id = 1; + while taken.contains(&client_id) { + client_id += 1; } - self.clients.insert(next_client_id, None); - next_client_id + client_id } pub fn associate_pipe_with_client(&mut self, pipe_id: String, client_id: ClientId) { self.pipes.insert(pipe_id, client_id); @@ -893,6 +912,54 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { start_server_impl(os_input, socket_path, true); } +fn handle_new_connection( + session_data: Arc>>, + session_state: Arc>, + mut os_input: Box, + to_server: SenderWithContext, + stream: interprocess::local_socket::Stream, + #[cfg(windows)] reply_stream: interprocess::local_socket::Stream, +) -> Result<()> { + let mut receiver: IpcReceiverWithContext = + IpcReceiverWithContext::new(stream); + + let _ = receiver.set_read_timeout(Some(PRE_HANDSHAKE_READ_TIMEOUT)); + let first_instruction = match receiver.try_recv_client_msg() { + Ok((instruction, _err_ctx)) => instruction, + Err(_) => return Ok(()), + }; + let _ = receiver.set_read_timeout(None); + + if let ClientToServerMsg::ConnStatus = first_instruction { + let mut sender = receiver.get_sender::(); + let _ = sender.send_server_msg(ServerToClientMsg::Connected); + return Ok(()); + } + + let client_id = session_state.write().unwrap().new_client(); + + #[cfg(windows)] + let registered = os_input.register_client_with_reply(client_id, reply_stream); + #[cfg(not(windows))] + let registered = os_input.register_client(client_id, &receiver); + + if let Err(err) = registered { + log::error!("Failed to register client {}: {:?}", client_id, err); + let _ = session_state.write().unwrap().remove_client(client_id); + return Ok(()); + } + + route_thread_main( + session_data, + session_state, + os_input, + to_server, + receiver, + client_id, + Some(first_instruction), + ) +} + pub fn start_server_impl( mut os_input: Box, socket_path: PathBuf, @@ -946,41 +1013,41 @@ pub fn start_server_impl( for stream in listener.incoming() { match stream { Ok(stream) => { - let mut os_input = os_input.clone(); - let client_id = session_state.write().unwrap().new_client(); + let os_input = os_input.clone(); #[cfg(windows)] - let reply_stream = reply_listener - .accept() - .expect("failed to accept reply connection"); - - #[cfg(windows)] - let receiver = os_input - .new_client_with_reply(client_id, stream, reply_stream) - .unwrap(); - #[cfg(not(windows))] - let receiver = os_input.new_client(client_id, stream).unwrap(); + let reply_stream = match reply_listener.accept() { + Ok(reply_stream) => reply_stream, + Err(err) => { + log::error!("Failed to accept reply connection: {:?}", err); + continue; + }, + }; let session_data = session_data.clone(); let session_state = session_state.clone(); let to_server = to_server.clone(); - thread::Builder::new() + let spawn_result = thread::Builder::new() .name("server_router".to_string()) .spawn(move || { - route_thread_main( + handle_new_connection( session_data, session_state, os_input, to_server, - receiver, - client_id, + stream, + #[cfg(windows)] + reply_stream, ) .fatal() - }) - .unwrap(); + }); + if let Err(err) = spawn_result { + log::error!("Failed to spawn router thread: {:?}", err); + } }, Err(err) => { - panic!("err {:?}", err); + log::error!("Failed to accept connection: {:?}", err); + thread::sleep(ACCEPT_ERROR_BACKOFF); }, } } @@ -999,10 +1066,15 @@ pub fn start_server_impl( == Some(LayoutInfo::BuiltIn("welcome".to_owned())) || config.options.default_layout == Some(PathBuf::from("welcome")); - let successfully_written_config = Config::write_config_to_disk_if_it_does_not_exist( - config.to_string(true), - &cli_assets.config_file_path, - ); + let successfully_written_config = + if zellij_utils::distribution::bundled_config().is_some() { + false + } else { + Config::write_config_to_disk_if_it_does_not_exist( + config.to_string(true), + &cli_assets.config_file_path, + ) + }; // if we successfully wrote the config to disk, it means two things: // 1. It did not exist beforehand // 2. The config folder is writeable @@ -1026,6 +1098,11 @@ pub fn start_server_impl( .unwrap_or_else(|| default_palette().into()), rounded_corners: config.ui.pane_frames.rounded_corners, hide_session_name: config.ui.pane_frames.hide_session_name, + border_style: config.ui.pane_frames.resolved_border_style(), + floating_border_style: config + .ui + .pane_frames + .resolved_floating_border_style(), }, }; @@ -1207,6 +1284,11 @@ pub fn start_server_impl( .unwrap_or_else(|| default_palette().into()), rounded_corners: config.ui.pane_frames.rounded_corners, hide_session_name: config.ui.pane_frames.hide_session_name, + border_style: config.ui.pane_frames.resolved_border_style(), + floating_border_style: config + .ui + .pane_frames + .resolved_floating_border_style(), }, }; @@ -1389,22 +1471,14 @@ pub fn start_server_impl( // Handle regular client removal remove_client!(client_id, os_input, session_state, session_data); drop(completion_tx); // prevent deadlock with route thread - session_data - .write() - .unwrap() - .as_ref() - .unwrap() - .senders - .send_to_screen(ScreenInstruction::RemoveClient(client_id)) - .unwrap(); - session_data - .write() - .unwrap() - .as_ref() - .unwrap() - .senders - .send_to_plugin(PluginInstruction::RemoveClient(client_id)) - .unwrap(); + if let Some(session_data) = session_data.write().unwrap().as_ref() { + let _ = session_data + .senders + .send_to_screen(ScreenInstruction::RemoveClient(client_id)); + let _ = session_data + .senders + .send_to_plugin(PluginInstruction::RemoveClient(client_id)); + } if !session_state.read().unwrap().active_clients_are_connected() { *session_data.write().unwrap() = None; let client_ids_to_cleanup: Vec = session_state @@ -1455,22 +1529,14 @@ pub fn start_server_impl( } // Handle regular client removal remove_client!(client_id, os_input, session_state, session_data); - session_data - .write() - .unwrap() - .as_ref() - .unwrap() - .senders - .send_to_screen(ScreenInstruction::RemoveClient(client_id)) - .unwrap(); - session_data - .write() - .unwrap() - .as_ref() - .unwrap() - .senders - .send_to_plugin(PluginInstruction::RemoveClient(client_id)) - .unwrap(); + if let Some(session_data) = session_data.write().unwrap().as_ref() { + let _ = session_data + .senders + .send_to_screen(ScreenInstruction::RemoveClient(client_id)); + let _ = session_data + .senders + .send_to_plugin(PluginInstruction::RemoveClient(client_id)); + } } }, ServerInstruction::SendWebClientsForbidden(client_id) => { @@ -1535,22 +1601,14 @@ pub fn start_server_impl( // by us having to wait for session_data to send cleanup // signals to the various threads for client_id in client_ids { - session_data - .write() - .unwrap() - .as_ref() - .unwrap() - .senders - .send_to_screen(ScreenInstruction::RemoveClient(client_id)) - .unwrap(); - session_data - .write() - .unwrap() - .as_ref() - .unwrap() - .senders - .send_to_plugin(PluginInstruction::RemoveClient(client_id)) - .unwrap(); + if let Some(session_data) = session_data.write().unwrap().as_ref() { + let _ = session_data + .senders + .send_to_screen(ScreenInstruction::RemoveClient(client_id)); + let _ = session_data + .senders + .send_to_plugin(PluginInstruction::RemoveClient(client_id)); + } } }, ServerInstruction::Render(serialized_output) => { @@ -1616,7 +1674,6 @@ pub fn start_server_impl( }, ServerInstruction::ConnStatus(client_id) => { let _ = os_input.send_to_client(client_id, ServerToClientMsg::Connected); - remove_client!(client_id, os_input, session_state, session_data); }, ServerInstruction::Log( lines_to_log, diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs index be19d0392..8e78e4eeb 100644 --- a/zellij-server/src/os_input_output.rs +++ b/zellij-server/src/os_input_output.rs @@ -332,18 +332,16 @@ pub trait ServerOsApi: Send + Sync { /// Returns a [`Box`] pointer to this [`ServerOsApi`] struct. fn box_clone(&self) -> Box; fn send_to_client(&self, client_id: ClientId, msg: ServerToClientMsg) -> Result<()>; - fn new_client( + fn register_client( &mut self, client_id: ClientId, - stream: LocalSocketStream, - ) -> Result>; - /// Create a new client with a separate reply stream (Windows dual-pipe IPC). - fn new_client_with_reply( + receiver: &IpcReceiverWithContext, + ) -> Result<()>; + fn register_client_with_reply( &mut self, client_id: ClientId, - stream: LocalSocketStream, reply_stream: LocalSocketStream, - ) -> Result>; + ) -> Result<()>; fn remove_client(&mut self, client_id: ClientId) -> Result<()>; fn load_palette(&self) -> Palette; /// Returns the current working directory for a given pid @@ -461,35 +459,32 @@ impl ServerOsApi for ServerOsInputOutput { } } - fn new_client( + fn register_client( &mut self, client_id: ClientId, - stream: LocalSocketStream, - ) -> Result> { - let receiver = IpcReceiverWithContext::new(stream); + receiver: &IpcReceiverWithContext, + ) -> Result<()> { let sender = ClientSender::new(client_id, receiver.get_sender()); self.client_senders .lock() .to_anyhow() .with_context(|| format!("failed to create new client {client_id}"))? .insert(client_id, sender); - Ok(receiver) + Ok(()) } - fn new_client_with_reply( + fn register_client_with_reply( &mut self, client_id: ClientId, - stream: LocalSocketStream, reply_stream: LocalSocketStream, - ) -> Result> { - let receiver = IpcReceiverWithContext::new(stream); + ) -> Result<()> { let sender = ClientSender::new(client_id, IpcSenderWithContext::new(reply_stream)); self.client_senders .lock() .to_anyhow() .with_context(|| format!("failed to create new client {client_id}"))? .insert(client_id, sender); - Ok(receiver) + Ok(()) } fn remove_client(&mut self, client_id: ClientId) -> Result<()> { diff --git a/zellij-server/src/output/mod.rs b/zellij-server/src/output/mod.rs index ee2138f2b..89d40c4d4 100644 --- a/zellij-server/src/output/mod.rs +++ b/zellij-server/src/output/mod.rs @@ -4,7 +4,7 @@ use crate::panes::Row; use crate::panes::Selection; use crate::{ - panes::kitty_graphics::store::{InternalImageId, KittyImageStore}, + panes::kitty_graphics::store::{InternalImageId, KittyImageStore, ScaledImageKey}, panes::sixel::SixelImageStore, panes::terminal_character::{AnsiCode, CharacterStyles}, panes::{LinkHandler, PaneId, TerminalCharacter, DEFAULT_STYLES, EMPTY_TERMINAL_CHARACTER}, @@ -371,13 +371,13 @@ pub struct KittyChunkKey { pub struct HostPlacementRecord { pub host_image_id: u32, pub host_placement_id: u32, - pub image_key: (InternalImageId, Option<(u16, u16)>), + pub image_key: (InternalImageId, Option), pub geometry: KittyImageChunk, } #[derive(Debug, Clone)] pub struct HostKittyState { - pub transmitted: HashMap<(InternalImageId, Option<(u16, u16)>), u32>, + pub transmitted: HashMap<(InternalImageId, Option), u32>, pub live_placements: HashMap, next_host_image_id: u32, next_host_placement_id: u32, @@ -469,10 +469,13 @@ fn serialize_kitty_frame(kitty_input: KittyFrameInput) -> Result { host_state.live_placements.clear(); host_state.transmitted.clear(); } - let mut freed: Vec<((InternalImageId, Option<(u16, u16)>), u32)> = host_state + let mut freed: Vec<((InternalImageId, Option), u32)> = host_state .transmitted .iter() - .filter(|(image_key, _)| kitty_image_store.get(image_key.0).is_none()) + .filter(|(image_key, _)| match image_key.1 { + Some(key) => kitty_image_store.scaled_variant(image_key.0, key).is_none(), + None => kitty_image_store.get(image_key.0).is_none(), + }) .map(|(image_key, host_id)| (*image_key, *host_id)) .collect(); freed.sort_by_key(|(_, host_id)| *host_id); @@ -542,19 +545,15 @@ fn serialize_kitty_frame(kitty_input: KittyFrameInput) -> Result { host_state.live_placements.remove(&key); } for (key, chunk) in current { - let variant = if chunk.scaled_px.is_some() { - Some(chunk.dest_cells) - } else { - None - }; + let variant = chunk.scaled_image; let image_key = (chunk.internal_image_id, variant); let host_image_id = match host_state.transmitted.get(&image_key) { Some(host_image_id) => *host_image_id, None => { let raster_dims = match variant { - Some(cells) => { - match kitty_image_store.scaled_variant(chunk.internal_image_id, cells) { - Some(_) => chunk.scaled_px, + Some(key) => { + match kitty_image_store.scaled_variant(chunk.internal_image_id, key) { + Some(_) => Some(key.size), None => None, } }, diff --git a/zellij-server/src/output/unit/output_tests.rs b/zellij-server/src/output/unit/output_tests.rs index 928efad4a..5f13c04c2 100644 --- a/zellij-server/src/output/unit/output_tests.rs +++ b/zellij-server/src/output/unit/output_tests.rs @@ -1188,8 +1188,7 @@ fn kitty_chunk( cell_offset_x: 0, cell_offset_y: 0, z_index: 0, - dest_cells: (3, 2), - scaled_px: None, + scaled_image: None, placement_uid, } } @@ -1433,7 +1432,6 @@ fn kitty_occlusion_crops_exclude_covered_quarter() { let mut chunk = kitty_chunk(internal, 1, 0, 0); chunk.source_px_width = 40; chunk.source_px_height = 80; - chunk.dest_cells = (4, 4); let output = run_kitty_frame(&parts, vec![chunk], Some(floating_panes_stack)); let crops = parse_kitty_placement_crops(&output); let crop_set: HashSet<(usize, usize, usize, usize, usize, usize)> = @@ -1559,7 +1557,6 @@ fn kitty_emitted_bytes_roundtrip_through_our_parser() { chunk.cell_offset_x = 3; chunk.cell_offset_y = 4; chunk.z_index = -1; - chunk.dest_cells = (2, 2); let output = run_kitty_frame(&parts, vec![chunk.clone()], None); let commands = extract_kitty_commands(&output); assert!( @@ -1635,3 +1632,120 @@ fn kitty_host_ids_stay_within_signed_32_bit_range() { } } } + +#[test] +fn kitty_cropped_placements_preserve_pixels_after_cell_resize() { + use crate::panes::kitty_graphics::grid_state::{KittyGrid, KittyVerticalAnchor}; + use crate::panes::kitty_graphics::parser::KittyCommand; + + let colors = [ + [255, 0, 0, 255], + [0, 255, 0, 255], + [0, 0, 255, 255], + [255; 4], + ]; + for scaled in [false, true] { + let parts = create_test_kitty_parts(); + let cell = Rc::new(RefCell::new(Some(SizeInPixels { + width: 10, + height: 20, + }))); + let mut grid = KittyGrid::new(cell.clone(), parts.0.clone()); + let image = DecodedImage { + bytes: (0..40) + .flat_map(|y| (0..20).flat_map(move |x| colors[y / 20 * 2 + x / 10])) + .collect(), + width: 20, + height: 40, + format: KittyFormat::Rgba32, + }; + let image_id = grid + .transmit( + &KittyCommand { + image_id: Some(1), + ..Default::default() + }, + image, + ) + .unwrap(); + let internal = grid.pane_image_id_map()[&image_id]; + for i in 0..4 { + let command = KittyCommand { + image_id: Some(image_id), + placement_id: Some(i as u32 + 1), + source_x: (i % 2 * 10) as u32, + source_y: (i / 2 * 20) as u32, + source_w: 10, + source_h: 20, + columns: u32::from(scaled), + rows: u32::from(scaled), + ..Default::default() + }; + grid.place( + image_id, + internal, + &command, + (i * 20, 0), + cell.borrow().unwrap(), + KittyVerticalAnchor { + canonical_line: 0, + offset_px_from_line_start: 0, + }, + ) + .unwrap(); + } + let mut host_images = HashMap::new(); + let mut host_placements = std::collections::BTreeMap::::new(); + for size in [(10, 20), (15, 30)] { + *cell.borrow_mut() = Some(SizeInPixels { + width: size.0, + height: size.1, + }); + grid.character_cell_size_possibly_changed(); + let chunks = grid.viewport_kitty_chunks(20, 0, 50, 0, 0); + let output = run_kitty_frame(&parts, chunks, None); + let mut parser = KittyCommandParser::new(); + for raw in extract_kitty_commands(&output) { + if let Some(command) = parser.parse(&raw) { + let command = command.unwrap(); + match command.action { + KittyAction::Transmit => { + host_images.insert(command.image_id.unwrap(), command.image.unwrap()); + }, + KittyAction::Display => { + host_placements.insert(command.placement_id.unwrap(), command); + }, + KittyAction::Delete => { + let id = command.image_id.unwrap(); + host_images.remove(&id); + host_placements.retain(|_, placement| placement.image_id != Some(id)); + }, + _ => {}, + } + } + } + assert_eq!(host_placements.len(), 4); + assert_eq!(host_images.len(), if scaled { 4 } else { 1 }); + for (i, command) in host_placements.values().enumerate() { + let image = &host_images[&command.image_id.unwrap()]; + let expected_size = if scaled { size } else { (10, 20) }; + assert_eq!( + (command.source_w as usize, command.source_h as usize), + expected_size + ); + for y in command.source_y..command.source_y + command.source_h { + for x in command.source_x..command.source_x + command.source_w { + let offset = ((y * image.width + x) * 4) as usize; + assert_eq!( + &image.bytes[offset..offset + 4], + &colors[i], + "scaled={scaled}, cell={size:?}, placement={i}" + ); + } + } + } + let scaled_bytes = if scaled { 4 * size.0 * size.1 * 4 } else { 0 }; + assert_eq!(parts.0.borrow().total_bytes(), 20 * 40 * 4 + scaled_bytes); + } + } +} diff --git a/zellij-server/src/pane_groups.rs b/zellij-server/src/pane_groups.rs index eb0fcb3de..7e0f762f8 100644 --- a/zellij-server/src/pane_groups.rs +++ b/zellij-server/src/pane_groups.rs @@ -169,6 +169,7 @@ impl PaneGroups { height: Some(PercentOrFixed::Fixed(height)), pinned: Some(true), borderless: Some(false), + border_style: None, }; let _ = self.senders.send_to_pty(PtyInstruction::FillPluginCwd( should_float, diff --git a/zellij-server/src/panes/floating_panes/mod.rs b/zellij-server/src/panes/floating_panes/mod.rs index bf6b98cb8..bff23a035 100644 --- a/zellij-server/src/panes/floating_panes/mod.rs +++ b/zellij-server/src/panes/floating_panes/mod.rs @@ -1,6 +1,6 @@ pub mod floating_pane_grid; use zellij_utils::{ - data::{Direction, FloatingPaneCoordinates, PaneInfo, ResizeStrategy}, + data::{BorderStyle, Direction, FloatingPaneCoordinates, PaneInfo, ResizeStrategy}, position::Position, }; @@ -41,6 +41,7 @@ pub struct FloatingPanes { viewport: Rc>, connected_clients: Rc>>, connected_clients_in_app: Rc>>, // bool -> is_web_client + client_display_slots: Rc>>, mode_info: Rc>>, character_cell_size: Rc>>, default_mode_info: ModeInfo, @@ -70,6 +71,7 @@ impl FloatingPanes { viewport: Rc>, connected_clients: Rc>>, connected_clients_in_app: Rc>>, // bool -> is_web_client + client_display_slots: Rc>>, mode_info: Rc>>, character_cell_size: Rc>>, fullscreen_covers_ui: Rc>, @@ -86,6 +88,7 @@ impl FloatingPanes { viewport, connected_clients, connected_clients_in_app, + client_display_slots, mode_info, character_cell_size, session_is_mirrored, @@ -598,6 +601,7 @@ impl FloatingPanes { mouse_scroll_resize, mouse_hover_tips, self.dimmed_clients.clone(), + &self.client_display_slots.borrow(), ); for client_id in &connected_clients { let client_mode = self @@ -1710,6 +1714,17 @@ impl FloatingPanes { pane.update_rounded_corners(rounded_corners); } } + pub fn update_border_styles( + &mut self, + border_style: BorderStyle, + floating_border_style: BorderStyle, + ) { + self.style.border_style = border_style; + self.style.floating_border_style = floating_border_style; + for pane in self.panes.values_mut() { + pane.invalidate_frame_cache(); + } + } pub fn next_selectable_pane_id_above(&mut self, pane_id: &PaneId) -> Option { let display_area = *self.display_area.borrow(); let viewport = *self.viewport.borrow(); diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs index 0b2af88b9..7ad003938 100644 --- a/zellij-server/src/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -1922,6 +1922,9 @@ impl Grid { let changed_rects = self .output_buffer .changed_rects_in_viewport(self.viewport.len()); + if let Some(image_ids_to_reap) = self.sixel_grid.drain_image_ids_to_reap() { + self.sixel_grid.reap_images(image_ids_to_reap); + } let changed_sixel_image_chunks = self.sixel_grid.changed_sixel_chunks_in_viewport( changed_rects, self.lines_above.len(), @@ -1929,9 +1932,6 @@ impl Grid { x_offset, y_offset, ); - if let Some(image_ids_to_reap) = self.sixel_grid.drain_image_ids_to_reap() { - self.sixel_grid.reap_images(image_ids_to_reap); - } if self.kitty_settle_placements_below_the_viewport() { self.kitty_reanchor_all_from_pixels(); } diff --git a/zellij-server/src/panes/kitty_graphics/grid_state.rs b/zellij-server/src/panes/kitty_graphics/grid_state.rs index 7a1d9f712..edbf396d5 100644 --- a/zellij-server/src/panes/kitty_graphics/grid_state.rs +++ b/zellij-server/src/panes/kitty_graphics/grid_state.rs @@ -1,5 +1,5 @@ use super::parser::{DecodedImage, KittyCommand, KittyError, KittyErrorCode}; -use super::store::{InternalImageId, KittyImageStore}; +use super::store::{InternalImageId, KittyImageStore, ScaledImageKey}; use crate::panes::sixel::PixelRect; use std::cell::RefCell; use std::collections::HashMap; @@ -18,8 +18,7 @@ pub struct KittyImageChunk { pub cell_offset_x: u32, pub cell_offset_y: u32, pub z_index: i32, - pub dest_cells: (u16, u16), - pub scaled_px: Option<(usize, usize)>, + pub scaled_image: Option, pub placement_uid: u64, } @@ -46,6 +45,15 @@ pub struct KittyPlacement { pub vertical_anchor: KittyVerticalAnchor, } +impl KittyPlacement { + pub fn scaled_image(&self) -> Option { + self.scaled_px.map(|size| ScaledImageKey { + source: self.source_rect, + size, + }) + } +} + #[derive(Debug, Clone, Copy, PartialEq)] pub struct KittyReplyData { pub image_id: Option, @@ -329,6 +337,12 @@ impl KittyGrid { (c, r) => (c * cell.width, r * cell.height, c, r, true), }; let dest_cells = (cols as u16, rows as u16); + let source_rect = PixelRect { + x: source_x, + y: source_y as isize, + width: source_w, + height: source_h, + }; if is_scaled { let scaled_bytes = { let store = self.kitty_image_store.borrow(); @@ -348,7 +362,10 @@ impl KittyGrid { }; self.kitty_image_store.borrow_mut().add_scaled_variant( internal, - dest_cells, + ScaledImageKey { + source: source_rect, + size: (dst_w, dst_h), + }, scaled_bytes, ); } @@ -386,12 +403,7 @@ impl KittyGrid { placement_uid, internal_id: internal, display_rect, - source_rect: PixelRect { - x: source_x, - y: source_y as isize, - width: source_w, - height: source_h, - }, + source_rect, emit_x: 0, emit_y: 0, scaled_px: if is_scaled { @@ -830,7 +842,12 @@ impl KittyGrid { (self.previous_cell_size, *self.character_cell_size.borrow()) { if previous_cell_size != character_cell_size { - let mut regenerations: Vec<(InternalImageId, (u16, u16), PixelRect)> = Vec::new(); + for internal in self.image_ids.values() { + self.kitty_image_store + .borrow_mut() + .clear_scaled_variants(*internal); + } + let mut regenerations: Vec<(InternalImageId, ScaledImageKey)> = Vec::new(); for placement in self.placements.iter_mut() { placement.display_rect.x = (placement.display_rect.x / previous_cell_size.width) @@ -846,14 +863,12 @@ impl KittyGrid { Some((placement.display_rect.width, placement.display_rect.height)); placement.emit_x = 0; placement.emit_y = 0; - regenerations.push(( - placement.internal_id, - placement.dest_cells, - placement.source_rect, - )); + regenerations + .push((placement.internal_id, placement.scaled_image().unwrap())); } } - for (internal, dest_cells, source_rect) in regenerations { + for (internal, key) in regenerations { + let source_rect = key.source; let scaled_bytes = { let store = self.kitty_image_store.borrow(); match store.get(internal) { @@ -871,8 +886,8 @@ impl KittyGrid { &cropped, source_rect.width, source_rect.height, - dest_cells.0 as usize * character_cell_size.width, - dest_cells.1 as usize * character_cell_size.height, + key.size.0, + key.size.1, )) }, None => None, @@ -881,7 +896,7 @@ impl KittyGrid { if let Some(scaled_bytes) = scaled_bytes { self.kitty_image_store.borrow_mut().add_scaled_variant( internal, - dest_cells, + key, scaled_bytes, ); } @@ -1034,8 +1049,14 @@ impl KittyGrid { .saturating_sub(line_index + scrollback_size_in_lines); let cell_y = viewport_y_offset + line_index + image_cell_distance_from_changed_rect_top; - let source_px_x = placement.emit_x; - let source_px_y = placement.emit_y + let (source_x, source_y) = if placement.scaled_px.is_some() { + (0, 0) + } else { + (placement.source_rect.x, placement.source_rect.y as usize) + }; + let source_px_x = source_x + placement.emit_x; + let source_px_y = source_y + + placement.emit_y + std::cmp::max(changed_rect_top_edge - image_top_edge, 0) as usize; let source_px_height = std::cmp::min( (std::cmp::min(changed_rect_bottom_edge, image_bottom_edge) @@ -1062,8 +1083,7 @@ impl KittyGrid { cell_offset_x: placement.cell_offset.0, cell_offset_y: placement.cell_offset.1, z_index: placement.z_index, - dest_cells: placement.dest_cells, - scaled_px: placement.scaled_px, + scaled_image: placement.scaled_image(), placement_uid: placement.placement_uid, }); } diff --git a/zellij-server/src/panes/kitty_graphics/store.rs b/zellij-server/src/panes/kitty_graphics/store.rs index f41966db8..2870cec12 100644 --- a/zellij-server/src/panes/kitty_graphics/store.rs +++ b/zellij-server/src/panes/kitty_graphics/store.rs @@ -2,18 +2,25 @@ use base64::engine::general_purpose::STANDARD as BASE64_ENCODER; use base64::engine::Engine as _; use super::parser::{DecodedImage, KittyError, KittyErrorCode, KittyFormat}; +use crate::panes::sixel::PixelRect; use std::collections::HashMap; pub const DEFAULT_KITTY_STORE_QUOTA_BYTES: usize = 335_544_320; pub type InternalImageId = u64; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ScaledImageKey { + pub source: PixelRect, + pub size: (usize, usize), +} + #[derive(Debug, Clone, PartialEq)] pub struct KittyImage { pub rgba: Vec, pub width: u32, pub height: u32, - pub scaled_variants: HashMap<(u16, u16), Vec>, + pub scaled_variants: HashMap>, } #[derive(Debug, Clone)] @@ -21,7 +28,7 @@ struct StoredImage { image: KittyImage, refcount: usize, lru_stamp: u64, - base64_cache: HashMap, String>, + base64_cache: HashMap, String>, } #[derive(Debug, Clone)] @@ -93,10 +100,10 @@ impl KittyImageStore { pub fn get(&self, id: InternalImageId) -> Option<&KittyImage> { self.images.get(&id).map(|stored| &stored.image) } - pub fn scaled_variant(&self, id: InternalImageId, cells: (u16, u16)) -> Option<&[u8]> { + pub fn scaled_variant(&self, id: InternalImageId, key: ScaledImageKey) -> Option<&[u8]> { self.images .get(&id) - .and_then(|stored| stored.image.scaled_variants.get(&cells)) + .and_then(|stored| stored.image.scaled_variants.get(&key)) .map(|bytes| bytes.as_slice()) } pub fn touch(&mut self, id: InternalImageId) { @@ -132,21 +139,21 @@ impl KittyImageStore { pub fn base64_for( &mut self, id: InternalImageId, - variant: Option<(u16, u16)>, + variant: Option, ) -> Option { let stored = self.images.get_mut(&id)?; if let Some(cached) = stored.base64_cache.get(&variant) { return Some(cached.clone()); } let bytes = match variant { - Some(cells) => stored.image.scaled_variants.get(&cells)?, + Some(key) => stored.image.scaled_variants.get(&key)?, None => &stored.image.rgba, }; let encoded = BASE64_ENCODER.encode(bytes); stored.base64_cache.insert(variant, encoded.clone()); Some(encoded) } - pub fn add_scaled_variant(&mut self, id: InternalImageId, cells: (u16, u16), bytes: Vec) { + pub fn add_scaled_variant(&mut self, id: InternalImageId, key: ScaledImageKey, bytes: Vec) { let lru_stamp = self.bump_lru(); let inserted = match self.images.get_mut(&id) { Some(stored) => { @@ -154,10 +161,10 @@ impl KittyImageStore { let old_len = stored .image .scaled_variants - .insert(cells, bytes) + .insert(key, bytes) .map(|old| old.len()) .unwrap_or(0); - stored.base64_cache.remove(&Some(cells)); + stored.base64_cache.remove(&Some(key)); stored.lru_stamp = lru_stamp; Some((old_len, new_len)) }, @@ -170,6 +177,17 @@ impl KittyImageStore { } } } + pub fn clear_scaled_variants(&mut self, id: InternalImageId) { + if let Some(stored) = self.images.get_mut(&id) { + self.total_bytes -= stored + .image + .scaled_variants + .drain() + .map(|(_, bytes)| bytes.len()) + .sum::(); + stored.base64_cache.retain(|key, _| key.is_none()); + } + } pub fn refcount(&self, id: InternalImageId) -> Option { self.images.get(&id).map(|stored| stored.refcount) } diff --git a/zellij-server/src/panes/kitty_graphics/unit/grid_state_tests.rs b/zellij-server/src/panes/kitty_graphics/unit/grid_state_tests.rs index b36825e1c..31d0b6268 100644 --- a/zellij-server/src/panes/kitty_graphics/unit/grid_state_tests.rs +++ b/zellij-server/src/panes/kitty_graphics/unit/grid_state_tests.rs @@ -104,7 +104,7 @@ fn changed_chunks_for_placement_straddling_changed_rect() { assert_eq!(chunk.source_px_y, 0); assert_eq!(chunk.source_px_width, 20); assert_eq!(chunk.source_px_height, 10); - assert_eq!(chunk.scaled_px, None); + assert_eq!(chunk.scaled_image, None); } #[test] @@ -188,8 +188,7 @@ fn changed_chunks_for_scaled_placement_use_variant_space() { assert_eq!(chunk.cell_y, 1); assert_eq!(chunk.source_px_y, 20); assert_eq!(chunk.source_px_height, 20); - assert_eq!(chunk.scaled_px, Some((20, 40))); - assert_eq!(chunk.dest_cells, (2, 2)); + assert_eq!(chunk.scaled_image.unwrap().size, (20, 40)); } #[test] diff --git a/zellij-server/src/panes/kitty_graphics/unit/store_tests.rs b/zellij-server/src/panes/kitty_graphics/unit/store_tests.rs index 28397d478..8ef387bc7 100644 --- a/zellij-server/src/panes/kitty_graphics/unit/store_tests.rs +++ b/zellij-server/src/panes/kitty_graphics/unit/store_tests.rs @@ -98,11 +98,19 @@ fn byte_accounting_matches_hand_computed_total_with_scaled_variants() { let mut store = KittyImageStore::default(); let id_a = store.store_image(rgba_image(10, 10)).unwrap(); let id_b = store.store_image(rgba_image(4, 5)).unwrap(); - store.add_scaled_variant(id_a, (3, 2), vec![0; 120]); - store.add_scaled_variant(id_a, (5, 4), vec![0; 60]); - store.add_scaled_variant(id_a, (3, 2), vec![0; 100]); + let first = super::ScaledImageKey { + source: crate::panes::sixel::PixelRect::new(0, 0, 10, 10), + size: (3, 2), + }; + let second = super::ScaledImageKey { + size: (5, 4), + ..first + }; + store.add_scaled_variant(id_a, first, vec![0; 120]); + store.add_scaled_variant(id_a, second, vec![0; 60]); + store.add_scaled_variant(id_a, first, vec![0; 100]); assert_eq!(store.total_bytes(), 640); - assert_eq!(store.scaled_variant(id_a, (3, 2)).unwrap().len(), 100); + assert_eq!(store.scaled_variant(id_a, first).unwrap().len(), 100); store.free(id_a); assert_eq!(store.total_bytes(), 80); store.free(id_b); diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs index de426fd51..d6b0e4d45 100644 --- a/zellij-server/src/panes/plugin_pane.rs +++ b/zellij-server/src/panes/plugin_pane.rs @@ -22,7 +22,8 @@ use std::rc::Rc; use vte; use zellij_utils::data::PaneContents; use zellij_utils::data::{ - BareKey, KeyWithModifier, PermissionStatus, PermissionType, PluginPermission, + BareKey, BorderStyleOverride, KeyWithModifier, PermissionStatus, PermissionType, + PluginPermission, }; use zellij_utils::pane_size::{Offset, SizeInPixels}; use zellij_utils::position::Position; @@ -99,6 +100,7 @@ pub(crate) struct PluginPane { prev_pane_name: String, frame: HashMap, borderless: bool, + border_style_override: BorderStyleOverride, exclude_from_sync: bool, pane_frame_color_override: Option<(PaletteColor, Option)>, invoked_with: Option, @@ -146,6 +148,7 @@ impl PluginPane { content_offset: Offset::default(), pane_title: title, borderless: false, + border_style_override: BorderStyleOverride::default(), pane_name: pane_name.clone(), prev_pane_name: pane_name, terminal_emulator_colors, @@ -747,6 +750,14 @@ impl Pane for PluginPane { fn borderless(&self) -> bool { self.borderless } + fn set_border_style_override(&mut self, border_style_override: BorderStyleOverride) { + self.border_style_override = border_style_override; + self.frame.clear(); + self.set_should_render(true); + } + fn border_style_override(&self) -> BorderStyleOverride { + self.border_style_override + } fn set_exclude_from_sync(&mut self, exclude_from_sync: bool) { self.exclude_from_sync = exclude_from_sync; } @@ -847,6 +858,10 @@ impl Pane for PluginPane { self.style.rounded_corners = rounded_corners; self.frame.clear(); } + fn invalidate_frame_cache(&mut self) { + self.frame.clear(); + self.set_should_render(true); + } fn set_should_be_suppressed(&mut self, should_be_suppressed: bool) { self.should_be_suppressed = should_be_suppressed; } diff --git a/zellij-server/src/panes/sixel.rs b/zellij-server/src/panes/sixel.rs index b5cf0bc0f..79c16d632 100644 --- a/zellij-server/src/panes/sixel.rs +++ b/zellij-server/src/panes/sixel.rs @@ -1,6 +1,6 @@ use crate::output::SixelImageChunk; use std::cell::RefCell; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::rc::Rc; use sixel_image::{SixelDeserializer, SixelImage}; @@ -66,6 +66,7 @@ pub struct SixelGrid { character_cell_size: Rc>>, currently_parsing: Option, image_ids_to_reap: Vec, + images_to_check_for_emptiness: HashSet, sixel_parser: Option, pub sixel_image_store: Rc>, } @@ -167,8 +168,8 @@ impl SixelGrid { if let Some(intersecting_rect) = pixel_rect.intersecting_rect(&image_size_and_coordinates) { - if intersecting_rect.x == pixel_rect.x - && intersecting_rect.y == pixel_rect.y + if intersecting_rect.x == 0 + && intersecting_rect.y == 0 && intersecting_rect.height == pixel_rect.height && intersecting_rect.width == pixel_rect.width { @@ -225,6 +226,21 @@ impl SixelGrid { } } pub fn drain_image_ids_to_reap(&mut self) -> Option> { + if !self.images_to_check_for_emptiness.is_empty() { + let store = self.sixel_image_store.borrow(); + for image_id in self.images_to_check_for_emptiness.drain() { + let has_lit_pixels = store + .sixel_images + .get(&image_id) + .map_or(false, |(sixel_image, _)| { + sixel_image.pixels.iter().flatten().any(|pixel| pixel.on) + }); + if !has_lit_pixels { + self.sixel_image_locations.remove(&image_id); + self.image_ids_to_reap.push(image_id); + } + } + } let images_to_reap = self.image_ids_to_reap.drain(..); if images_to_reap.len() > 0 { Some(images_to_reap.collect()) @@ -255,6 +271,7 @@ impl SixelGrid { .map(|(image_id, _image_rect)| image_id) .collect(); image_ids.append(&mut self.image_ids_to_reap); + self.images_to_check_for_emptiness.clear(); if !image_ids.is_empty() { Some(image_ids) } else { @@ -262,13 +279,17 @@ impl SixelGrid { } } pub fn next_image_id(&self) -> usize { - self.sixel_image_store.borrow().sixel_images.keys().len() + let mut store = self.sixel_image_store.borrow_mut(); + let id = store.next_image_id; + store.next_image_id += 1; + id } pub fn new_sixel_image(&mut self, sixel_image_id: usize, sixel_image: SixelImage) { self.sixel_image_store .borrow_mut() .sixel_images .insert(sixel_image_id, (sixel_image, HashMap::new())); + self.images_to_check_for_emptiness.insert(sixel_image_id); } pub fn remove_pixels_from_image(&mut self, image_id: usize, pixel_rect: PixelRect) { if let Some((sixel_image, sixel_image_cache)) = self @@ -285,6 +306,7 @@ impl SixelGrid { ); sixel_image_cache.clear(); // TODO: more intelligent cache clearing } + self.images_to_check_for_emptiness.insert(image_id); } pub fn reap_images(&mut self, ids_to_reap: Vec) { for id in ids_to_reap { @@ -428,6 +450,7 @@ type SixelImageCache = HashMap; #[derive(Debug, Clone, Default)] pub struct SixelImageStore { sixel_images: HashMap, + next_image_id: usize, } impl SixelImageStore { diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index 9230b2459..f226ad375 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -25,8 +25,8 @@ use zellij_utils::input::mouse::{MouseEvent, MouseEventType}; use zellij_utils::pane_size::Offset; use zellij_utils::{ data::{ - BareKey, InputMode, KeyWithModifier, Palette, PaletteColor, PaneId as ZellijUtilsPaneId, - RegexHighlight, Style, Styling, + BareKey, BorderStyleOverride, InputMode, KeyWithModifier, Palette, PaletteColor, + PaneId as ZellijUtilsPaneId, RegexHighlight, Style, Styling, }, errors::prelude::*, input::layout::Run, @@ -145,6 +145,7 @@ pub struct TerminalPane { prev_pane_name: String, frame: HashMap, borderless: bool, + border_style_override: BorderStyleOverride, exclude_from_sync: bool, fake_cursor_locations: HashSet<(usize, usize)>, // (x, y) - these hold a record of previous fake cursors which we need to clear on render search_term: String, @@ -990,6 +991,14 @@ impl Pane for TerminalPane { self.set_content_offset(Offset::frame(1)); } } + fn set_border_style_override(&mut self, border_style_override: BorderStyleOverride) { + self.border_style_override = border_style_override; + self.frame.clear(); + self.set_should_render(true); + } + fn border_style_override(&self) -> BorderStyleOverride { + self.border_style_override + } fn set_exclude_from_sync(&mut self, exclude_from_sync: bool) { self.exclude_from_sync = exclude_from_sync; @@ -1232,6 +1241,10 @@ impl Pane for TerminalPane { self.style.rounded_corners = rounded_corners; self.frame.clear(); } + fn invalidate_frame_cache(&mut self) { + self.frame.clear(); + self.set_should_render(true); + } fn drain_fake_cursors(&mut self) -> Option> { if !self.fake_cursor_locations.is_empty() { for (y, _x) in &self.fake_cursor_locations { @@ -1418,6 +1431,7 @@ impl TerminalPane { pane_name: pane_name.clone(), prev_pane_name: pane_name, borderless: false, + border_style_override: BorderStyleOverride::default(), exclude_from_sync: false, fake_cursor_locations: HashSet::new(), search_term: String::new(), diff --git a/zellij-server/src/panes/tiled_panes/mod.rs b/zellij-server/src/panes/tiled_panes/mod.rs index 94c824b9b..a186d9773 100644 --- a/zellij-server/src/panes/tiled_panes/mod.rs +++ b/zellij-server/src/panes/tiled_panes/mod.rs @@ -28,7 +28,10 @@ use crate::{ }; use stacked_panes::StackedPanes; use zellij_utils::{ - data::{Direction, ModeInfo, PaneInfo, Resize, ResizeStrategy, Style, Styling}, + data::{ + BorderStyle, BorderStyleOverride, Direction, LineStyle, ModeInfo, PaneInfo, Resize, + ResizeStrategy, Style, Styling, + }, errors::prelude::*, input::{ command::RunCommand, @@ -70,6 +73,7 @@ pub struct TiledPanes { viewport: Rc>, connected_clients: Rc>>, connected_clients_in_app: Rc>>, // bool -> is_web_client + client_display_slots: Rc>>, mode_info: Rc>>, character_cell_size: Rc>>, stacked_resize: Rc>, @@ -97,6 +101,7 @@ impl TiledPanes { viewport: Rc>, connected_clients: Rc>>, connected_clients_in_app: Rc>>, // bool -> is_web_client + client_display_slots: Rc>>, mode_info: Rc>>, character_cell_size: Rc>>, stacked_resize: Rc>, @@ -115,6 +120,7 @@ impl TiledPanes { viewport, connected_clients, connected_clients_in_app, + client_display_slots, mode_info, character_cell_size, stacked_resize, @@ -1161,6 +1167,11 @@ impl TiledPanes { let connected_clients: Vec = connected_clients.into_iter().collect(); let multiple_users_exist_in_session = { self.connected_clients_in_app.borrow().len() > 1 }; let mut client_id_to_boundaries: HashMap = HashMap::new(); + let fallback_line_style = self + .style + .border_style + .uniform_style() + .unwrap_or(LineStyle::Single); let active_panes = if floating_panes_are_visible { HashMap::new() } else { @@ -1274,6 +1285,7 @@ impl TiledPanes { mouse_scroll_resize, mouse_hover_tips, self.dimmed_clients.clone(), + &self.client_display_slots.borrow(), ); pane_contents_and_ui.set_frame_geom_override(visible_member_frame_override); pane_contents_and_ui.set_blank_title(reserved_rows_for_pane > 0); @@ -1317,9 +1329,12 @@ impl TiledPanes { pane_is_selectable, ) .with_context(err_context)?; - let boundaries = client_id_to_boundaries - .entry(*client_id) - .or_insert_with(|| Boundaries::new(*self.viewport.borrow())); + let boundaries = + client_id_to_boundaries + .entry(*client_id) + .or_insert_with(|| { + Boundaries::new(*self.viewport.borrow(), fallback_line_style) + }); pane_contents_and_ui.render_pane_boundaries( *client_id, client_mode, @@ -1329,9 +1344,12 @@ impl TiledPanes { pane_is_on_bottom_of_stack, ); } else { - let boundaries = client_id_to_boundaries - .entry(*client_id) - .or_insert_with(|| Boundaries::new(*self.viewport.borrow())); + let boundaries = + client_id_to_boundaries + .entry(*client_id) + .or_insert_with(|| { + Boundaries::new(*self.viewport.borrow(), fallback_line_style) + }); pane_contents_and_ui.render_pane_boundaries( *client_id, client_mode, @@ -1415,6 +1433,7 @@ impl TiledPanes { run: Option, geom: PaneGeom, should_be_borderless: Option, + border_style: Option, ) { match self .panes @@ -1423,6 +1442,9 @@ impl TiledPanes { { Some((_, pane)) => { pane.set_geom(geom); + if let Some(border_style) = border_style { + pane.set_border_style_override(border_style); + } if let Some(should_be_borderless) = should_be_borderless { pane.set_borderless(should_be_borderless); @@ -2738,13 +2760,17 @@ impl TiledPanes { self.panes.remove(&pane_id) } pub fn remove_pane(&mut self, pane_id: PaneId) -> Option> { + let closed_logical_position = self + .panes + .get(&pane_id) + .and_then(|pane| pane.position_and_size().logical_position); let mut pane_grid = TiledPaneGrid::new( &mut self.panes, &self.panes_to_hide, *self.display_area.borrow(), *self.viewport.borrow(), ); - if pane_grid.fill_space_over_pane(pane_id) { + let closed_pane = if pane_grid.fill_space_over_pane(pane_id) { // successfully filled space over pane let closed_pane = self.panes.remove(&pane_id); self.move_clients_out_of_pane(pane_id); @@ -2762,7 +2788,20 @@ impl TiledPanes { self.active_panes.clear(&mut self.panes); } closed_pane + }; + if let Some(closed_logical_position) = closed_logical_position { + for pane in self.panes.values_mut() { + let mut geom = pane.position_and_size(); + if geom + .logical_position + .is_some_and(|position| position > closed_logical_position) + { + geom.logical_position = geom.logical_position.map(|position| position - 1); + pane.set_geom(geom); + } + } } + closed_pane } pub fn hold_pane( &mut self, @@ -3113,6 +3152,17 @@ impl TiledPanes { pane.update_rounded_corners(rounded_corners); } } + pub fn update_border_styles( + &mut self, + border_style: BorderStyle, + floating_border_style: BorderStyle, + ) { + self.style.border_style = border_style; + self.style.floating_border_style = floating_border_style; + for pane in self.panes.values_mut() { + pane.invalidate_frame_cache(); + } + } pub fn stack_panes( &mut self, root_pane_id: PaneId, diff --git a/zellij-server/src/panes/tiled_panes/stacked_panes.rs b/zellij-server/src/panes/tiled_panes/stacked_panes.rs index 4df175e84..f49833ab8 100644 --- a/zellij-server/src/panes/tiled_panes/stacked_panes.rs +++ b/zellij-server/src/panes/tiled_panes/stacked_panes.rs @@ -61,6 +61,8 @@ impl<'a> StackedPanes<'a> { let destination_pane = panes .get_mut(&destination_pane_id) .with_context(err_context)?; + destination_pane_geom.logical_position = + destination_pane.position_and_size().logical_position; destination_pane.set_geom(destination_pane_geom); } else if destination_pane_stack_id.is_some() { // we're moving down to the highest pane in the stack, we need to expand it and shrink the @@ -97,6 +99,8 @@ impl<'a> StackedPanes<'a> { let destination_pane = panes .get_mut(&destination_pane_id) .with_context(err_context)?; + destination_pane_geom.logical_position = + destination_pane.position_and_size().logical_position; destination_pane.set_geom(destination_pane_geom); } else if destination_pane_stack_id.is_some() { // we're moving up to the lowest pane in the stack, we need to expand it and shrink the @@ -1053,6 +1057,8 @@ impl<'a> StackedPanes<'a> { pane_to_close_position_and_size.stacked = None; } let pane_below = panes.get_mut(&pane_id_below).with_context(err_context)?; + pane_to_close_position_and_size.logical_position = + pane_below.position_and_size().logical_position; pane_below.set_geom(pane_to_close_position_and_size); return Ok(true); } else if position_of_current_pane > 0 { @@ -1070,6 +1076,8 @@ impl<'a> StackedPanes<'a> { pane_to_close_position_and_size.stacked = None; } let pane_above = panes.get_mut(&pane_id_above).with_context(err_context)?; + pane_to_close_position_and_size.logical_position = + pane_above.position_and_size().logical_position; pane_above.set_geom(pane_to_close_position_and_size); return Ok(true); } else { diff --git a/zellij-server/src/panes/unit/grid_tests.rs b/zellij-server/src/panes/unit/grid_tests.rs index fa221da9c..f2c209f1c 100644 --- a/zellij-server/src/panes/unit/grid_tests.rs +++ b/zellij-server/src/panes/unit/grid_tests.rs @@ -3303,6 +3303,51 @@ pub fn sixel_images_are_reaped_when_scrolled_off() { ); } +#[test] +fn sixel_replacement_reaps_covered_images_without_reusing_live_ids() { + let (mut grid, _) = new_kitty_grid(10, 25); + let mut parser = vte::Parser::new(); + for step in 0..18u8 { + let width = if step < 12 { 20 } else { 10 + (step % 3) * 10 }; + if step >= 12 { + parser.advance(&mut grid, b"\x1b[3;4H "); + } + let bytes = format!( + "\u{1b}[3;4H\u{1b}P9;1q\"1;1;{};12#1;2;{};0;0#1!{}~-!{}~\u{1b}\\", + width, + step * 5, + width, + width + ); + parser.advance(&mut grid, bytes.as_bytes()); + grid.read_changes(0, 0); + let coordinates: Vec<_> = grid.sixel_grid.image_coordinates().collect(); + assert_eq!(coordinates.len(), 1, "obsolete image at step {}", step); + let (id, rect) = coordinates[0]; + let mut store = grid.sixel_grid.sixel_image_store.borrow_mut(); + assert_eq!(store.image_count(), 1, "storage after step {}", step); + let serialized = store + .serialize_image(id, 0, 0, rect.width, rect.height) + .expect("replacement image survives reaping"); + let decoded = sixel_image::SixelImage::new(serialized.as_bytes()).unwrap(); + assert!(decoded.pixels.iter().flatten().all(|pixel| { + pixel.on + && decoded.color_registers[&pixel.color] + == sixel_image::SixelColor::Rgb(step * 5, 0, 0) + })); + } + parser.advance(&mut grid, b"\x1b[3;4H "); + let (_, sixel_chunks, _) = grid.read_changes(0, 0); + assert!( + sixel_chunks.is_empty(), + "erased rasters must not be replayed" + ); + assert_eq!(grid.sixel_grid.sixel_image_store.borrow().image_count(), 0); + parser.advance(&mut grid, b"\x1b[3;4H\x1bP0;0q\"1;1;20;12?\x1b\\"); + let (_, sixel_chunks, _) = grid.read_changes(0, 0); + assert_eq!(sixel_chunks.len(), 1); +} + #[test] pub fn sixel_images_are_reaped_when_resetting() { let mut vte_parser = vte::Parser::new(); @@ -7250,7 +7295,7 @@ fn kitty_c_r_scaling_produces_exact_cell_rect_and_variant() { assert_eq!( kitty_image_store .borrow() - .scaled_variant(internal_id, (3, 2)) + .scaled_variant(internal_id, placement.scaled_image().unwrap()) .unwrap() .len(), 30 * 40 * 4 diff --git a/zellij-server/src/panes/unit/terminal_pane_tests.rs b/zellij-server/src/panes/unit/terminal_pane_tests.rs index a4307d49a..43ba4cf5b 100644 --- a/zellij-server/src/panes/unit/terminal_pane_tests.rs +++ b/zellij-server/src/panes/unit/terminal_pane_tests.rs @@ -1080,7 +1080,7 @@ fn create_guest_modal_pane() -> TerminalPane { fn press_key( pane: &mut TerminalPane, bare_key: zellij_utils::data::BareKey, - client_id: u16, + client_id: crate::ClientId, ) -> Option { let key = zellij_utils::data::KeyWithModifier::new(bare_key); pane.adjust_input_to_terminal(&Some(key), vec![], false, Some(client_id)) diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs index 0ab51a88f..a476380b8 100644 --- a/zellij-server/src/plugins/mod.rs +++ b/zellij-server/src/plugins/mod.rs @@ -88,6 +88,7 @@ pub enum PluginInstruction { Resize(PluginId, usize, usize), // plugin_id, columns, rows AddClient(ClientId), RemoveClient(ClientId), + UpdatePluginTabIndices(Vec<(PluginId, usize)>), NewTab( Option, Option, @@ -236,6 +237,7 @@ impl From<&PluginInstruction> for PluginContext { PluginInstruction::Exit => PluginContext::Exit, PluginInstruction::AddClient(_) => PluginContext::AddClient, PluginInstruction::RemoveClient(_) => PluginContext::RemoveClient, + PluginInstruction::UpdatePluginTabIndices(..) => PluginContext::UpdatePluginTabIndices, PluginInstruction::NewTab(..) => PluginContext::NewTab, PluginInstruction::OverrideLayout(..) => PluginContext::OverrideLayout, PluginInstruction::ApplyCachedEvents { .. } => PluginContext::ApplyCachedEvents, @@ -510,6 +512,9 @@ pub(crate) fn plugin_thread_main( PluginInstruction::RemoveClient(client_id) => { wasm_bridge.remove_client(client_id); }, + PluginInstruction::UpdatePluginTabIndices(tab_indices) => { + wasm_bridge.update_plugin_tab_indices(tab_indices); + }, PluginInstruction::NewTab( cwd, terminal_action, diff --git a/zellij-server/src/plugins/pipes.rs b/zellij-server/src/plugins/pipes.rs index a7a7e7ce4..24f453fbf 100644 --- a/zellij-server/src/plugins/pipes.rs +++ b/zellij-server/src/plugins/pipes.rs @@ -82,6 +82,24 @@ impl PendingPipes { } pipe_names_to_unblock } + pub fn unload_plugin_client( + &mut self, + plugin_id: &PluginId, + client_id: &ClientId, + ) -> Vec { + let mut pipe_names_to_unblock = vec![]; + for (pipe_name, pending_pipe_info) in self.pipes.iter_mut() { + let should_unblock_this_pipe = + pending_pipe_info.unload_plugin_client(plugin_id, client_id); + if should_unblock_this_pipe { + pipe_names_to_unblock.push(pipe_name.to_owned()); + } + } + for pipe_name in &pipe_names_to_unblock { + self.pipes.remove(pipe_name); + } + pipe_names_to_unblock + } } #[derive(Debug, Clone, Default)] @@ -135,6 +153,17 @@ impl PendingPipeInfo { false } } + pub fn unload_plugin_client( + &mut self, + plugin_id_to_unload: &PluginId, + client_id_to_unload: &ClientId, + ) -> bool { + self.currently_being_processed_by + .retain(|(plugin_id, client_id)| { + plugin_id != plugin_id_to_unload || client_id != client_id_to_unload + }); + self.currently_being_processed_by.is_empty() && !self.is_explicitly_blocked + } } pub fn apply_pipe_message_to_plugin( diff --git a/zellij-server/src/plugins/plugin_loader.rs b/zellij-server/src/plugins/plugin_loader.rs index 8121fbe19..8406ee606 100644 --- a/zellij-server/src/plugins/plugin_loader.rs +++ b/zellij-server/src/plugins/plugin_loader.rs @@ -1,5 +1,5 @@ use crate::plugins::plugin_map::{ - PluginEnv, PluginMap, RunningPlugin, VecDequeInputStream, WriteOutputStream, + PluginEnv, PluginMap, PluginMetadata, RunningPlugin, VecDequeInputStream, WriteOutputStream, }; use crate::plugins::plugin_worker::{plugin_worker, RunningWorker}; use crate::plugins::wasm_bridge::{LoadingContext, PluginCache}; @@ -132,6 +132,7 @@ impl<'a> PluginLoader<'a> { self } pub fn start_plugin(&mut self) -> Result<()> { + self.record_plugin_metadata(); let module = if self.skip_cache { self.interpret_module()? } else { @@ -143,6 +144,19 @@ impl<'a> PluginLoader<'a> { self.clone_instance_for_other_clients()?; Ok(()) } + fn record_plugin_metadata(&mut self) { + self.plugin_map.insert_metadata( + self.plugin_id, + PluginMetadata { + plugin_config: self.plugin_config.clone(), + tab_index: self.tab_index, + rows: self.size.rows, + columns: self.size.cols, + cwd: self.plugin_cwd.clone(), + is_background: self.tab_index.is_none(), + }, + ); + } fn interpret_module(&mut self) -> Result { self.loading_indication.override_previous_error(); let wasm_bytes = self.plugin_config.resolve_wasm_bytes(&self.plugin_dir)?; @@ -266,7 +280,6 @@ impl<'a> PluginLoader<'a> { wasi_ctx, plugin_own_data_dir: self.plugin_own_data_dir.clone(), plugin_own_cache_dir: self.plugin_own_cache_dir.clone(), - tab_index: self.tab_index, path_to_default_shell: self.path_to_default_shell.clone(), default_shell: self.default_shell.clone(), plugin_cwd: self.plugin_cwd.clone(), @@ -378,7 +391,6 @@ impl<'a> PluginLoader<'a> { wasi_ctx, plugin_own_data_dir: self.plugin_own_data_dir.clone(), plugin_own_cache_dir: self.plugin_own_cache_dir.clone(), - tab_index: self.tab_index, path_to_default_shell: self.path_to_default_shell.clone(), default_shell: self.default_shell.clone(), plugin_cwd: self.plugin_cwd.clone(), diff --git a/zellij-server/src/plugins/plugin_map.rs b/zellij-server/src/plugins/plugin_map.rs index 733a61e54..14558a188 100644 --- a/zellij-server/src/plugins/plugin_map.rs +++ b/zellij-server/src/plugins/plugin_map.rs @@ -27,33 +27,35 @@ use zellij_utils::{data::PermissionType, errors::prelude::*}; // so when adding/removing from the map - everything is halted, that's life // but when cloning the internal RunningPlugin and Subscriptions atomics, we can call methods on // them without blocking other instances +pub type PluginKey = (PluginId, ClientId); +pub type PluginAssets = ( + Arc>, + Arc>, + HashMap>, +); +pub type RemovedPluginAssets = HashMap; + +#[derive(Clone, Debug)] +pub struct PluginMetadata { + pub plugin_config: PluginConfig, + pub tab_index: Option, + pub rows: usize, + pub columns: usize, + pub cwd: PathBuf, + pub is_background: bool, +} + #[derive(Default)] pub struct PluginMap { - plugin_assets: HashMap< - (PluginId, ClientId), - ( - Arc>, - Arc>, - HashMap>, - ), - >, + plugin_assets: HashMap, + plugin_metadata: HashMap, } impl PluginMap { - pub fn remove_plugins( - &mut self, - pid: PluginId, - ) -> HashMap< - (PluginId, ClientId), - ( - Arc>, - Arc>, - HashMap>, - ), - > { + pub fn remove_plugins(&mut self, pid: PluginId) -> RemovedPluginAssets { + self.plugin_metadata.remove(&pid); let mut removed = HashMap::new(); - let ids_in_plugin_map: Vec<(PluginId, ClientId)> = - self.plugin_assets.keys().copied().collect(); + let ids_in_plugin_map: Vec = self.plugin_assets.keys().copied().collect(); for (plugin_id, client_id) in ids_in_plugin_map { if pid == plugin_id { if let Some(plugin_asset) = self.plugin_assets.remove(&(plugin_id, client_id)) { @@ -63,13 +65,44 @@ impl PluginMap { } removed } + pub fn remove_plugin_clients(&mut self, client_id: ClientId) -> RemovedPluginAssets { + let mut removed = HashMap::new(); + let ids_in_plugin_map: Vec = self.plugin_assets.keys().copied().collect(); + for key in ids_in_plugin_map { + if key.1 == client_id { + if let Some(plugin_asset) = self.plugin_assets.remove(&key) { + removed.insert(key, plugin_asset); + } + } + } + removed + } + pub fn insert_metadata(&mut self, plugin_id: PluginId, metadata: PluginMetadata) { + self.plugin_metadata.insert(plugin_id, metadata); + } + pub fn metadata(&self, plugin_id: PluginId) -> Option<&PluginMetadata> { + self.plugin_metadata.get(&plugin_id) + } + pub fn set_tab_index(&mut self, plugin_id: PluginId, tab_index: usize) { + if let Some(metadata) = self.plugin_metadata.get_mut(&plugin_id) { + if !metadata.is_background { + metadata.tab_index = Some(tab_index); + } + } + } + pub fn set_size(&mut self, plugin_id: PluginId, rows: usize, columns: usize) { + if let Some(metadata) = self.plugin_metadata.get_mut(&plugin_id) { + metadata.rows = rows; + metadata.columns = columns; + } + } + pub fn set_cwd(&mut self, plugin_id: PluginId, cwd: PathBuf) { + if let Some(metadata) = self.plugin_metadata.get_mut(&plugin_id) { + metadata.cwd = cwd; + } + } pub fn plugin_ids(&self) -> Vec { - let mut unique_plugins: HashSet = self - .plugin_assets - .keys() - .map(|(plugin_id, _client_id)| *plugin_id) - .collect(); - unique_plugins.drain().into_iter().collect() + self.plugin_metadata.keys().copied().collect() } pub fn running_plugins(&mut self) -> Vec<(PluginId, ClientId, Arc>)> { self.plugin_assets @@ -154,17 +187,14 @@ impl PluginMap { ) -> Result> { let err_context = || format!("Failed to get plugin ids for location {plugin_location}"); let plugin_ids: Vec = self - .plugin_assets + .plugin_metadata .iter() - .filter(|(_, (running_plugin, _subscriptions, _workers))| { - let running_plugin = running_plugin.lock().unwrap(); - let plugin_config = &running_plugin.store.data().plugin; - let running_plugin_location = &plugin_config.location; - let running_plugin_configuration = &plugin_config.initial_userspace_configuration; - running_plugin_location == plugin_location - && running_plugin_configuration == plugin_configuration + .filter(|(_plugin_id, metadata)| { + &metadata.plugin_config.location == plugin_location + && &metadata.plugin_config.initial_userspace_configuration + == plugin_configuration }) - .map(|((plugin_id, _client_id), _)| *plugin_id) + .map(|(plugin_id, _metadata)| *plugin_id) .collect(); if plugin_ids.is_empty() { return Err(ZellijError::PluginDoesNotExist).with_context(err_context); @@ -231,39 +261,25 @@ impl PluginMap { ); } pub fn run_plugin_of_plugin_id(&self, plugin_id: PluginId) -> Option { - self.plugin_assets - .iter() - .find_map(|((p_id, _), (running_plugin, _, _))| { - if *p_id == plugin_id { - let running_plugin = running_plugin.lock().unwrap(); - let plugin_config = &running_plugin.store.data().plugin; - let run_plugin_location = plugin_config.location.clone(); - let run_plugin_configuration = - plugin_config.initial_userspace_configuration.clone(); - let initial_cwd = plugin_config.initial_cwd.clone(); - Some(RunPlugin { - _allow_exec_host_cmd: false, - location: run_plugin_location, - configuration: run_plugin_configuration, - initial_cwd, - }) - } else { - None - } + self.plugin_metadata + .get(&plugin_id) + .map(|metadata| RunPlugin { + _allow_exec_host_cmd: false, + location: metadata.plugin_config.location.clone(), + configuration: metadata + .plugin_config + .initial_userspace_configuration + .clone(), + initial_cwd: metadata.plugin_config.initial_cwd.clone(), }) } pub fn list_plugins(&self) -> BTreeMap { - let all_plugin_ids: HashSet = self - .all_plugin_ids() - .into_iter() - .map(|(plugin_id, _client_id)| plugin_id) - .collect(); let mut plugin_ids_to_cmds: BTreeMap = BTreeMap::new(); - for plugin_id in all_plugin_ids { - let plugin_cmd = self.run_plugin_of_plugin_id(plugin_id); + for plugin_id in self.plugin_metadata.keys() { + let plugin_cmd = self.run_plugin_of_plugin_id(*plugin_id); match plugin_cmd { Some(plugin_cmd) => { - plugin_ids_to_cmds.insert(plugin_id, plugin_cmd.clone()); + plugin_ids_to_cmds.insert(*plugin_id, plugin_cmd.clone()); }, None => log::error!("Plugin with id: {plugin_id} not found"), } @@ -280,7 +296,6 @@ pub struct PluginEnv { pub permissions: Arc>>>, pub senders: ThreadSenders, pub wasi_ctx: WasiCtx, - pub tab_index: Option, pub client_id: ClientId, #[allow(dead_code)] pub plugin_own_data_dir: PathBuf, diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_pane_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_pane_plugin_command.snap index 9b2efb216..9cf819603 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_pane_plugin_command.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_pane_plugin_command.snap @@ -8,6 +8,7 @@ Some( None, NoPreference { borderless: None, + border_style: None, }, false, ClientId( diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_tabs_with_layout_plugin_command-2.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_tabs_with_layout_plugin_command-2.snap index 05830618b..c2e0af65b 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_tabs_with_layout_plugin_command-2.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_tabs_with_layout_plugin_command-2.snap @@ -18,6 +18,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -36,6 +37,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -51,6 +53,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_tabs_with_layout_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_tabs_with_layout_plugin_command.snap index 54d7a13fb..a9d5403fc 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_tabs_with_layout_plugin_command.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_tabs_with_layout_plugin_command.snap @@ -18,6 +18,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -36,6 +37,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -51,6 +53,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_command_pane_background_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_command_pane_background_plugin_command.snap index 54aec244e..edfb695ad 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_command_pane_background_plugin_command.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_command_pane_background_plugin_command.snap @@ -31,6 +31,7 @@ Some( None, NoPreference { borderless: None, + border_style: None, }, true, ClientId( diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_command_pane_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_command_pane_plugin_command.snap index 343bc4f48..9a6f802de 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_command_pane_plugin_command.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_command_pane_plugin_command.snap @@ -30,6 +30,7 @@ Some( Tiled { direction: None, borderless: None, + border_style: None, }, false, ClientId( diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_file_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_file_plugin_command.snap index 69dc0b9c5..3edf07214 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_file_plugin_command.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_file_plugin_command.snap @@ -28,6 +28,7 @@ Some( Tiled { direction: None, borderless: None, + border_style: None, }, false, ClientId( diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_file_with_line_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_file_with_line_plugin_command.snap index 0aef35090..3d4a7f219 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_file_with_line_plugin_command.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_file_with_line_plugin_command.snap @@ -30,6 +30,7 @@ Some( Tiled { direction: None, borderless: None, + border_style: None, }, false, ClientId( diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_terminal_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_terminal_plugin_command.snap index e64921771..64979704e 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_terminal_plugin_command.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__open_terminal_plugin_command.snap @@ -23,6 +23,7 @@ Some( Tiled { direction: None, borderless: None, + border_style: None, }, false, ClientId( diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__override_layout_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__override_layout_plugin_command.snap index 2ef7c7a36..da0694f42 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__override_layout_plugin_command.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__override_layout_plugin_command.snap @@ -21,6 +21,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -60,6 +61,7 @@ Some( borderless: Some( true, ), + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -75,6 +77,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -112,6 +115,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -130,6 +134,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: Some( 0, @@ -147,6 +152,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -162,6 +168,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -201,6 +208,7 @@ Some( borderless: Some( true, ), + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -216,6 +224,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -248,6 +257,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: Some( 0, @@ -272,6 +282,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -290,6 +301,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -308,6 +320,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -326,6 +339,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -341,6 +355,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -356,6 +371,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -371,6 +387,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -410,6 +427,7 @@ Some( borderless: Some( true, ), + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -425,6 +443,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -457,6 +476,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: Some( 0, @@ -481,6 +501,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -499,6 +520,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -517,6 +539,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -535,6 +558,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -550,6 +574,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -572,6 +597,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -590,6 +616,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -608,6 +635,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -626,6 +654,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -641,6 +670,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -656,6 +686,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -671,6 +702,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -710,6 +742,7 @@ Some( borderless: Some( true, ), + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -725,6 +758,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -760,6 +794,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -778,6 +813,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -793,6 +829,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -832,6 +869,7 @@ Some( borderless: Some( true, ), + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -847,6 +885,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -879,6 +918,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: Some( 0, @@ -903,6 +943,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -921,6 +962,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -939,6 +981,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -957,6 +1000,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -972,6 +1016,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -987,6 +1032,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1002,6 +1048,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1041,6 +1088,7 @@ Some( borderless: Some( true, ), + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1056,6 +1104,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1088,6 +1137,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: Some( 0, @@ -1112,6 +1162,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1130,6 +1181,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1148,6 +1200,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1166,6 +1219,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1181,6 +1235,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1203,6 +1258,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1221,6 +1277,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1239,6 +1296,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1257,6 +1315,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1272,6 +1331,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1287,6 +1347,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1302,6 +1363,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1341,6 +1403,7 @@ Some( borderless: Some( true, ), + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1356,6 +1419,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1391,6 +1455,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: Some( 0, @@ -1408,6 +1473,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1447,6 +1513,7 @@ Some( borderless: Some( true, ), + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1462,6 +1529,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1501,6 +1569,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1519,6 +1588,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: Some( 0, @@ -1536,6 +1606,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1551,6 +1622,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1590,6 +1662,7 @@ Some( borderless: Some( true, ), + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1605,6 +1678,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -1662,6 +1736,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -1694,6 +1769,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -1726,6 +1802,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -1758,6 +1835,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -1790,6 +1868,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -1822,6 +1901,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -1854,6 +1934,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -1886,6 +1967,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -1918,6 +2000,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -1950,6 +2033,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -1985,6 +2069,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -2017,6 +2102,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -2045,6 +2131,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -2077,6 +2164,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -2105,6 +2193,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -2133,6 +2222,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -2169,6 +2259,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -2201,6 +2292,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -2233,6 +2325,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -2265,6 +2358,7 @@ Some( ), pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, diff --git a/zellij-server/src/plugins/wasm_bridge.rs b/zellij-server/src/plugins/wasm_bridge.rs index 5aa2d3681..faba63375 100644 --- a/zellij-server/src/plugins/wasm_bridge.rs +++ b/zellij-server/src/plugins/wasm_bridge.rs @@ -4,7 +4,9 @@ use crate::plugins::pipes::{ apply_pipe_message_to_plugin, pipes_to_block_or_unblock, PendingPipes, PipeStateChange, }; use crate::plugins::plugin_loader::PluginLoader; -use crate::plugins::plugin_map::{AtomicEvent, PluginEnv, PluginMap, RunningPlugin, Subscriptions}; +use crate::plugins::plugin_map::{ + AtomicEvent, PluginEnv, PluginMap, RemovedPluginAssets, RunningPlugin, Subscriptions, +}; use crate::plugins::plugin_worker::MessageToWorker; use crate::plugins::watch_filesystem::watch_filesystem; @@ -688,10 +690,11 @@ impl WasmBridge { return Ok(()); } - let mut new_plugins = HashSet::new(); - for plugin_id in self.plugin_map.lock().unwrap().plugin_ids() { - new_plugins.insert(plugin_id); - } + let new_plugins: HashSet = { + let plugin_map = self.plugin_map.lock().unwrap(); + self.connected_clients.lock().unwrap().push(client_id); + plugin_map.plugin_ids().into_iter().collect() + }; for plugin_id in new_plugins { if self .plugin_map @@ -783,7 +786,6 @@ impl WasmBridge { }, ) } - self.connected_clients.lock().unwrap().push(client_id); Ok(()) } pub fn resize_plugin( @@ -795,6 +797,11 @@ impl WasmBridge { ) -> Result<()> { let err_context = move || format!("failed to resize plugin {pid}"); + self.plugin_map + .lock() + .unwrap() + .set_size(pid, new_rows, new_columns); + let plugins_to_resize: Vec<(PluginId, ClientId, Arc>)> = self .plugin_map .lock() @@ -1028,7 +1035,7 @@ impl WasmBridge { // Execute directly on pinned thread (no async I/O needed for directory check/change) self.plugin_executor .execute_for_plugin(plugin_id_to_update, { - move |senders, _plugin_map, _connected_clients, _plugin_cache, _engine| { + move |senders, plugin_map, _connected_clients, _plugin_cache, _engine| { match new_host_dir.try_exists() { Ok(false) => { log::error!( @@ -1081,6 +1088,11 @@ impl WasmBridge { Ok(wasi_ctx) => { drop(std::mem::replace(&mut plugin_env.wasi_ctx, wasi_ctx)); plugin_env.plugin_cwd = new_host_dir.clone(); + drop(running_plugin); + plugin_map + .lock() + .unwrap() + .set_cwd(*plugin_id, new_host_dir.clone()); let _ = senders.send_to_plugin(PluginInstruction::Update(vec![( @@ -1258,6 +1270,78 @@ impl WasmBridge { if let Some(ref mut prev_report) = self.previous_pane_render_report { prev_report.all_pane_contents.remove(&client_id); } + + let instances_to_clean_up = self + .plugin_map + .lock() + .unwrap() + .remove_plugin_clients(client_id); + self.clean_up_plugin_instances(instances_to_clean_up); + + for messages in self.cached_worker_messages.values_mut() { + messages.retain(|(c_id, _, _, _)| c_id != &client_id); + } + self.cached_worker_messages + .retain(|_plugin_id, messages| !messages.is_empty()); + } + + pub fn update_plugin_tab_indices(&mut self, tab_indices: Vec<(PluginId, usize)>) { + let mut plugin_map = self.plugin_map.lock().unwrap(); + for (plugin_id, tab_index) in tab_indices { + plugin_map.set_tab_index(plugin_id, tab_index); + } + } + + fn clean_up_plugin_instances(&mut self, instances: RemovedPluginAssets) { + if instances.is_empty() { + return; + } + for ((plugin_id, client_id), (running_plugin, subscriptions, workers)) in instances { + if running_plugin.lock().unwrap().intercepting_key_presses() { + let _ = self + .senders + .send_to_screen(ScreenInstruction::ClearKeyPressesIntercepts(client_id)); + } + let _ = self.senders.send_to_screen( + ScreenInstruction::UpdateBackgroundPluginSubscriptions( + plugin_id, + client_id, + HashSet::new(), + ), + ); + + for (_worker_name, worker_sender) in workers { + drop(worker_sender.send(MessageToWorker::Exit)); + } + + self.plugin_executor.execute_for_plugin(plugin_id, { + move |_senders, _plugin_map, _connected_clients, _plugin_cache, _engine| { + let cache_dir = running_plugin + .lock() + .unwrap() + .store + .data() + .plugin_own_data_dir + .clone(); + if let Err(e) = std::fs::remove_dir_all(&cache_dir) { + log::error!("Failed to remove data dir for plugin instance: {:?}", e); + } + drop(running_plugin); + drop(subscriptions); + } + }); + + let mut pipes_to_unblock = self + .pending_pipes + .unload_plugin_client(&plugin_id, &client_id); + for pipe_name in pipes_to_unblock.drain(..) { + let _ = self + .senders + .send_to_server(ServerInstruction::UnblockCliPipeInput(pipe_name)) + .context("failed to unblock input pipe"); + } + } + self.cached_plugin_map.clear(); } fn get_changed_panes_per_client( @@ -1329,17 +1413,13 @@ impl WasmBridge { client_id: ClientId, events: HashSet, ) { - // Check if this plugin is a background plugin (tab_index == None) let is_background = { - let mut plugin_map = self.plugin_map.lock().unwrap(); - plugin_map - .running_plugins_and_subscriptions() - .iter() - .any(|(pid, cid, rp, _)| { - *pid == plugin_id - && *cid == client_id - && rp.lock().unwrap().store.data().tab_index.is_none() - }) + let plugin_map = self.plugin_map.lock().unwrap(); + plugin_map.contains(plugin_id, client_id) + && plugin_map + .metadata(plugin_id) + .map(|metadata| metadata.is_background) + .unwrap_or(false) }; if is_background { let _ = self.senders.send_to_screen( @@ -1689,41 +1769,29 @@ impl WasmBridge { self.plugin_map .lock() .unwrap() - .get_running_plugin(plugin_id, None) - .map(|r| { - let r = r.lock().unwrap(); - (r.rows, r.columns) - }) + .metadata(plugin_id) + .map(|metadata| (metadata.rows, metadata.columns)) } fn cwd_of_plugin_id(&self, plugin_id: PluginId) -> Option { self.plugin_map .lock() .unwrap() - .get_running_plugin(plugin_id, None) - .map(|r| { - let r = r.lock().unwrap(); - r.store.data().plugin_cwd.clone() - }) + .metadata(plugin_id) + .map(|metadata| metadata.cwd.clone()) } fn plugin_config_of_plugin_id(&self, plugin_id: PluginId) -> Option { self.plugin_map .lock() .unwrap() - .get_running_plugin(plugin_id, None) - .map(|r| { - let r = r.lock().unwrap(); - r.store.data().plugin.clone() - }) + .metadata(plugin_id) + .map(|metadata| metadata.plugin_config.clone()) } fn tab_index_of_plugin_id(&self, plugin_id: PluginId) -> Option { self.plugin_map .lock() .unwrap() - .get_running_plugin(plugin_id, None) - .and_then(|r| { - let r = r.lock().unwrap(); - r.store.data().tab_index - }) + .metadata(plugin_id) + .and_then(|metadata| metadata.tab_index) } fn start_plugin_loading_indication( &self, @@ -1815,6 +1883,10 @@ impl WasmBridge { .data_mut() .set_permissions(HashSet::from_iter(permissions.clone())); + if running_plugin.store.data().plugin.is_builtin() { + return Ok(()); + } + let mut permission_cache = PermissionCache::from_path_or_default(cache_path); permission_cache.cache( running_plugin.store.data().plugin.location.to_string(), diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs index e562467ae..8841bf266 100644 --- a/zellij-server/src/plugins/zellij_exports.rs +++ b/zellij-server/src/plugins/zellij_exports.rs @@ -21,13 +21,14 @@ use tokio::sync::oneshot; use wasmi::{Caller, Linker}; use zellij_utils::consts::ipc_connect; use zellij_utils::data::{ - BreakPanesToNewTabResponse, BreakPanesToTabWithIdResponse, BreakPanesToTabWithIndexResponse, - CommandType, ConnectToSession, DeleteAllDeadSessionsResponse, DeleteDeadSessionResponse, - DeleteLayoutResponse, EditLayoutResponse, Event, FloatingPaneCoordinates, - FocusOrCreateTabResponse, GetFocusedPaneInfoResponse, GetPaneCwdResponse, GetPanePidResponse, - GetPaneRunningCommandResponse, HttpVerb, KeyWithModifier, KillSessionsResponse, LayoutInfo, - LayoutMetadata, LayoutParsingError, MessageToPlugin, NewPanePlacement, NewTabResponse, - NewTabUnfocusedResponse, NewTiledPaneInTabResponse, OpenCommandPaneBackgroundResponse, + BorderStyleOverride, BreakPanesToNewTabResponse, BreakPanesToTabWithIdResponse, + BreakPanesToTabWithIndexResponse, CommandType, ConnectToSession, DeleteAllDeadSessionsResponse, + DeleteDeadSessionResponse, DeleteLayoutResponse, EditLayoutResponse, Event, + FloatingPaneCoordinates, FocusOrCreateTabResponse, GetFocusedPaneInfoResponse, + GetPaneCwdResponse, GetPanePidResponse, GetPaneRunningCommandResponse, HttpVerb, + KeyWithModifier, KillSessionsResponse, LayoutInfo, LayoutMetadata, LayoutParsingError, + MessageToPlugin, NewPanePlacement, NewTabResponse, NewTabUnfocusedResponse, + NewTiledPaneInTabResponse, OpenCommandPaneBackgroundResponse, OpenCommandPaneFloatingNearPluginResponse, OpenCommandPaneFloatingResponse, OpenCommandPaneInPlaceOfPaneIdResponse, OpenCommandPaneInPlaceOfPluginResponse, OpenCommandPaneInPlaceResponse, OpenCommandPaneNearPluginResponse, OpenCommandPaneResponse, @@ -199,21 +200,35 @@ fn host_run_plugin_command(mut caller: Caller<'_, PluginEnv>) { floating_pane_coordinates, context, ) => open_file_floating(env, file_to_open, floating_pane_coordinates, context), - PluginCommand::OpenTerminal(cwd) => open_terminal(env, cwd.path.try_into()?), + PluginCommand::OpenTerminal(cwd) => { + let border_style = cwd.border_style; + open_terminal(env, cwd.path.try_into()?, border_style) + }, PluginCommand::OpenTerminalNearPlugin(cwd) => { - open_terminal_near_plugin(env, cwd.path.try_into()?) + let border_style = cwd.border_style; + open_terminal_near_plugin(env, cwd.path.try_into()?, border_style) }, PluginCommand::OpenTerminalFloating(cwd, floating_pane_coordinates) => { + let floating_pane_coordinates = FloatingPaneCoordinates::merge_border_style( + floating_pane_coordinates, + cwd.border_style, + ); open_terminal_floating(env, cwd.path.try_into()?, floating_pane_coordinates) }, PluginCommand::OpenTerminalFloatingNearPlugin( cwd, floating_pane_coordinates, - ) => open_terminal_floating_near_plugin( - env, - cwd.path.try_into()?, - floating_pane_coordinates, - ), + ) => { + let floating_pane_coordinates = FloatingPaneCoordinates::merge_border_style( + floating_pane_coordinates, + cwd.border_style, + ); + open_terminal_floating_near_plugin( + env, + cwd.path.try_into()?, + floating_pane_coordinates, + ) + }, PluginCommand::OpenCommandPane(command_to_run, context) => { open_command_pane(env, command_to_run, context) }, @@ -351,6 +366,12 @@ fn host_run_plugin_command(mut caller: Caller<'_, PluginEnv>) { PluginCommand::QuitZellij => quit_zellij(env), PluginCommand::PreviousSwapLayout => previous_swap_layout(env), PluginCommand::NextSwapLayout => next_swap_layout(env), + PluginCommand::ApplyTiledSwapLayout(layout_name) => { + apply_tiled_swap_layout(env, layout_name) + }, + PluginCommand::ApplyFloatingSwapLayout(layout_name) => { + apply_floating_swap_layout(env, layout_name) + }, PluginCommand::GoToTabName(tab_name) => go_to_tab_name(env, tab_name), PluginCommand::FocusOrCreateTab(tab_name) => focus_or_create_tab(env, tab_name), PluginCommand::GoToTab(tab_index) => go_to_tab(env, tab_index), @@ -622,6 +643,9 @@ fn host_run_plugin_command(mut caller: Caller<'_, PluginEnv>) { PluginCommand::TogglePaneBorderless(pane_id) => { toggle_pane_borderless(env, pane_id.into()) }, + PluginCommand::SetPaneBorderStyle(pane_id, border_style) => { + set_pane_border_style(env, pane_id.into(), border_style) + }, PluginCommand::SetPaneBorderless(pane_id, borderless) => { set_pane_borderless(env, pane_id.into(), borderless) }, @@ -901,8 +925,9 @@ fn show_cursor(env: &PluginEnv, cursor_position: Option<(usize, usize)>) { } fn request_permission(env: &PluginEnv, permissions: Vec) -> Result<()> { - if PermissionCache::from_path_or_default(None) - .check_permissions(env.plugin.location.to_string(), &permissions) + if env.plugin.is_builtin() + || PermissionCache::from_path_or_default(None) + .check_permissions(env.plugin.location.to_string(), &permissions) { return env .senders @@ -1208,6 +1233,7 @@ fn open_editor_pane_in_new_tab( .map(|cwd| translate_plugin_path(env, cwd)) .or_else(|| Some(env.plugin_cwd.clone())); let file_to_open = FileToOpen { + border_style: None, path, cwd, ..file_to_open @@ -1407,6 +1433,7 @@ fn open_file(env: &PluginEnv, file_to_open: FileToOpen, context: BTreeMap, ) { + let border_style = file_to_open.border_style; let cwd = file_to_open .cwd .map(|cwd| translate_plugin_path(env, cwd)) @@ -1600,7 +1632,7 @@ fn open_file_near_plugin( let pty_instr = PtyInstruction::SpawnTerminal( Some(open_file), Some(title), - NewPanePlacement::default(), + NewPanePlacement::default().with_border_style(border_style), start_suppressed, ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)), Some(NotificationEnd::new(completion_tx)), @@ -1625,6 +1657,10 @@ fn open_file_floating_near_plugin( floating_pane_coordinates: Option, context: BTreeMap, ) { + let floating_pane_coordinates = FloatingPaneCoordinates::merge_border_style( + floating_pane_coordinates, + file_to_open.border_style, + ); let cwd = file_to_open .cwd .map(|cwd| translate_plugin_path(env, cwd)) @@ -1702,7 +1738,7 @@ fn open_file_in_place_of_plugin( .non_fatal(); } -fn open_terminal(env: &PluginEnv, cwd: PathBuf) { +fn open_terminal(env: &PluginEnv, cwd: PathBuf, border_style: Option) { let error_msg = || format!("failed to open file in plugin {}", env.name()); let cwd = translate_plugin_path(env, cwd); let mut default_shell = env.default_shell.clone().unwrap_or_else(|| { @@ -1724,6 +1760,7 @@ fn open_terminal(env: &PluginEnv, cwd: PathBuf) { near_current_pane: false, no_focus: false, borderless: None, + border_style, tab_id: None, }; let result = apply_action!(action, error_msg, env); @@ -1738,7 +1775,11 @@ fn open_terminal(env: &PluginEnv, cwd: PathBuf) { .non_fatal(); } -fn open_terminal_near_plugin(env: &PluginEnv, cwd: PathBuf) { +fn open_terminal_near_plugin( + env: &PluginEnv, + cwd: PathBuf, + border_style: Option, +) { let cwd = translate_plugin_path(env, cwd); let mut default_shell = env.default_shell.clone().unwrap_or_else(|| { TerminalAction::RunCommand(RunCommand { @@ -1758,6 +1799,7 @@ fn open_terminal_near_plugin(env: &PluginEnv, cwd: PathBuf) { NewPanePlacement::Tiled { direction: None, borderless: None, + border_style, }, false, ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)), @@ -1940,6 +1982,7 @@ fn open_command_pane_in_place_of_plugin( context: BTreeMap, ) { let command = command_to_run.path; + let _border_style = command_to_run.border_style; let cwd = command_to_run .cwd .map(|cwd| translate_plugin_path(env, cwd)); @@ -2039,6 +2082,7 @@ fn open_command_pane_in_place_of_pane_id( context: BTreeMap, ) { let command = command_to_run.path; + let _border_style = command_to_run.border_style; let cwd = command_to_run .cwd .map(|cwd| translate_plugin_path(env, cwd)); @@ -2134,6 +2178,7 @@ fn open_command_pane( ) { let error_msg = || format!("failed to open command in plugin {}", env.name()); let command = command_to_run.path; + let border_style = command_to_run.border_style; let cwd = command_to_run .cwd .map(|cwd| translate_plugin_path(env, cwd)); @@ -2164,6 +2209,7 @@ fn open_command_pane( near_current_pane: false, no_focus: false, borderless: None, + border_style, tab_id: None, }; let result = apply_action!(action, error_msg, env); @@ -2185,6 +2231,7 @@ fn open_command_pane_near_plugin( context: BTreeMap, ) { let command = command_to_run.path; + let border_style = command_to_run.border_style; let cwd = command_to_run .cwd .map(|cwd| translate_plugin_path(env, cwd)); @@ -2218,6 +2265,7 @@ fn open_command_pane_near_plugin( NewPanePlacement::Tiled { direction: None, borderless: None, + border_style, }, false, ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)), @@ -2244,6 +2292,7 @@ fn open_command_pane_floating( ) { let error_msg = || format!("failed to open command in plugin {}", env.name()); let command = command_to_run.path; + let border_style = command_to_run.border_style; let cwd = command_to_run .cwd .map(|cwd| translate_plugin_path(env, cwd)); @@ -2270,7 +2319,10 @@ fn open_command_pane_floating( let action = Action::NewFloatingPane { command: Some(run_command_action), pane_name: name, - coordinates: floating_pane_coordinates, + coordinates: FloatingPaneCoordinates::merge_border_style( + floating_pane_coordinates, + border_style, + ), near_current_pane: false, no_focus: false, tab_id: None, @@ -2295,6 +2347,7 @@ fn open_command_pane_floating_near_plugin( context: BTreeMap, ) { let command = command_to_run.path; + let border_style = command_to_run.border_style; let cwd = command_to_run .cwd .map(|cwd| translate_plugin_path(env, cwd)); @@ -2325,7 +2378,10 @@ fn open_command_pane_floating_near_plugin( let _ = env.senders.send_to_pty(PtyInstruction::SpawnTerminal( Some(run_cmd), name, - NewPanePlacement::Floating(floating_pane_coordinates), + NewPanePlacement::Floating(FloatingPaneCoordinates::merge_border_style( + floating_pane_coordinates, + border_style, + )), false, ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)), Some(NotificationEnd::new(completion_tx)), @@ -2355,6 +2411,7 @@ fn open_command_pane_in_place( ) { let error_msg = || format!("failed to open command in plugin {}", env.name()); let command = command_to_run.path; + let _border_style = command_to_run.border_style; let cwd = command_to_run .cwd .map(|cwd| translate_plugin_path(env, cwd)); @@ -2869,6 +2926,7 @@ fn new_tiled_pane_in_tab(env: &PluginEnv, tab_position: usize) { near_current_pane: false, no_focus: false, borderless: None, + border_style: None, tab_id: Some(tab_position), }; let result = apply_action!(action, error_msg, env); @@ -3251,6 +3309,23 @@ fn next_swap_layout(env: &PluginEnv) { apply_action!(action, error_msg, env); } +fn apply_tiled_swap_layout(env: &PluginEnv, layout_name: String) { + let error_msg = || format!("failed to apply tiled swap layout in plugin {}", env.name()); + let action = Action::ApplyTiledSwapLayout { name: layout_name }; + apply_action!(action, error_msg, env); +} + +fn apply_floating_swap_layout(env: &PluginEnv, layout_name: String) { + let error_msg = || { + format!( + "failed to apply floating swap layout in plugin {}", + env.name() + ) + }; + let action = Action::ApplyFloatingSwapLayout { name: layout_name }; + apply_action!(action, error_msg, env); +} + fn go_to_tab_name(env: &PluginEnv, tab_name: String) { let error_msg = || format!("failed to change tab in plugin {}", env.name()); let create = false; @@ -3945,6 +4020,16 @@ fn set_pane_borderless(env: &PluginEnv, pane_id: PaneId, borderless: bool) { )); } +fn set_pane_border_style(env: &PluginEnv, pane_id: PaneId, border_style: BorderStyleOverride) { + let _ = env + .senders + .send_to_screen(ScreenInstruction::SetPaneBorderStyle( + pane_id, + border_style, + None, + )); +} + fn set_pane_color(env: &PluginEnv, pane_id: PaneId, fg: Option, bg: Option) { let _ = env .senders @@ -4685,6 +4770,7 @@ fn try_edit_layout( // Create FileToOpen for the layout file let file_to_open = FileToOpen { + border_style: None, path: file_path, line_number: None, cwd: Some(layout_dir.clone()), @@ -5520,6 +5606,8 @@ fn check_command_permission( | PluginCommand::QuitZellij | PluginCommand::PreviousSwapLayout | PluginCommand::NextSwapLayout + | PluginCommand::ApplyTiledSwapLayout(..) + | PluginCommand::ApplyFloatingSwapLayout(..) | PluginCommand::GoToTabName(..) | PluginCommand::FocusOrCreateTab(..) | PluginCommand::GoToTab(..) @@ -5554,6 +5642,7 @@ fn check_command_permission( | PluginCommand::ChangeFloatingPanesCoordinates(..) | PluginCommand::TogglePaneBorderless(..) | PluginCommand::SetPaneBorderless(..) + | PluginCommand::SetPaneBorderStyle(..) | PluginCommand::SetPaneColor(..) | PluginCommand::GroupAndUngroupPanes(..) | PluginCommand::HighlightAndUnhighlightPanes(..) diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 7ee9e0d59..5b9dff480 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -23,7 +23,7 @@ use zellij_utils::{ UnblockCondition, }, envs, - errors::prelude::*, + errors::{prelude::*, ErrorContext}, input::{ actions::{Action, SearchDirection, SearchOption}, command::TerminalAction, @@ -678,8 +678,12 @@ pub(crate) fn route_action( Some(direction) => NewPanePlacement::Tiled { direction: Some(direction), borderless: None, + border_style: None, + }, + None => NewPanePlacement::NoPreference { + borderless: None, + border_style: None, }, - None => NewPanePlacement::NoPreference { borderless: None }, }; senders .send_to_pty(PtyInstruction::SpawnTerminal( @@ -782,7 +786,12 @@ pub(crate) fn route_action( } else { NewPanePlacement::Tiled { direction: split_direction, - borderless: None, + borderless: floating_pane_coordinates + .as_ref() + .and_then(|c| c.borderless), + border_style: floating_pane_coordinates + .as_ref() + .and_then(|c| c.border_style), } }, start_suppressed, @@ -889,6 +898,7 @@ pub(crate) fn route_action( NewPanePlacement::Stacked { pane_id_to_stack_under: None, borderless: None, + border_style: None, }, if no_focus { ClientTabIndexOrPaneId::TabIndexNoFocus(tab_id) @@ -902,6 +912,7 @@ pub(crate) fn route_action( NewPanePlacement::Stacked { pane_id_to_stack_under: Some(pane_id.into()), borderless: None, + border_style: None, }, ClientTabIndexOrPaneId::PaneId(pane_id), ) @@ -910,6 +921,7 @@ pub(crate) fn route_action( NewPanePlacement::Stacked { pane_id_to_stack_under: None, borderless: None, + border_style: None, }, if no_focus { ClientTabIndexOrPaneId::ClientIdNoFocus(client_id) @@ -937,6 +949,7 @@ pub(crate) fn route_action( near_current_pane, no_focus, borderless, + border_style, tab_id, } => { let run_cmd = run_command @@ -951,6 +964,7 @@ pub(crate) fn route_action( NewPanePlacement::Tiled { direction, borderless, + border_style, }, false, client_tab_index_or_paneid, @@ -1008,6 +1022,7 @@ pub(crate) fn route_action( NewPanePlacement::Tiled { direction: command.direction, borderless: None, + border_style: None, }, false, client_tab_index_or_paneid, @@ -1337,6 +1352,24 @@ pub(crate) fn route_action( )) .with_context(err_context)?; }, + Action::ApplyTiledSwapLayout { name } => { + senders + .send_to_screen(ScreenInstruction::ApplyTiledSwapLayout( + client_id, + name, + Some(NotificationEnd::new(completion_tx)), + )) + .with_context(err_context)?; + }, + Action::ApplyFloatingSwapLayout { name } => { + senders + .send_to_screen(ScreenInstruction::ApplyFloatingSwapLayout( + client_id, + name, + Some(NotificationEnd::new(completion_tx)), + )) + .with_context(err_context)?; + }, Action::OverrideLayout { tabs, retain_existing_terminal_panes, @@ -1897,6 +1930,18 @@ pub(crate) fn route_action( )) .with_context(err_context)?; }, + Action::SetPaneBorderStyle { + pane_id, + border_style, + } => { + senders + .send_to_screen(ScreenInstruction::SetPaneBorderStyle( + pane_id.into(), + border_style, + Some(NotificationEnd::new(completion_tx)), + )) + .with_context(err_context)?; + }, Action::TogglePaneInGroup => { senders .send_to_screen(ScreenInstruction::TogglePaneInGroup( @@ -2160,6 +2205,24 @@ pub(crate) fn route_action( )) .with_context(err_context)?; }, + Action::ApplyTiledSwapLayoutByTabId { id, name } => { + senders + .send_to_screen(ScreenInstruction::ApplyTiledSwapLayoutWithTabId( + id as usize, + name, + Some(NotificationEnd::new(completion_tx)), + )) + .with_context(err_context)?; + }, + Action::ApplyFloatingSwapLayoutByTabId { id, name } => { + senders + .send_to_screen(ScreenInstruction::ApplyFloatingSwapLayoutWithTabId( + id as usize, + name, + Some(NotificationEnd::new(completion_tx)), + )) + .with_context(err_context)?; + }, Action::MoveTabByTabId { id, direction } => { senders .send_to_screen(ScreenInstruction::MoveTabWithTabId( @@ -2257,13 +2320,21 @@ pub(crate) fn route_thread_main( to_server: SenderWithContext, mut receiver: IpcReceiverWithContext, client_id: ClientId, + first_instruction: Option, ) -> Result<()> { let mut retry_queue = VecDeque::new(); let err_context = || format!("failed to handle instruction for client {client_id}"); let mut seen_cli_pipes = HashSet::new(); let mut consecutive_unknown_messages_received = 0; + let mut first_instruction = + first_instruction.map(|instruction| (instruction, ErrorContext::default())); + let mut cleanup_requested = false; 'route_loop: loop { - match receiver.try_recv_client_msg() { + let received = match first_instruction.take() { + Some(instruction) => Ok(instruction), + None => receiver.try_recv_client_msg(), + }; + match received { Ok((instruction, err_ctx)) => { consecutive_unknown_messages_received = 0; err_ctx.update_thread_ctx(); @@ -2674,7 +2745,6 @@ pub(crate) fn route_thread_main( } }, ClientToServerMsg::ClientExited => { - let _ = to_server.send(ServerInstruction::RemoveClient(client_id)); return Ok(true); }, ClientToServerMsg::KillSession => { @@ -2927,6 +2997,7 @@ pub(crate) fn route_thread_main( }, ); let _ = to_server.send(ServerInstruction::RemoveClient(client_id)); + cleanup_requested = true; break 'route_loop; } }, @@ -2938,7 +3009,9 @@ pub(crate) fn route_thread_main( let _ = os_input.send_to_client(client_id, ServerToClientMsg::UnblockInputThread); } // route thread exited, make sure we clean up - let _ = to_server.send(ServerInstruction::RemoveClient(client_id)); + if !cleanup_requested { + let _ = to_server.send(ServerInstruction::RemoveClient(client_id)); + } Ok(()) } diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 5f8f428de..0355912c0 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -40,11 +40,12 @@ use crate::route::NotificationEnd; use log::{debug, warn}; use zellij_utils::data::{ - CommandOrPlugin, Direction, EventType, FloatingPaneCoordinates, GetFocusedPaneInfoResponse, - HostTerminalThemeMode, KeyWithModifier, LayoutInfo, LayoutWithError, ListPanesResponse, - ListTabsResponse, NewPanePlacement, PaneContents, PaneInfo, PaneListEntry, PaneManifest, - PaneRenderReport, PaneScrollbackResponse, PluginPermission, RegexHighlight, Resize, - ResizeStrategy, SessionInfo, Styling, TabInfo, ThemeHue, WebSharing, + BorderStyle, BorderStyleOverride, CommandOrPlugin, Direction, EventType, + FloatingPaneCoordinates, GetFocusedPaneInfoResponse, HostTerminalThemeMode, KeyWithModifier, + LayoutInfo, LayoutWithError, ListPanesResponse, ListTabsResponse, NewPanePlacement, + PaneContents, PaneInfo, PaneListEntry, PaneManifest, PaneRenderReport, PaneScrollbackResponse, + PluginPermission, RegexHighlight, Resize, ResizeStrategy, SessionInfo, Styling, TabInfo, + ThemeHue, WebSharing, }; use zellij_utils::errors::prelude::*; use zellij_utils::input::actions::Action; @@ -647,6 +648,8 @@ pub enum ScreenInstruction { }, PreviousSwapLayout(ClientId, Option), NextSwapLayout(ClientId, Option), + ApplyTiledSwapLayout(ClientId, String, Option), + ApplyFloatingSwapLayout(ClientId, String, Option), OverrideLayout( Option, // cwd (applies to all tabs) Option, // default_shell (applies to all tabs) @@ -812,6 +815,8 @@ pub enum ScreenInstruction { auto_layout: bool, rounded_corners: bool, hide_session_name: bool, + border_style: BorderStyle, + floating_border_style: BorderStyle, stacked_resize: bool, stacked_pane_list: bool, default_editor: Option, @@ -890,6 +895,7 @@ pub enum ScreenInstruction { ), TogglePaneBorderless(PaneId, Option), SetPaneBorderless(PaneId, bool, Option), + SetPaneBorderStyle(PaneId, BorderStyleOverride, Option), AddHighlightPaneFrameColorOverride(Vec, Option), // Option => optional // message GroupAndUngroupPanes(Vec, Vec, bool, ClientId), // panes_to_group, panes_to_ungroup, bool -> for all clients @@ -959,6 +965,8 @@ pub enum ScreenInstruction { ToggleFloatingPanesWithTabId(usize, Option, Option), PreviousSwapLayoutWithTabId(usize, Option), NextSwapLayoutWithTabId(usize, Option), + ApplyTiledSwapLayoutWithTabId(usize, String, Option), + ApplyFloatingSwapLayoutWithTabId(usize, String, Option), MoveTabWithTabId(usize, Direction, Option), SetSoftKeyboard { client_id: ClientId, @@ -1165,6 +1173,10 @@ impl From<&ScreenInstruction> for ScreenContext { }, ScreenInstruction::PreviousSwapLayout(..) => ScreenContext::PreviousSwapLayout, ScreenInstruction::NextSwapLayout(..) => ScreenContext::NextSwapLayout, + ScreenInstruction::ApplyTiledSwapLayout(..) => ScreenContext::ApplyTiledSwapLayout, + ScreenInstruction::ApplyFloatingSwapLayout(..) => { + ScreenContext::ApplyFloatingSwapLayout + }, ScreenInstruction::OverrideLayout(..) => ScreenContext::OverrideLayout, ScreenInstruction::OverrideLayoutComplete(..) => ScreenContext::OverrideLayoutComplete, ScreenInstruction::QueryTabNames(..) => ScreenContext::QueryTabNames, @@ -1255,6 +1267,7 @@ impl From<&ScreenInstruction> for ScreenContext { }, ScreenInstruction::TogglePaneBorderless(..) => ScreenContext::TogglePaneBorderless, ScreenInstruction::SetPaneBorderless(..) => ScreenContext::SetPaneBorderless, + ScreenInstruction::SetPaneBorderStyle(..) => ScreenContext::SetPaneBorderStyle, ScreenInstruction::AddHighlightPaneFrameColorOverride(..) => { ScreenContext::AddHighlightPaneFrameColorOverride }, @@ -1361,6 +1374,12 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::NextSwapLayoutWithTabId(..) => { ScreenContext::NextSwapLayoutWithTabId }, + ScreenInstruction::ApplyTiledSwapLayoutWithTabId(..) => { + ScreenContext::ApplyTiledSwapLayoutWithTabId + }, + ScreenInstruction::ApplyFloatingSwapLayoutWithTabId(..) => { + ScreenContext::ApplyFloatingSwapLayoutWithTabId + }, ScreenInstruction::MoveTabWithTabId(..) => ScreenContext::MoveTabWithTabId, ScreenInstruction::SetSoftKeyboard { .. } => ScreenContext::SetSoftKeyboard, ScreenInstruction::FocusHostSession(..) => ScreenContext::FocusHostSession, @@ -1538,6 +1557,8 @@ pub(crate) struct Screen { terminal_emulator_colors: Rc>, terminal_emulator_color_codes: Rc>>, connected_clients: Rc>>, // bool -> is_web_client + client_display_slots: Rc>>, + highest_client_id_seen: ClientId, /// The indices of this [`Screen`]'s active [`Tab`]s. active_tab_ids: BTreeMap, client_sizes: HashMap, @@ -1597,6 +1618,8 @@ pub(crate) struct Screen { cached_layout_errors: Vec, pane_render_subscribers: HashMap, background_plugin_subscriptions: HashMap<(PluginId, ClientId), HashSet>, + last_reported_plugin_tab_indices: HashMap, + state_report_target: Option<(PluginId, ClientId)>, next_forward_token: u32, pending_forwarded_queries: HashMap, forward_queue: VecDeque, @@ -1750,6 +1773,8 @@ impl Screen { client_kitty_host_state: Rc::new(RefCell::new(HashMap::new())), style: client_attributes.style, connected_clients: Rc::new(RefCell::new(HashMap::new())), + client_display_slots: Rc::new(RefCell::new(HashMap::new())), + highest_client_id_seen: 0, active_tab_ids: BTreeMap::new(), client_sizes: HashMap::new(), global_last_active_tab_id: 0, @@ -1805,6 +1830,8 @@ impl Screen { cached_layout_errors: vec![], pane_render_subscribers: HashMap::new(), background_plugin_subscriptions: HashMap::new(), + last_reported_plugin_tab_indices: HashMap::new(), + state_report_target: None, next_forward_token: 1, // 0 is reserved as the startup sentinel pending_forwarded_queries: HashMap::new(), forward_queue: VecDeque::new(), @@ -4849,6 +4876,7 @@ impl Screen { self.pane_frame_style, self.auto_layout, self.connected_clients.clone(), + self.client_display_slots.clone(), self.session_is_mirrored, client_id, self.copy_options.clone(), @@ -5032,8 +5060,8 @@ impl Screen { }) .with_context(err_context)?; - if !self.active_tab_ids.contains_key(&client_id) { - // this means this is a new client and we need to add it to our state properly + let client_has_since_disconnected = client_id <= self.highest_client_id_seen; + if !self.active_tab_ids.contains_key(&client_id) && !client_has_since_disconnected { self.add_client(client_id, is_web_client) .with_context(err_context)?; } @@ -5061,8 +5089,16 @@ impl Screen { format!("failed to attach client {client_id} to tab with index {tab_index}") }; - // Set followed_client_id to the first regular client if not already set - if self.followed_client_id.is_none() && !self.watcher_clients.contains_key(&client_id) { + let followed_client_is_gone = self + .followed_client_id + .map(|followed_client_id| { + !self + .connected_clients + .borrow() + .contains_key(&followed_client_id) + }) + .unwrap_or(true); + if followed_client_is_gone && !self.watcher_clients.contains_key(&client_id) { self.followed_client_id = Some(client_id); } @@ -5097,11 +5133,13 @@ impl Screen { bail!("Can't find a valid tab to attach client to!"); }; + self.highest_client_id_seen = self.highest_client_id_seen.max(client_id); self.active_tab_ids.insert(client_id, tab_index); self.client_kitty_host_state.borrow_mut().remove(&client_id); self.connected_clients .borrow_mut() .insert(client_id, is_web_client); + self.assign_display_slot(client_id); if is_web_client { self.kitty_host_capabilities .borrow_mut() @@ -5136,6 +5174,7 @@ impl Screen { pub fn remove_client(&mut self, client_id: ClientId) -> Result<()> { let err_context = || format!("failed to remove client {client_id}"); + self.highest_client_id_seen = self.highest_client_id_seen.max(client_id); self.set_client_dimmed(client_id, false, None); let passthrough_panes: Vec = self @@ -5218,7 +5257,10 @@ impl Screen { self.push_sixel_host_support_to_tabs(); } self.client_sizes.remove(&client_id); + self.client_display_slots.borrow_mut().remove(&client_id); self.pane_render_subscribers.remove(&client_id); + self.background_plugin_subscriptions + .retain(|(_plugin_id, c_id), _subscriptions| c_id != &client_id); self.last_forwarded_osc7.remove(&client_id); self.client_host_focused.remove(&client_id); self.client_notification_protocols.remove(&client_id); @@ -5297,6 +5339,7 @@ impl Screen { is_fullscreen_active: tab.is_fullscreen_active(), is_sync_panes_active: tab.is_sync_panes_active(), are_floating_panes_visible: tab.are_floating_panes_visible(), + other_focused_client_slots: self.display_slots_of_clients(&all_focused_clients), other_focused_clients: all_focused_clients, active_swap_layout_name, is_swap_layout_dirty, @@ -5344,6 +5387,8 @@ impl Screen { is_fullscreen_active: tab.is_fullscreen_active(), is_sync_panes_active: tab.is_sync_panes_active(), are_floating_panes_visible: tab.are_floating_panes_visible(), + other_focused_client_slots: self + .display_slots_of_clients(&other_focused_clients), other_focused_clients, active_swap_layout_name, is_swap_layout_dirty, @@ -5402,6 +5447,20 @@ impl Screen { Ok(pane_manifest) } + fn report_pane_and_tab_state_to_plugin( + &mut self, + plugin_id: PluginId, + client_id: ClientId, + ) -> Result<()> { + self.state_report_target = Some((plugin_id, client_id)); + let pane_state = self.generate_and_report_pane_state(); + let tab_state = self.generate_and_report_tab_state(); + self.state_report_target = None; + pane_state?; + tab_state?; + Ok(()) + } + fn collect_pane_list(&self, show_all: bool) -> Result { fn should_include_pane(pane_info: &PaneInfo, show_all: bool) -> bool { pane_info.is_selectable || show_all @@ -5473,6 +5532,45 @@ impl Screen { false } } + fn assign_display_slot(&mut self, client_id: ClientId) { + let mut display_slots = self.client_display_slots.borrow_mut(); + if display_slots.contains_key(&client_id) { + return; + } + let taken: HashSet = display_slots.values().copied().collect(); + let mut slot = 1; + while taken.contains(&slot) { + slot += 1; + } + display_slots.insert(client_id, slot); + } + fn display_slots_of_clients(&self, client_ids: &[ClientId]) -> Vec { + let display_slots = self.client_display_slots.borrow(); + client_ids + .iter() + .map(|client_id| display_slots.get(client_id).copied().unwrap_or(0)) + .collect() + } + fn report_plugin_tab_indices(&mut self) { + let mut current: HashMap = HashMap::new(); + for tab in self.tabs.values() { + for plugin_id in tab.get_plugin_ids() { + current.insert(plugin_id, tab.position); + } + } + if current == self.last_reported_plugin_tab_indices { + return; + } + let tab_indices: Vec<(PluginId, usize)> = current + .iter() + .map(|(plugin_id, tab_index)| (*plugin_id, *tab_index)) + .collect(); + let _ = self + .bus + .senders + .send_to_plugin(PluginInstruction::UpdatePluginTabIndices(tab_indices)); + self.last_reported_plugin_tab_indices = current; + } fn log_and_report_session_state(&mut self) -> Result<()> { let err_context = || format!("Failed to log and report session state"); @@ -5480,6 +5578,7 @@ impl Screen { self.revert_fit_disabled_without_reference_client() .with_context(err_context)?; self.reconcile_all_single_pane_focus(); + self.report_plugin_tab_indices(); // generate own session info let pane_manifest = self.generate_and_report_pane_state()?; let tab_infos = self.generate_and_report_tab_state()?; @@ -5880,7 +5979,7 @@ impl Screen { Ok(()) } - fn client_id(&mut self, client_id: ClientId) -> Option { + fn client_id(&mut self, client_id: ClientId) -> Option { if self.get_active_tab(client_id).is_ok() { Some(client_id) } else { @@ -6171,6 +6270,13 @@ impl Screen { /// Returns plugin IDs from the client's active tab plus background plugins /// subscribed to the given event type. fn targeted_plugin_ids(&self, client_id: ClientId, event_type: EventType) -> Vec { + if let Some((target_plugin_id, target_client_id)) = self.state_report_target { + return if target_client_id == client_id { + vec![target_plugin_id] + } else { + vec![] + }; + } let mut plugin_ids = Vec::new(); // Active-tab plugins if let Some(active_tab_id) = self.active_tab_ids.get(&client_id) { @@ -6712,6 +6818,7 @@ impl Screen { height: Some(PercentOrFixed::Fixed(pane.rows())), pinned: Some(pane.current_geom().is_pinned), borderless: Some(pane.borderless()), + border_style: None, }; new_active_tab.add_floating_pane( pane, @@ -6880,6 +6987,8 @@ impl Screen { auto_layout: bool, rounded_corners: bool, hide_session_name: bool, + border_style: BorderStyle, + floating_border_style: BorderStyle, stacked_resize: bool, stacked_pane_list: bool, default_editor: Option, @@ -6906,6 +7015,10 @@ impl Screen { self.default_mode_info.update_theme(theme); self.default_mode_info .update_rounded_corners(rounded_corners); + self.default_mode_info + .update_border_styles(border_style, floating_border_style); + self.style.border_style = border_style; + self.style.floating_border_style = floating_border_style; // `default_mode_info` is the fallback used by `change_mode` for // clients that don't yet have a per-client `mode_info` entry, so its // keybinds and base mode must be kept in sync with reconfigures. @@ -6946,6 +7059,7 @@ impl Screen { for tab in self.tabs.values_mut() { tab.update_theme(theme); tab.update_rounded_corners(rounded_corners); + tab.update_border_styles(border_style, floating_border_style); tab.update_default_shell(default_shell.clone()); tab.update_default_editor(self.default_editor.clone()); tab.update_auto_layout(auto_layout); @@ -7294,6 +7408,18 @@ impl Screen { } } } + pub fn set_pane_border_style( + &mut self, + pane_id: PaneId, + border_style: BorderStyleOverride, + ) -> bool { + for (_tab_id, tab) in self.tabs.iter_mut() { + if tab.has_pane_with_pid(&pane_id) { + return tab.set_pane_border_style(pane_id, border_style); + } + } + false + } pub fn handle_mouse_event(&mut self, event: MouseEvent, client_id: ClientId) { let is_bare_motion = event.event_type == MouseEventType::Motion && !event.left @@ -7510,6 +7636,7 @@ impl Screen { focused_clients, default_fg, default_bg, + p.border_style_override(), ) }) .collect(); @@ -7539,7 +7666,7 @@ impl Screen { PaneLayoutMetadata::new( pane_id, p.position_and_size(), - false, // floating panes are never borderless + p.borderless(), p.invoked_with().clone(), p.custom_title(), !focused_clients.is_empty(), @@ -7551,6 +7678,7 @@ impl Screen { focused_clients, default_fg, default_bg, + p.border_style_override(), ) }) .collect(); @@ -7668,6 +7796,7 @@ impl Screen { is_fullscreen_active: tab.is_fullscreen_active(), is_sync_panes_active: tab.is_sync_panes_active(), are_floating_panes_visible: tab.are_floating_panes_visible(), + other_focused_client_slots: self.display_slots_of_clients(&all_focused_clients), other_focused_clients: all_focused_clients, active_swap_layout_name, is_swap_layout_dirty, @@ -8415,6 +8544,7 @@ pub(crate) fn screen_thread_main( NewPanePlacement::Tiled { direction: Some(direction), borderless, + border_style: None, } => { if direction == Direction::Left || direction == Direction::Right @@ -10429,6 +10559,58 @@ pub(crate) fn screen_thread_main( screen.render(None)?; screen.log_and_report_session_state()?; }, + ScreenInstruction::ApplyTiledSwapLayout(client_id, layout_name, mut completion_tx) => { + let mut applied = false; + active_tab_and_connected_client_id!( + screen, + client_id, + |tab: &mut Tab, _client_id: ClientId| { + applied = tab.apply_tiled_swap_layout(&layout_name)?; + Ok::<(), anyhow::Error>(()) + }, + ? + ); + if !applied { + log::error!("Tiled swap layout not found or incompatible: {layout_name}"); + if let Some(ref mut c) = completion_tx { + c.set_exit_status(1); + c.set_error_message(format!( + "Tiled swap layout not found or incompatible: {layout_name}" + )); + } + } + drop(completion_tx); + screen.render(None)?; + screen.log_and_report_session_state()?; + }, + ScreenInstruction::ApplyFloatingSwapLayout( + client_id, + layout_name, + mut completion_tx, + ) => { + let mut applied = false; + active_tab_and_connected_client_id!( + screen, + client_id, + |tab: &mut Tab, _client_id: ClientId| { + applied = tab.apply_floating_swap_layout(&layout_name)?; + Ok::<(), anyhow::Error>(()) + }, + ? + ); + if !applied { + log::error!("Floating swap layout not found or incompatible: {layout_name}"); + if let Some(ref mut c) = completion_tx { + c.set_exit_status(1); + c.set_error_message(format!( + "Floating swap layout not found or incompatible: {layout_name}" + )); + } + } + drop(completion_tx); + screen.render(None)?; + screen.log_and_report_session_state()?; + }, ScreenInstruction::OverrideLayout( cwd, default_shell, @@ -10828,6 +11010,7 @@ pub(crate) fn screen_thread_main( new_pane_placement = NewPanePlacement::Tiled { direction: None, borderless: None, + border_style: None, }; } if should_be_in_place { @@ -11603,6 +11786,8 @@ pub(crate) fn screen_thread_main( auto_layout, rounded_corners, hide_session_name, + border_style, + floating_border_style, stacked_resize, stacked_pane_list, default_editor, @@ -11636,6 +11821,8 @@ pub(crate) fn screen_thread_main( auto_layout, rounded_corners, hide_session_name, + border_style, + floating_border_style, stacked_resize, stacked_pane_list, default_editor, @@ -12062,6 +12249,16 @@ pub(crate) fn screen_thread_main( screen.set_pane_borderless(pane_id, borderless); let _ = screen.render(None); }, + ScreenInstruction::SetPaneBorderStyle(pane_id, border_style, mut completion_tx) => { + if !screen.set_pane_border_style(pane_id, border_style) { + log::error!("Pane with id {:?} not found", pane_id); + if let Some(c) = completion_tx.as_mut() { + c.set_exit_status(1); + c.set_error_message(format!("Pane with id {:?} not found", pane_id)); + } + } + let _ = screen.render(None); + }, ScreenInstruction::GroupAndUngroupPanes( pane_ids_to_group, pane_ids_to_ungroup, @@ -12322,7 +12519,10 @@ pub(crate) fn screen_thread_main( } else { screen .background_plugin_subscriptions - .insert((plugin_id, client_id), subscriptions); + .entry((plugin_id, client_id)) + .or_default() + .extend(subscriptions); + screen.report_pane_and_tab_state_to_plugin(plugin_id, client_id)?; } }, ScreenInstruction::ClearHintTextCache => { @@ -12806,6 +13006,60 @@ pub(crate) fn screen_thread_main( screen.render(None)?; screen.log_and_report_session_state()?; }, + ScreenInstruction::ApplyTiledSwapLayoutWithTabId( + tab_id, + layout_name, + mut _completion_tx, + ) => { + if let Some(tab) = screen.tabs.get_mut(&tab_id) { + let applied = tab.apply_tiled_swap_layout(&layout_name)?; + if !applied { + log::error!("Tiled swap layout not found or incompatible: {layout_name}"); + if let Some(ref mut c) = _completion_tx { + c.set_exit_status(1); + c.set_error_message(format!( + "Tiled swap layout not found or incompatible: {layout_name}" + )); + } + } + } else { + log::error!("Tab with id {} not found", tab_id); + if let Some(ref mut c) = _completion_tx { + c.set_exit_status(1); + c.set_error_message(format!("Tab with id {} not found", tab_id)); + } + } + screen.render(None)?; + screen.log_and_report_session_state()?; + }, + ScreenInstruction::ApplyFloatingSwapLayoutWithTabId( + tab_id, + layout_name, + mut _completion_tx, + ) => { + if let Some(tab) = screen.tabs.get_mut(&tab_id) { + let applied = tab.apply_floating_swap_layout(&layout_name)?; + if !applied { + log::error!( + "Floating swap layout not found or incompatible: {layout_name}" + ); + if let Some(ref mut c) = _completion_tx { + c.set_exit_status(1); + c.set_error_message(format!( + "Floating swap layout not found or incompatible: {layout_name}" + )); + } + } + } else { + log::error!("Tab with id {} not found", tab_id); + if let Some(ref mut c) = _completion_tx { + c.set_exit_status(1); + c.set_error_message(format!("Tab with id {} not found", tab_id)); + } + } + screen.render(None)?; + screen.log_and_report_session_state()?; + }, ScreenInstruction::MoveTabWithTabId(tab_id, direction, _completion_tx) => { if pending_tab_ids.is_empty() { screen.move_tab_by_id(tab_id, direction)?; diff --git a/zellij-server/src/session_layout_metadata.rs b/zellij-server/src/session_layout_metadata.rs index 62efea986..f92a0f44d 100644 --- a/zellij-server/src/session_layout_metadata.rs +++ b/zellij-server/src/session_layout_metadata.rs @@ -6,7 +6,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use zellij_utils::common_path::common_path_all; use zellij_utils::pane_size::PaneGeom; use zellij_utils::{ - data::{LayoutMetadata, PaneMetadata, TabMetadata}, + data::{BorderStyleOverride, LayoutMetadata, PaneMetadata, TabMetadata}, input::command::RunCommand, input::layout::{Layout, Run, RunPlugin, RunPluginOrAlias}, input::plugins::PluginAliases, @@ -535,6 +535,7 @@ impl Into for PaneLayoutMetadata { pane_contents: self.pane_contents, default_fg: self.default_fg, default_bg: self.default_bg, + border_style: self.border_style.none_if_empty(), } } } @@ -561,6 +562,7 @@ pub struct PaneLayoutMetadata { focused_clients: Vec, default_fg: Option, default_bg: Option, + border_style: BorderStyleOverride, } impl PaneLayoutMetadata { @@ -575,6 +577,7 @@ impl PaneLayoutMetadata { focused_clients: Vec, default_fg: Option, default_bg: Option, + border_style: BorderStyleOverride, ) -> Self { PaneLayoutMetadata { id, @@ -588,6 +591,7 @@ impl PaneLayoutMetadata { focused_clients, default_fg, default_bg, + border_style, } } fn to_pane_metadata(&self) -> PaneMetadata { @@ -664,13 +668,15 @@ impl ClientMetadata { default_editor: &Option, ) -> String { let mut lines = vec![]; - lines.push(String::from("CLIENT_ID ZELLIJ_PANE_ID RUNNING_COMMAND")); + lines.push(format!( + "{0: <10} {1: <14} {2}", + "CLIENT_ID", "ZELLIJ_PANE_ID", "RUNNING_COMMAND" + )); for (client_id, client_metadata) in clients_metadata.iter() { - // 9 - CLIENT_ID, 14 - ZELLIJ_PANE_ID, 15 - RUNNING_COMMAND lines.push(format!( "{} {} {}", - format!("{0: <9}", client_id), + format!("{0: <10}", client_id), format!("{0: <14}", client_metadata.stringify_pane_id()), format!( "{0: <15}", @@ -701,6 +707,7 @@ mod tests { vec![], None, None, + Default::default(), ) } @@ -720,6 +727,7 @@ mod tests { vec![], None, None, + Default::default(), ) } diff --git a/zellij-server/src/tab/layout_applier.rs b/zellij-server/src/tab/layout_applier.rs index 44e100a43..136bd3eaf 100644 --- a/zellij-server/src/tab/layout_applier.rs +++ b/zellij-server/src/tab/layout_applier.rs @@ -552,6 +552,7 @@ impl<'a> LayoutApplier<'a> { } new_plugin.set_borderless(layout.borderless.unwrap_or(false)); + new_plugin.set_border_style_override(layout.border_style.unwrap_or_default()); if let Some(exclude_from_sync) = layout.exclude_from_sync { new_plugin.set_exclude_from_sync(exclude_from_sync); } @@ -605,6 +606,7 @@ impl<'a> LayoutApplier<'a> { } else { new_pane.set_borderless(false); } + new_pane.set_border_style_override(floating_pane_layout.border_style.unwrap_or_default()); resize_pty!( new_pane, self.os_api, @@ -662,6 +664,7 @@ impl<'a> LayoutApplier<'a> { } else { new_pane.set_borderless(false); } + new_pane.set_border_style_override(floating_pane_layout.border_style.unwrap_or_default()); if floating_pane_layout.default_fg.is_some() || floating_pane_layout.default_bg.is_some() { new_pane.set_pane_default_colors( floating_pane_layout.default_fg.clone(), @@ -735,6 +738,7 @@ impl<'a> LayoutApplier<'a> { new_pane.handle_pty_bytes("\n\r".as_bytes().into()); } new_pane.set_borderless(layout.borderless.unwrap_or(false)); + new_pane.set_border_style_override(layout.border_style.unwrap_or_default()); if let Some(exclude_from_sync) = layout.exclude_from_sync { new_pane.set_exclude_from_sync(exclude_from_sync); } @@ -764,6 +768,7 @@ impl<'a> LayoutApplier<'a> { layout.run, position_and_size, layout.borderless, + layout.border_style, ); found_exact_match = true; } @@ -784,6 +789,7 @@ impl<'a> LayoutApplier<'a> { run_instruction.clone(), position_and_size, layout.borderless, + layout.border_style, ); found_empty_location = true; } @@ -1331,6 +1337,9 @@ impl<'a> PaneApplier<'a> { if let Some(should_be_borderless) = floating_panes_layout.borderless { pane.set_borderless(should_be_borderless); } + if let Some(border_style) = floating_panes_layout.border_style { + pane.set_border_style_override(border_style); + } self.apply_position_and_size_to_floating_pane(pane, position_and_size); Ok(()) } @@ -1410,6 +1419,9 @@ impl<'a> PaneApplier<'a> { if let Some(should_be_borderless) = layout.borderless { pane.set_borderless(should_be_borderless); } + if let Some(border_style) = layout.border_style { + pane.set_border_style_override(border_style); + } if let Some(pane_title) = layout.name.as_ref() { pane.set_title(pane_title.into()); } diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index 5dc2decae..a1c0d4c0e 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -17,8 +17,9 @@ use unicode_width::UnicodeWidthStr; use uuid::Uuid; use zellij_utils::data::PaneContents; use zellij_utils::data::{ - Direction, KeyWithModifier, NewPanePlacement, PaneInfo, PermissionStatus, PermissionType, - PluginPermission, RegexHighlight, ResizeStrategy, Style, StyledText, WebSharing, + BorderStyle, BorderStyleOverride, Direction, KeyWithModifier, NewPanePlacement, PaneInfo, + PermissionStatus, PermissionType, PluginPermission, RegexHighlight, ResizeStrategy, Style, + StyledText, WebSharing, }; use zellij_utils::errors::prelude::*; use zellij_utils::input::command::RunCommand; @@ -267,6 +268,7 @@ pub(crate) struct Tab { word_separators: String, currently_marking_pane_group: Rc>>, connected_clients_in_app: Rc>>, // bool -> is_web_client + client_display_slots: Rc>>, // the below are the configured values - the ones that will be set if and when the web server // is brought online web_server_ip: IpAddr, @@ -627,6 +629,10 @@ pub trait Pane { fn load_pane_name(&mut self); fn set_borderless(&mut self, borderless: bool); fn borderless(&self) -> bool; + fn set_border_style_override(&mut self, _border_style: BorderStyleOverride) {} + fn border_style_override(&self) -> BorderStyleOverride { + BorderStyleOverride::default() + } fn set_exclude_from_sync(&mut self, exclude_from_sync: bool); fn exclude_from_sync(&self) -> bool; @@ -754,6 +760,7 @@ pub trait Pane { fn update_kitty_host_support(&mut self, _supported: KittyHostSupport) {} fn update_sixel_host_support(&mut self, _supported: bool) {} fn update_rounded_corners(&mut self, _rounded_corners: bool) {} + fn invalidate_frame_cache(&mut self) {} fn set_should_be_suppressed(&mut self, _should_be_suppressed: bool) {} fn query_should_be_suppressed(&self) -> bool { false @@ -865,6 +872,7 @@ impl Tab { pane_frame_style: PaneFrameStyle, auto_layout: bool, connected_clients_in_app: Rc>>, // bool -> is_web_client + client_display_slots: Rc>>, session_is_mirrored: bool, client_id: Option, copy_options: CopyOptions, @@ -921,6 +929,7 @@ impl Tab { viewport.clone(), connected_clients.clone(), connected_clients_in_app.clone(), + client_display_slots.clone(), mode_info.clone(), character_cell_size.clone(), stacked_resize.clone(), @@ -938,6 +947,7 @@ impl Tab { viewport.clone(), connected_clients.clone(), connected_clients_in_app.clone(), + client_display_slots.clone(), mode_info.clone(), character_cell_size.clone(), fullscreen_covers_ui.clone(), @@ -1031,6 +1041,7 @@ impl Tab { osc133_command_selection: true, word_separators: DEFAULT_WORD_SEPARATORS.to_owned(), connected_clients_in_app, + client_display_slots, web_server_ip, web_server_port, panes_with_pending_bell: HashSet::new(), @@ -1684,6 +1695,7 @@ impl Tab { self.mouse_scroll_resize, self.mouse_hover_tips, self.dimmed_clients.clone(), + &self.client_display_slots.borrow(), ); pane_contents_and_ui.set_frame_geom_override(Some(header_geom)); pane_contents_and_ui.set_stack_list_entry( @@ -1907,10 +1919,16 @@ impl Tab { } } fn relayout_floating_panes(&mut self, search_backwards: bool) -> Result<()> { - if let Some(layout_candidate) = self + let layout_candidate = self .swap_layouts - .swap_floating_panes(&self.floating_panes, search_backwards) - { + .swap_floating_panes(&self.floating_panes, search_backwards); + self.apply_floating_layout_candidate(layout_candidate) + } + fn apply_floating_layout_candidate( + &mut self, + layout_candidate: Option>, + ) -> Result<()> { + if let Some(layout_candidate) = layout_candidate { LayoutApplier::new( &self.viewport, &self.senders, @@ -1949,10 +1967,16 @@ impl Tab { self.tiled_panes.unset_fullscreen(); } self.dissolve_stack_lists_for_classic_mutation(); - if let Some(layout_candidate) = self + let layout_candidate = self .swap_layouts - .swap_tiled_panes(&self.tiled_panes, search_backwards) - { + .swap_tiled_panes(&self.tiled_panes, search_backwards); + self.apply_tiled_layout_candidate(layout_candidate) + } + fn apply_tiled_layout_candidate( + &mut self, + layout_candidate: Option, + ) -> Result<()> { + if let Some(layout_candidate) = layout_candidate { let application_res = LayoutApplier::new( &self.viewport, &self.senders, @@ -1995,6 +2019,46 @@ impl Tab { .with_context(|| format!("failed to apply cached resizes"))?; Ok(()) } + fn settled_tiled_pane_count(&self) -> usize { + let mut pane_count = self.tiled_panes.visible_panes_count(); + if self.tiled_panes.fullscreen_is_active() { + pane_count += self.tiled_panes.panes_to_hide_count(); + } + if self.stacked_pane_list_is_active() { + pane_count += self.suppressed_stack_list_members().count(); + } + pane_count + } + pub fn apply_tiled_swap_layout(&mut self, layout_name: &str) -> Result { + let Some((position, layout_candidate)) = self + .swap_layouts + .tiled_layout_candidate_by_name(layout_name, self.settled_tiled_pane_count()) + else { + return Ok(false); + }; + if self.tiled_panes.fullscreen_is_active() { + self.tiled_panes.unset_fullscreen(); + } + self.dissolve_stack_lists_for_classic_mutation(); + self.swap_layouts + .set_current_tiled_layout_position(position); + self.apply_tiled_layout_candidate(Some(layout_candidate))?; + Ok(true) + } + pub fn apply_floating_swap_layout(&mut self, layout_name: &str) -> Result { + let Some((position, layout_candidate)) = + self.swap_layouts.floating_layout_candidate_by_name( + layout_name, + self.floating_panes.visible_panes_count(), + ) + else { + return Ok(false); + }; + self.swap_layouts + .set_current_floating_layout_position(position); + self.apply_floating_layout_candidate(Some(layout_candidate))?; + Ok(true) + } pub fn previous_swap_layout(&mut self) -> Result<()> { let search_backwards = true; if self.floating_panes.panes_are_visible() { @@ -2283,7 +2347,15 @@ impl Tab { Ok(()) } pub fn remove_client(&mut self, client_id: ClientId) { - self.focus_pane_id = None; + let is_connected_to_this_tab = self.connected_clients.borrow().contains(&client_id); + if is_connected_to_this_tab { + let is_last_connected_client = self.connected_clients.borrow().len() == 1; + self.focus_pane_id = if is_last_connected_client { + self.tiled_panes.focused_pane_id(client_id) + } else { + None + }; + } self.mode_info .borrow_mut() .get_mut(&client_id) @@ -2483,8 +2555,9 @@ impl Tab { blocking_notification: Option, ) -> Result<()> { let invoked_with = self.normalize_invoked_with_for_default_shell(invoked_with); - match new_pane_placement { - NewPanePlacement::NoPreference { borderless } => self.new_no_preference_pane( + let border_style = new_pane_placement.get_border_style(); + let result = match new_pane_placement { + NewPanePlacement::NoPreference { borderless, .. } => self.new_no_preference_pane( pid, initial_pane_title, invoked_with, @@ -2497,6 +2570,7 @@ impl Tab { NewPanePlacement::Tiled { direction: None, borderless, + .. } => self.new_tiled_pane( pid, initial_pane_title, @@ -2510,6 +2584,7 @@ impl Tab { NewPanePlacement::Tiled { direction: Some(direction), borderless, + .. } => { let is_vertical = direction == Direction::Left || direction == Direction::Right; let focused_client_id = client_id.filter(|_| should_focus_pane); @@ -2588,6 +2663,7 @@ impl Tab { pane_id_to_replace, close_replaced_pane, borderless, + .. } => self.new_in_place_pane( pid, initial_pane_title, @@ -2601,6 +2677,7 @@ impl Tab { NewPanePlacement::Stacked { pane_id_to_stack_under, borderless, + .. } => self.new_stacked_pane( pid, initial_pane_title, @@ -2612,7 +2689,11 @@ impl Tab { blocking_notification, borderless, ), + }; + if let Some(border_style) = border_style { + self.set_pane_border_style(pid, border_style); } + result } pub fn new_no_preference_pane( &mut self, @@ -7558,6 +7639,21 @@ impl Tab { pane.update_rounded_corners(rounded_corners); } } + pub fn update_border_styles( + &mut self, + border_style: BorderStyle, + floating_border_style: BorderStyle, + ) { + self.style.border_style = border_style; + self.style.floating_border_style = floating_border_style; + self.floating_panes + .update_border_styles(border_style, floating_border_style); + self.tiled_panes + .update_border_styles(border_style, floating_border_style); + for (_, pane) in self.suppressed_panes.values_mut() { + pane.invalidate_frame_cache(); + } + } pub fn update_arrow_fonts(&mut self, should_support_arrow_fonts: bool) { self.arrow_fonts = should_support_arrow_fonts; self.floating_panes @@ -7833,6 +7929,27 @@ impl Tab { log::error!("Pane with id {:?} not found", pane_id); Ok(()) } + pub fn set_pane_border_style( + &mut self, + pane_id: PaneId, + border_style: BorderStyleOverride, + ) -> bool { + if let Some(pane) = self.floating_panes.get_pane_mut(pane_id) { + pane.set_border_style_override(border_style); + self.set_force_render(); + return true; + } + if let Some(pane) = self.tiled_panes.get_pane_mut(pane_id) { + pane.set_border_style_override(border_style); + self.set_force_render(); + return true; + } + if let Some(pane) = self.suppressed_panes.get_mut(&pane_id) { + pane.1.set_border_style_override(border_style); + return true; + } + false + } pub fn get_viewport(&self) -> Viewport { self.viewport.borrow().clone() } diff --git a/zellij-server/src/tab/swap_layouts.rs b/zellij-server/src/tab/swap_layouts.rs index af4e55903..0eb04769a 100644 --- a/zellij-server/src/tab/swap_layouts.rs +++ b/zellij-server/src/tab/swap_layouts.rs @@ -127,6 +127,7 @@ impl SwapLayouts { if self.swap_floating_layouts.is_empty() { return None; } + let pane_count = floating_panes.visible_panes_count(); let initial_position = self.current_floating_layout_position; macro_rules! progress_layout { @@ -161,10 +162,8 @@ impl SwapLayouts { .nth(self.current_floating_layout_position) { Some(swap_layout) => { - for (constraint, layout) in swap_layout.0.iter() { - if self.state_fits_floating_panes_constraint(constraint, floating_panes) { - return Some(layout.clone()); - }; + if let Some(layout) = self.floating_layout_candidate(swap_layout, pane_count) { + return Some(layout); } progress_layout!(); }, @@ -178,41 +177,87 @@ impl SwapLayouts { } None } - fn state_fits_tiled_panes_constraint( - &self, - constraint: &LayoutConstraint, - tiled_panes: &TiledPanes, - ) -> bool { + fn state_fits_pane_count_constraint(constraint: &LayoutConstraint, pane_count: usize) -> bool { match constraint { - LayoutConstraint::MaxPanes(max_panes) => { - tiled_panes.visible_panes_count() <= *max_panes - }, - LayoutConstraint::MinPanes(min_panes) => { - tiled_panes.visible_panes_count() >= *min_panes - }, - LayoutConstraint::ExactPanes(pane_count) => { - tiled_panes.visible_panes_count() == *pane_count - }, + LayoutConstraint::MaxPanes(max_panes) => pane_count <= *max_panes, + LayoutConstraint::MinPanes(min_panes) => pane_count >= *min_panes, + LayoutConstraint::ExactPanes(exact_panes) => pane_count == *exact_panes, LayoutConstraint::NoConstraint => true, } } - fn state_fits_floating_panes_constraint( + fn tiled_layout_candidate( &self, - constraint: &LayoutConstraint, - floating_panes: &FloatingPanes, - ) -> bool { - match constraint { - LayoutConstraint::MaxPanes(max_panes) => { - floating_panes.visible_panes_count() <= *max_panes - }, - LayoutConstraint::MinPanes(min_panes) => { - floating_panes.visible_panes_count() >= *min_panes - }, - LayoutConstraint::ExactPanes(pane_count) => { - floating_panes.visible_panes_count() == *pane_count - }, - LayoutConstraint::NoConstraint => true, + swap_layout: &SwapTiledLayout, + pane_count: usize, + ) -> Option { + for (constraint, layout) in swap_layout.0.iter() { + if Self::state_fits_pane_count_constraint(constraint, pane_count) { + let focus_layout_if_not_focused = true; + let display_area = self.display_area.borrow(); + // TODO: reuse the assets from position_panes_in_space here? + let display_area = PaneGeom::from(&*display_area); + if layout + .position_panes_in_space( + &display_area, + Some(pane_count), + false, + focus_layout_if_not_focused, + ) + .is_ok() + { + return Some(layout.clone()); + } + }; } + None + } + fn floating_layout_candidate( + &self, + swap_layout: &SwapFloatingLayout, + pane_count: usize, + ) -> Option> { + for (constraint, layout) in swap_layout.0.iter() { + if Self::state_fits_pane_count_constraint(constraint, pane_count) { + return Some(layout.clone()); + }; + } + None + } + pub fn tiled_layout_candidate_by_name( + &self, + layout_name: &str, + pane_count: usize, + ) -> Option<(usize, TiledPaneLayout)> { + self.swap_tiled_layouts + .iter() + .enumerate() + .filter(|(_, layout)| layout.1.as_deref() == Some(layout_name)) + .find_map(|(position, layout)| { + self.tiled_layout_candidate(layout, pane_count) + .map(|layout| (position, layout)) + }) + } + pub fn floating_layout_candidate_by_name( + &self, + layout_name: &str, + pane_count: usize, + ) -> Option<(usize, Vec)> { + self.swap_floating_layouts + .iter() + .enumerate() + .filter(|(_, layout)| layout.1.as_deref() == Some(layout_name)) + .find_map(|(position, layout)| { + self.floating_layout_candidate(layout, pane_count) + .map(|layout| (position, layout)) + }) + } + pub fn set_current_tiled_layout_position(&mut self, position: usize) { + self.current_tiled_layout_position = position; + self.is_tiled_damaged = false; + } + pub fn set_current_floating_layout_position(&mut self, position: usize) { + self.current_floating_layout_position = position; + self.is_floating_damaged = false; } pub fn swap_tiled_panes( &mut self, @@ -222,6 +267,7 @@ impl SwapLayouts { if self.swap_tiled_layouts.is_empty() { return None; } + let pane_count = tiled_panes.visible_panes_count(); macro_rules! progress_layout { () => {{ @@ -256,25 +302,8 @@ impl SwapLayouts { .nth(self.current_tiled_layout_position) { Some(swap_layout) => { - for (constraint, layout) in swap_layout.0.iter() { - if self.state_fits_tiled_panes_constraint(constraint, tiled_panes) { - let focus_layout_if_not_focused = true; - let display_area = self.display_area.borrow(); - // TODO: reuse the assets from position_panes_in_space here? - let pane_count = tiled_panes.visible_panes_count(); - let display_area = PaneGeom::from(&*display_area); - if layout - .position_panes_in_space( - &display_area, - Some(pane_count), - false, - focus_layout_if_not_focused, - ) - .is_ok() - { - return Some(layout.clone()); - } - }; + if let Some(layout) = self.tiled_layout_candidate(swap_layout, pane_count) { + return Some(layout); } progress_layout!(); }, diff --git a/zellij-server/src/tab/unit/layout_applier_tests.rs b/zellij-server/src/tab/unit/layout_applier_tests.rs index fcf13a60b..fa2de18f4 100644 --- a/zellij-server/src/tab/unit/layout_applier_tests.rs +++ b/zellij-server/src/tab/unit/layout_applier_tests.rs @@ -75,19 +75,18 @@ impl ServerOsApi for FakeInputOutput { unimplemented!() } - fn new_client( + fn register_client( &mut self, _client_id: ClientId, - _stream: LocalSocketStream, - ) -> Result> { + _receiver: &IpcReceiverWithContext, + ) -> Result<()> { unimplemented!() } - fn new_client_with_reply( + fn register_client_with_reply( &mut self, _client_id: ClientId, - _stream: LocalSocketStream, _reply_stream: LocalSocketStream, - ) -> Result> { + ) -> Result<()> { unimplemented!() } @@ -199,6 +198,7 @@ fn create_layout_applier_fixtures( viewport.clone(), connected_clients_set.clone(), connected_clients.clone(), + Rc::new(RefCell::new(HashMap::new())), mode_info.clone(), character_cell_size.clone(), stacked_resize, @@ -218,6 +218,7 @@ fn create_layout_applier_fixtures( viewport.clone(), connected_clients_set, connected_clients.clone(), + Rc::new(RefCell::new(HashMap::new())), mode_info, character_cell_size.clone(), fullscreen_covers_ui, @@ -335,6 +336,7 @@ fn create_layout_applier_fixtures_with_receivers( viewport.clone(), connected_clients_set.clone(), connected_clients.clone(), + Rc::new(RefCell::new(HashMap::new())), mode_info.clone(), character_cell_size.clone(), stacked_resize, @@ -354,6 +356,7 @@ fn create_layout_applier_fixtures_with_receivers( viewport.clone(), connected_clients_set, connected_clients.clone(), + Rc::new(RefCell::new(HashMap::new())), mode_info, character_cell_size.clone(), fullscreen_covers_ui, diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__swap_tiled_layout_with_only_stacked_children.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__swap_tiled_layout_with_only_stacked_children.snap index 704f019c4..92d2b515a 100644 --- a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__swap_tiled_layout_with_only_stacked_children.snap +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__swap_tiled_layout_with_only_stacked_children.snap @@ -1,12 +1,12 @@ --- source: zellij-server/src/tab/./unit/tab_integration_tests.rs -assertion_line: 3253 +assertion_line: 5784 expression: snapshot --- 00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ 01 (C): ┌ Pane #2 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -02 (C): ┌ Pane #3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -03 (C): ┌ Pane #4 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +02 (C): ┌ Pane #4 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +03 (C): ┌ Pane #5 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ 04 (C): │ │ 05 (C): │ │ 06 (C): │ │ @@ -23,4 +23,3 @@ expression: snapshot 17 (C): │ │ 18 (C): │ │ 19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs index 303c5afea..6518493ab 100644 --- a/zellij-server/src/tab/unit/tab_integration_tests.rs +++ b/zellij-server/src/tab/unit/tab_integration_tests.rs @@ -14,10 +14,10 @@ use std::path::PathBuf; use std::sync::Mutex; use zellij_utils::channels::Receiver; -use zellij_utils::data::Direction; use zellij_utils::data::Resize; use zellij_utils::data::ResizeStrategy; use zellij_utils::data::WebSharing; +use zellij_utils::data::{BorderStyle, BorderStyleOverride, Direction, LineStyle}; use zellij_utils::envs::set_session_name; use zellij_utils::errors::{prelude::*, ErrorContext}; use zellij_utils::input::layout::{ @@ -94,19 +94,18 @@ impl ServerOsApi for FakeInputOutput { fn send_to_client(&self, _client_id: ClientId, _msg: ServerToClientMsg) -> Result<()> { unimplemented!() } - fn new_client( + fn register_client( &mut self, _client_id: ClientId, - _stream: LocalSocketStream, - ) -> Result> { + _receiver: &IpcReceiverWithContext, + ) -> Result<()> { unimplemented!() } - fn new_client_with_reply( + fn register_client_with_reply( &mut self, _client_id: ClientId, - _stream: LocalSocketStream, _reply_stream: LocalSocketStream, - ) -> Result> { + ) -> Result<()> { unimplemented!() } fn remove_client(&mut self, _client_id: ClientId) -> Result<()> { @@ -259,6 +258,7 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab { draw_pane_frames, auto_layout, connected_clients, + Rc::new(RefCell::new(HashMap::new())), session_is_mirrored, Some(client_id), copy_options, @@ -357,6 +357,7 @@ fn create_new_tab_with_stacked_pane_list( draw_pane_frames, auto_layout, connected_clients, + Rc::new(RefCell::new(HashMap::new())), session_is_mirrored, Some(client_id), copy_options, @@ -451,6 +452,7 @@ fn create_new_tab_without_pane_frames(size: Size, default_mode: ModeInfo) -> Tab draw_pane_frames, auto_layout, connected_clients, + Rc::new(RefCell::new(HashMap::new())), session_is_mirrored, Some(client_id), copy_options, @@ -562,6 +564,7 @@ fn create_new_tab_with_swap_layouts( }, auto_layout, connected_clients, + Rc::new(RefCell::new(HashMap::new())), session_is_mirrored, Some(client_id), copy_options, @@ -670,6 +673,7 @@ fn create_new_tab_with_os_api( draw_pane_frames, auto_layout, connected_clients, + Rc::new(RefCell::new(HashMap::new())), session_is_mirrored, Some(client_id), copy_options, @@ -764,6 +768,7 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str) draw_pane_frames, auto_layout, connected_clients, + Rc::new(RefCell::new(HashMap::new())), session_is_mirrored, Some(client_id), copy_options, @@ -872,6 +877,7 @@ fn create_new_tab_with_mock_pty_writer( draw_pane_frames, auto_layout, connected_clients, + Rc::new(RefCell::new(HashMap::new())), session_is_mirrored, Some(client_id), copy_options, @@ -971,6 +977,7 @@ fn create_new_tab_with_sixel_support( draw_pane_frames, auto_layout, connected_clients, + Rc::new(RefCell::new(HashMap::new())), session_is_mirrored, Some(client_id), copy_options, @@ -1558,6 +1565,7 @@ fn new_stacked_pane() { NewPanePlacement::Stacked { pane_id_to_stack_under: None, borderless: None, + border_style: None, }, Some(client_id), None, @@ -1597,6 +1605,7 @@ fn kitty_visible_panes_exclude_collapsed_stack_members() { NewPanePlacement::Stacked { pane_id_to_stack_under: None, borderless: None, + border_style: None, }, Some(client_id), None, @@ -5157,7 +5166,7 @@ fn pane_bracketed_paste_ignored_when_not_in_bracketed_paste_mode() { cols: 121, rows: 20, }; - let client_id: u16 = 1; + let client_id: ClientId = 1; let mut pty_instruction_bus = MockPtyInstructionBus::new(); let mut tab = create_new_tab_with_mock_pty_writer( @@ -5187,7 +5196,7 @@ fn pane_faux_scrolling_in_alternate_mode() { cols: 121, rows: 20, }; - let client_id: u16 = 1; + let client_id: ClientId = 1; let lines_to_scroll = 3; let mut pty_instruction_bus = MockPtyInstructionBus::new(); @@ -5525,6 +5534,180 @@ fn can_swap_tiled_layout_at_runtime() { assert_snapshot!(snapshot); } +#[test] +fn can_apply_named_tiled_layout_while_floating_pane_is_visible() { + let size = Size { + cols: 121, + rows: 20, + }; + let client_id = 1; + let swap_layouts = r#" + layout { + swap_tiled_layout name="vertical" { + tab max_panes=2 split_direction="vertical" { + pane + pane + } + } + swap_tiled_layout name="horizontal" { + tab max_panes=2 { + pane + pane + } + } + swap_tiled_layout name="one-pane" { + tab max_panes=1 { + pane + } + } + } + "#; + let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap(); + let mut tab = create_new_tab_with_swap_layouts( + size, + ModeInfo::default(), + ( + layout.swap_tiled_layouts.clone(), + layout.swap_floating_layouts.clone(), + ), + None, + true, + true, + ); + + tab.new_pane( + PaneId::Terminal(2), + None, + None, + false, + true, + NewPanePlacement::default(), + Some(client_id), + None, + ) + .unwrap(); + tab.toggle_floating_panes(Some(client_id), None, None) + .unwrap(); + tab.new_pane( + PaneId::Terminal(3), + None, + None, + false, + true, + NewPanePlacement::default(), + Some(client_id), + None, + ) + .unwrap(); + let focused_floating_pane = tab.get_active_pane_id(client_id); + let initial_layout = tab.swap_layout_info(); + + for unavailable_layout in ["missing", "one-pane"] { + assert!(!tab.apply_tiled_swap_layout(unavailable_layout).unwrap()); + assert_eq!(tab.swap_layout_info(), initial_layout); + assert!(tab.are_floating_panes_visible()); + assert_eq!(tab.get_active_pane_id(client_id), focused_floating_pane); + } + + assert!(tab.apply_tiled_swap_layout("horizontal").unwrap()); + assert!(tab.are_floating_panes_visible()); + assert_eq!(tab.get_active_pane_id(client_id), focused_floating_pane); + + tab.toggle_floating_panes(Some(client_id), None, None) + .unwrap(); + assert_eq!( + tab.swap_layout_info(), + (Some("horizontal".to_owned()), false) + ); +} + +#[test] +fn can_apply_named_floating_layout_while_floating_panes_are_hidden() { + let size = Size { + cols: 121, + rows: 20, + }; + let client_id = 1; + let swap_layouts = r#" + layout { + swap_floating_layout name="staggered" { + floating_panes max_panes=2 { + pane + pane + } + } + swap_floating_layout name="centered" { + floating_panes max_panes=2 { + pane x="10%" y="10%" + pane x="20%" y="20%" + } + } + swap_floating_layout name="one-pane" { + floating_panes max_panes=1 { + pane + } + } + } + "#; + let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap(); + let mut tab = create_new_tab_with_swap_layouts( + size, + ModeInfo::default(), + ( + layout.swap_tiled_layouts.clone(), + layout.swap_floating_layouts.clone(), + ), + None, + true, + true, + ); + + tab.toggle_floating_panes(Some(client_id), None, None) + .unwrap(); + tab.new_pane( + PaneId::Terminal(2), + None, + None, + false, + true, + NewPanePlacement::default(), + Some(client_id), + None, + ) + .unwrap(); + tab.new_pane( + PaneId::Terminal(3), + None, + None, + false, + true, + NewPanePlacement::default(), + Some(client_id), + None, + ) + .unwrap(); + tab.toggle_floating_panes(Some(client_id), None, None) + .unwrap(); + let focused_tiled_pane = tab.get_active_pane_id(client_id); + let initial_layout = tab.swap_layout_info(); + + for unavailable_layout in ["missing", "one-pane"] { + assert!(!tab.apply_floating_swap_layout(unavailable_layout).unwrap()); + assert_eq!(tab.swap_layout_info(), initial_layout); + assert!(!tab.are_floating_panes_visible()); + assert_eq!(tab.get_active_pane_id(client_id), focused_tiled_pane); + } + + assert!(tab.apply_floating_swap_layout("centered").unwrap()); + assert!(!tab.are_floating_panes_visible()); + assert_eq!(tab.swap_layout_info(), initial_layout); + assert_eq!(tab.get_active_pane_id(client_id), focused_tiled_pane); + + tab.toggle_floating_panes(Some(client_id), None, None) + .unwrap(); + assert_eq!(tab.swap_layout_info(), (Some("centered".to_owned()), false)); +} + #[test] fn can_swap_floating_layout_at_runtime() { let size = Size { @@ -5776,43 +5959,49 @@ fn swap_tiled_layout_with_only_stacked_children() { true, stacked_resize, ); - let new_pane_id_1 = PaneId::Terminal(2); - let new_pane_id_2 = PaneId::Terminal(3); - let new_pane_id_3 = PaneId::Terminal(4); + let new_pane_ids = [2, 3, 4, 5].map(PaneId::Terminal); + let open_pane = |tab: &mut Tab, pane_id| { + tab.new_pane( + pane_id, + None, + None, + false, + true, + NewPanePlacement::default(), + Some(client_id), + None, + ) + .unwrap(); + }; + let stack_order = |tab: &Tab| { + let mut panes = tab + .tiled_panes + .get_panes() + .filter_map(|(pane_id, pane)| { + let geom = pane.current_geom(); + geom.is_stacked().then_some((geom.y, *pane_id)) + }) + .collect::>(); + panes.sort_unstable(); + panes + .into_iter() + .map(|(_, pane_id)| pane_id) + .collect::>() + }; - tab.new_pane( - new_pane_id_1, - None, - None, - false, - true, - NewPanePlacement::default(), - Some(client_id), - None, - ) - .unwrap(); - tab.new_pane( - new_pane_id_2, - None, - None, - false, - true, - NewPanePlacement::default(), - Some(client_id), - None, - ) - .unwrap(); - tab.new_pane( - new_pane_id_3, - None, - None, - false, - true, - NewPanePlacement::default(), - Some(client_id), - None, - ) - .unwrap(); + for pane_id in &new_pane_ids[..3] { + open_pane(&mut tab, *pane_id); + } + tab.move_focus_up(client_id).unwrap(); + open_pane(&mut tab, new_pane_ids[3]); + let mut expected_order = vec![PaneId::Terminal(1)]; + expected_order.extend(new_pane_ids); + assert_eq!(stack_order(&tab), expected_order); + tab.close_pane(new_pane_ids[1], false, None); + expected_order.remove(2); + assert_eq!(stack_order(&tab), expected_order); + tab.next_swap_layout().unwrap(); + assert_eq!(stack_order(&tab), expected_order); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), @@ -10663,6 +10852,7 @@ fn borderless_floating_pane() { height: Some(PercentOrFixed::Fixed(10)), pinned: None, borderless: Some(true), + border_style: None, }; tab.new_floating_pane( @@ -10710,6 +10900,7 @@ fn borderless_pane_content_fills_edges() { height: Some(PercentOrFixed::Fixed(5)), pinned: None, borderless: Some(true), + border_style: None, }; tab.new_floating_pane( @@ -10758,6 +10949,7 @@ fn borderless_pinned_floating_pane() { height: Some(PercentOrFixed::Fixed(8)), pinned: Some(true), borderless: Some(true), + border_style: None, }; tab.new_floating_pane( @@ -10810,6 +11002,7 @@ fn cursor_hidden_when_floating_pane_is_under_pinned_pane() { height: Some(PercentOrFixed::Fixed(10)), pinned: Some(false), borderless: Some(false), + border_style: None, }; tab.new_floating_pane( @@ -10831,6 +11024,7 @@ fn cursor_hidden_when_floating_pane_is_under_pinned_pane() { height: Some(PercentOrFixed::Fixed(8)), pinned: Some(true), borderless: Some(false), + border_style: None, }; tab.new_floating_pane( @@ -10890,6 +11084,7 @@ fn cursor_visible_when_pinned_pane_is_focused() { height: Some(PercentOrFixed::Fixed(10)), pinned: Some(false), borderless: Some(false), + border_style: None, }; tab.new_floating_pane( @@ -10911,6 +11106,7 @@ fn cursor_visible_when_pinned_pane_is_focused() { height: Some(PercentOrFixed::Fixed(8)), pinned: Some(true), borderless: Some(false), + border_style: None, }; tab.new_floating_pane( @@ -11352,6 +11548,7 @@ fn test_ctrl_drag_resizes_floating_pane_from_edge() { height: Some(PercentOrFixed::Fixed(10)), pinned: None, borderless: Some(false), + border_style: None, }; tab.toggle_floating_panes(Some(client_id), None, None) @@ -11433,6 +11630,7 @@ fn test_ctrl_drag_resizes_floating_pane_from_corner() { height: Some(PercentOrFixed::Fixed(10)), pinned: None, borderless: Some(false), + border_style: None, }; tab.toggle_floating_panes(Some(client_id), None, None) @@ -11514,6 +11712,7 @@ fn test_ctrl_drag_resizes_pinned_floating_pane_when_floating_panes_not_shown() { height: Some(PercentOrFixed::Fixed(10)), pinned: Some(true), borderless: Some(false), + border_style: None, }; tab.new_pane( @@ -11777,6 +11976,7 @@ fn test_left_click_on_floating_pane_changes_focus() { height: Some(PercentOrFixed::Fixed(10)), pinned: None, borderless: Some(false), + border_style: None, }; let coordinates_2 = FloatingPaneCoordinates { @@ -11786,6 +11986,7 @@ fn test_left_click_on_floating_pane_changes_focus() { height: Some(PercentOrFixed::Fixed(10)), pinned: None, borderless: Some(false), + border_style: None, }; tab.toggle_floating_panes(Some(client_id), None, None) @@ -11848,6 +12049,7 @@ fn test_left_click_on_pinned_floating_pane() { height: Some(PercentOrFixed::Fixed(10)), pinned: None, borderless: Some(false), + border_style: None, }; let coordinates_2 = FloatingPaneCoordinates { @@ -11857,6 +12059,7 @@ fn test_left_click_on_pinned_floating_pane() { height: Some(PercentOrFixed::Fixed(10)), pinned: None, borderless: Some(false), + border_style: None, }; tab.toggle_floating_panes(Some(client_id), None, None) @@ -13186,6 +13389,7 @@ fn test_ctrl_scroll_up_increases_pinned_floating_pane_size_when_floating_panes_h height: Some(PercentOrFixed::Fixed(10)), pinned: Some(true), borderless: Some(false), + border_style: None, }; tab.new_pane( @@ -13254,6 +13458,7 @@ fn test_ctrl_scroll_down_decreases_pinned_floating_pane_size_when_floating_panes height: Some(PercentOrFixed::Fixed(10)), pinned: Some(true), borderless: Some(false), + border_style: None, }; tab.new_pane( @@ -13526,6 +13731,7 @@ fn adding_stacked_pane_in_stack_list_mode_moves_existing_pane_to_suppressed_imme NewPanePlacement::Stacked { pane_id_to_stack_under: None, borderless: None, + border_style: None, }, Some(client_id), None, @@ -13753,6 +13959,7 @@ fn in_place_pane_with_close_replaced_pane_false_restores_original() { NewPanePlacement::Tiled { direction: None, borderless: None, + border_style: None, }, Some(client_id), None, @@ -13816,6 +14023,7 @@ fn in_place_pane_with_close_replaced_pane_true_closes_original() { NewPanePlacement::Tiled { direction: None, borderless: None, + border_style: None, }, Some(client_id), None, @@ -13921,6 +14129,7 @@ fn create_new_tab_with_plugin_receiver( draw_pane_frames, auto_layout, connected_clients, + Rc::new(RefCell::new(HashMap::new())), session_is_mirrored, Some(client_id), copy_options, @@ -14880,6 +15089,7 @@ fn focus_follows_mouse_focuses_floating_pane_on_hover() { height: Some(PercentOrFixed::Fixed(10)), pinned: None, borderless: Some(false), + border_style: None, }; let coordinates_2 = FloatingPaneCoordinates { @@ -14889,6 +15099,7 @@ fn focus_follows_mouse_focuses_floating_pane_on_hover() { height: Some(PercentOrFixed::Fixed(10)), pinned: None, borderless: Some(false), + border_style: None, }; tab.toggle_floating_panes(Some(client_id), None, None) @@ -15104,6 +15315,7 @@ fn focus_follows_mouse_ignores_tiled_pane_when_floating_visible() { height: Some(PercentOrFixed::Fixed(10)), pinned: None, borderless: Some(false), + border_style: None, })), Some(client_id), None, @@ -15154,6 +15366,7 @@ fn focus_follows_mouse_focuses_floating_pane_when_floating_visible() { height: Some(PercentOrFixed::Fixed(10)), pinned: None, borderless: Some(false), + border_style: None, })), Some(client_id), None, @@ -15172,6 +15385,7 @@ fn focus_follows_mouse_focuses_floating_pane_when_floating_visible() { height: Some(PercentOrFixed::Fixed(10)), pinned: None, borderless: Some(false), + border_style: None, })), Some(client_id), None, @@ -15706,6 +15920,7 @@ fn create_new_tab_with_server_receiver( PaneFrameStyle::Full, true, // auto_layout connected_clients, + Rc::new(RefCell::new(HashMap::new())), true, // session_is_mirrored Some(client_id), copy_options, @@ -16365,3 +16580,202 @@ fn host_focus_events_only_reach_panes_that_asked_for_them() { .map(|bytes| String::from_utf8_lossy(bytes).to_string()); assert_eq!(written, Some("\u{1b}[O\u{1b}[I".to_owned())); } + +fn border_style_override(all: LineStyle) -> BorderStyleOverride { + BorderStyleOverride { + all: Some(all), + ..Default::default() + } +} + +fn uniform_border_style(line_style: LineStyle) -> BorderStyle { + BorderStyle { + top: line_style, + right: line_style, + bottom: line_style, + left: line_style, + rounded_corners: false, + } +} + +fn render_tab(tab: &mut Tab, size: Size, client_id: ClientId) -> String { + let mut output = Output::default(); + tab.render(&mut output, None).unwrap(); + take_snapshot( + output.serialize().unwrap().get(&client_id).unwrap(), + size.rows, + size.cols, + Palette::default(), + ) +} + +#[test] +fn a_per_pane_border_style_is_drawn_on_the_pane_frame() { + let size = Size { cols: 40, rows: 10 }; + let client_id = 1; + let mut tab = create_new_tab(size, ModeInfo::default()); + tab.set_pane_border_style( + PaneId::Terminal(1), + border_style_override(LineStyle::Double), + ); + let snapshot = render_tab(&mut tab, size, client_id); + assert!(snapshot.contains('╔'), "{}", snapshot); + assert!(snapshot.contains('║'), "{}", snapshot); + assert!(snapshot.contains('╝'), "{}", snapshot); +} + +#[test] +fn a_per_pane_border_style_reaches_a_floating_pane() { + let size = Size { cols: 60, rows: 20 }; + let client_id = 1; + let mut tab = create_new_tab(size, ModeInfo::default()); + tab.toggle_floating_panes(Some(client_id), None, None) + .unwrap(); + tab.new_pane( + PaneId::Terminal(2), + None, + None, + false, + true, + NewPanePlacement::default(), + Some(client_id), + None, + ) + .unwrap(); + tab.set_pane_border_style(PaneId::Terminal(2), border_style_override(LineStyle::Heavy)); + let snapshot = render_tab(&mut tab, size, client_id); + assert!(snapshot.contains('┏'), "{}", snapshot); + assert!(snapshot.contains('┃'), "{}", snapshot); +} + +#[test] +fn setting_a_border_style_on_an_unknown_pane_reports_failure() { + let size = Size { cols: 40, rows: 10 }; + let mut tab = create_new_tab(size, ModeInfo::default()); + assert!(tab.set_pane_border_style( + PaneId::Terminal(1), + border_style_override(LineStyle::Double) + )); + assert!(!tab.set_pane_border_style( + PaneId::Terminal(99), + border_style_override(LineStyle::Double) + )); +} + +#[test] +fn an_empty_border_style_override_restores_the_configured_default() { + let size = Size { cols: 40, rows: 10 }; + let client_id = 1; + let mut tab = create_new_tab(size, ModeInfo::default()); + tab.set_pane_border_style( + PaneId::Terminal(1), + border_style_override(LineStyle::Double), + ); + assert!(render_tab(&mut tab, size, client_id).contains('╔')); + tab.set_pane_border_style(PaneId::Terminal(1), BorderStyleOverride::default()); + let snapshot = render_tab(&mut tab, size, client_id); + assert!(!snapshot.contains('╔'), "{}", snapshot); + assert!(snapshot.contains('┌'), "{}", snapshot); +} + +#[test] +fn reconfiguring_the_global_border_style_repaints_open_panes() { + let size = Size { cols: 40, rows: 10 }; + let client_id = 1; + let mut tab = create_new_tab(size, ModeInfo::default()); + assert!(render_tab(&mut tab, size, client_id).contains('┌')); + let heavy = uniform_border_style(LineStyle::Heavy); + tab.update_border_styles(heavy, heavy); + let snapshot = render_tab(&mut tab, size, client_id); + assert!(snapshot.contains('┏'), "{}", snapshot); + assert!(snapshot.contains('┛'), "{}", snapshot); +} + +#[test] +fn a_per_pane_border_style_wins_over_the_global_one() { + let size = Size { cols: 40, rows: 10 }; + let client_id = 1; + let mut tab = create_new_tab(size, ModeInfo::default()); + let heavy = uniform_border_style(LineStyle::Heavy); + tab.update_border_styles(heavy, heavy); + tab.set_pane_border_style( + PaneId::Terminal(1), + border_style_override(LineStyle::Double), + ); + let snapshot = render_tab(&mut tab, size, client_id); + assert!(snapshot.contains('╔'), "{}", snapshot); + assert!(!snapshot.contains('┏'), "{}", snapshot); +} + +#[test] +fn border_styles_from_a_layout_reach_the_panes() { + let size = Size { cols: 60, rows: 20 }; + let client_id = 1; + let layout = r#" + layout { + pane border_style="double" + floating_panes { + pane x=2 y=2 width=20 height=6 border_style="heavy" + } + } + "#; + let mut tab = create_new_tab_with_layout(size, ModeInfo::default(), layout); + let snapshot = render_tab(&mut tab, size, client_id); + assert!(snapshot.contains('╔'), "{}", snapshot); + assert!(snapshot.contains('┏'), "{}", snapshot); +} + +#[test] +fn matching_neighbours_share_a_styled_grid_line() { + let size = Size { cols: 60, rows: 10 }; + let client_id = 1; + let mut tab = create_new_tab(size, ModeInfo::default()); + tab.set_pane_frames(PaneFrameStyle::None); + tab.vertical_split(PaneId::Terminal(2), None, client_id, None, None) + .unwrap(); + tab.set_pane_border_style( + PaneId::Terminal(1), + border_style_override(LineStyle::Double), + ); + tab.set_pane_border_style( + PaneId::Terminal(2), + border_style_override(LineStyle::Double), + ); + let snapshot = render_tab(&mut tab, size, client_id); + assert!(snapshot.contains('║'), "{}", snapshot); +} + +#[test] +fn mismatched_neighbours_fall_back_to_the_ambient_grid_line() { + let size = Size { cols: 60, rows: 10 }; + let client_id = 1; + let mut tab = create_new_tab(size, ModeInfo::default()); + tab.set_pane_frames(PaneFrameStyle::None); + tab.vertical_split(PaneId::Terminal(2), None, client_id, None, None) + .unwrap(); + let double = uniform_border_style(LineStyle::Double); + tab.update_border_styles(double, double); + tab.set_pane_border_style(PaneId::Terminal(2), border_style_override(LineStyle::Heavy)); + let snapshot = render_tab(&mut tab, size, client_id); + assert!(snapshot.contains('║'), "{}", snapshot); + assert!(!snapshot.contains('┃'), "{}", snapshot); + assert!(!snapshot.contains('│'), "{}", snapshot); +} + +#[test] +fn mismatched_neighbours_fall_back_to_single_when_the_ambient_style_is_mixed() { + let size = Size { cols: 60, rows: 10 }; + let client_id = 1; + let mut tab = create_new_tab(size, ModeInfo::default()); + tab.set_pane_frames(PaneFrameStyle::None); + tab.vertical_split(PaneId::Terminal(2), None, client_id, None, None) + .unwrap(); + let mixed = BorderStyle { + top: LineStyle::Double, + ..Default::default() + }; + tab.update_border_styles(mixed, mixed); + tab.set_pane_border_style(PaneId::Terminal(2), border_style_override(LineStyle::Heavy)); + let snapshot = render_tab(&mut tab, size, client_id); + assert!(snapshot.contains('│'), "{}", snapshot); +} diff --git a/zellij-server/src/tab/unit/tab_tests.rs b/zellij-server/src/tab/unit/tab_tests.rs index 86c5d6b9f..7239405bc 100644 --- a/zellij-server/src/tab/unit/tab_tests.rs +++ b/zellij-server/src/tab/unit/tab_tests.rs @@ -70,19 +70,18 @@ impl ServerOsApi for FakeInputOutput { fn send_to_client(&self, _client_id: ClientId, _msg: ServerToClientMsg) -> Result<()> { unimplemented!() } - fn new_client( + fn register_client( &mut self, _client_id: ClientId, - _stream: LocalSocketStream, - ) -> Result> { + _receiver: &IpcReceiverWithContext, + ) -> Result<()> { unimplemented!() } - fn new_client_with_reply( + fn register_client_with_reply( &mut self, _client_id: ClientId, - _stream: LocalSocketStream, _reply_stream: LocalSocketStream, - ) -> Result> { + ) -> Result<()> { unimplemented!() } fn remove_client(&mut self, _client_id: ClientId) -> Result<()> { @@ -212,6 +211,7 @@ fn create_new_tab_with_plugin_receiver( draw_pane_frames, auto_layout, connected_clients, + Rc::new(RefCell::new(HashMap::new())), session_is_mirrored, Some(client_id), copy_options, @@ -303,6 +303,7 @@ fn create_new_tab_with_layout(size: Size, layout: TiledPaneLayout) -> Tab { draw_pane_frames, auto_layout, connected_clients, + Rc::new(RefCell::new(HashMap::new())), session_is_mirrored, Some(client_id), copy_options, @@ -400,6 +401,7 @@ fn create_new_tab_with_cell_size( draw_pane_frames, auto_layout, connected_clients, + Rc::new(RefCell::new(HashMap::new())), session_is_mirrored, Some(client_id), copy_options, @@ -910,6 +912,7 @@ pub fn split_in_direction_without_a_connected_client_still_creates_the_pane() { NewPanePlacement::Tiled { direction: Some(Direction::Down), borderless: None, + border_style: None, }, None, None, @@ -940,6 +943,7 @@ pub fn split_in_direction_without_a_connected_client_splits_the_existing_pane() NewPanePlacement::Tiled { direction: Some(Direction::Right), borderless: None, + border_style: None, }, None, None, diff --git a/zellij-server/src/ui/border_glyphs.rs b/zellij-server/src/ui/border_glyphs.rs new file mode 100644 index 000000000..5c4baca2f --- /dev/null +++ b/zellij-server/src/ui/border_glyphs.rs @@ -0,0 +1,379 @@ +use crate::ui::boundaries::boundary_type; +use zellij_utils::data::LineStyle; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Corner { + TopLeft, + TopRight, + BottomLeft, + BottomRight, +} + +pub fn horizontal(line_style: LineStyle) -> &'static str { + match line_style { + LineStyle::Single => "─", + LineStyle::Double => "═", + LineStyle::Heavy => "━", + LineStyle::Dashed => "┄", + LineStyle::HeavyDashed => "┅", + } +} + +pub fn vertical(line_style: LineStyle) -> &'static str { + match line_style { + LineStyle::Single => "│", + LineStyle::Double => "║", + LineStyle::Heavy => "┃", + LineStyle::Dashed => "┆", + LineStyle::HeavyDashed => "┇", + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum Weight { + Light, + Double, + Heavy, +} + +fn weight(line_style: LineStyle) -> Weight { + match line_style { + LineStyle::Single | LineStyle::Dashed => Weight::Light, + LineStyle::Double => Weight::Double, + LineStyle::Heavy | LineStyle::HeavyDashed => Weight::Heavy, + } +} + +pub fn corner( + corner: Corner, + horizontal_style: LineStyle, + vertical_style: LineStyle, + rounded: bool, +) -> &'static str { + let horizontal_weight = weight(horizontal_style); + let mut vertical_weight = weight(vertical_style); + // there are no glyphs mixing a double arm with a heavy arm, in this case we render both arms + // in the weight of the horizontal arm + if (horizontal_weight == Weight::Double && vertical_weight == Weight::Heavy) + || (horizontal_weight == Weight::Heavy && vertical_weight == Weight::Double) + { + vertical_weight = horizontal_weight; + } + if rounded && horizontal_weight == Weight::Light && vertical_weight == Weight::Light { + return match corner { + Corner::TopLeft => boundary_type::TOP_LEFT_ROUND, + Corner::TopRight => boundary_type::TOP_RIGHT_ROUND, + Corner::BottomLeft => boundary_type::BOTTOM_LEFT_ROUND, + Corner::BottomRight => boundary_type::BOTTOM_RIGHT_ROUND, + }; + } + match (corner, horizontal_weight, vertical_weight) { + (Corner::TopLeft, Weight::Light, Weight::Light) => "┌", + (Corner::TopLeft, Weight::Double, Weight::Light) => "╒", + (Corner::TopLeft, Weight::Light, Weight::Double) => "╓", + (Corner::TopLeft, Weight::Double, Weight::Double) => "╔", + (Corner::TopLeft, Weight::Heavy, Weight::Light) => "┍", + (Corner::TopLeft, Weight::Light, Weight::Heavy) => "┎", + (Corner::TopLeft, Weight::Heavy, Weight::Heavy) => "┏", + + (Corner::TopRight, Weight::Light, Weight::Light) => "┐", + (Corner::TopRight, Weight::Double, Weight::Light) => "╕", + (Corner::TopRight, Weight::Light, Weight::Double) => "╖", + (Corner::TopRight, Weight::Double, Weight::Double) => "╗", + (Corner::TopRight, Weight::Heavy, Weight::Light) => "┑", + (Corner::TopRight, Weight::Light, Weight::Heavy) => "┒", + (Corner::TopRight, Weight::Heavy, Weight::Heavy) => "┓", + + (Corner::BottomLeft, Weight::Light, Weight::Light) => "└", + (Corner::BottomLeft, Weight::Double, Weight::Light) => "╘", + (Corner::BottomLeft, Weight::Light, Weight::Double) => "╙", + (Corner::BottomLeft, Weight::Double, Weight::Double) => "╚", + (Corner::BottomLeft, Weight::Heavy, Weight::Light) => "┕", + (Corner::BottomLeft, Weight::Light, Weight::Heavy) => "┖", + (Corner::BottomLeft, Weight::Heavy, Weight::Heavy) => "┗", + + (Corner::BottomRight, Weight::Light, Weight::Light) => "┘", + (Corner::BottomRight, Weight::Double, Weight::Light) => "╛", + (Corner::BottomRight, Weight::Light, Weight::Double) => "╜", + (Corner::BottomRight, Weight::Double, Weight::Double) => "╝", + (Corner::BottomRight, Weight::Heavy, Weight::Light) => "┙", + (Corner::BottomRight, Weight::Light, Weight::Heavy) => "┚", + (Corner::BottomRight, Weight::Heavy, Weight::Heavy) => "┛", + + (_, Weight::Double, Weight::Heavy) | (_, Weight::Heavy, Weight::Double) => { + unreachable!("double/heavy mixes are normalized above") + }, + } +} + +pub fn title_separator_left(horizontal_style: LineStyle) -> &'static str { + match weight(horizontal_style) { + Weight::Light => boundary_type::VERTICAL_LEFT, + Weight::Double => "╡", + Weight::Heavy => "┥", + } +} + +pub fn title_separator_right(horizontal_style: LineStyle) -> &'static str { + match weight(horizontal_style) { + Weight::Light => boundary_type::VERTICAL_RIGHT, + Weight::Double => "╞", + Weight::Heavy => "┝", + } +} + +pub fn remap_light_glyph(glyph: &'static str, line_style: LineStyle) -> &'static str { + match line_style { + LineStyle::Single => glyph, + LineStyle::Dashed => match glyph { + boundary_type::HORIZONTAL => "┄", + boundary_type::VERTICAL => "┆", + _ => glyph, + }, + LineStyle::Double => match glyph { + boundary_type::HORIZONTAL => "═", + boundary_type::VERTICAL => "║", + boundary_type::TOP_LEFT => "╔", + boundary_type::TOP_LEFT_ROUND => "╔", + boundary_type::TOP_RIGHT => "╗", + boundary_type::TOP_RIGHT_ROUND => "╗", + boundary_type::BOTTOM_LEFT => "╚", + boundary_type::BOTTOM_LEFT_ROUND => "╚", + boundary_type::BOTTOM_RIGHT => "╝", + boundary_type::BOTTOM_RIGHT_ROUND => "╝", + boundary_type::VERTICAL_LEFT => "╣", + boundary_type::VERTICAL_RIGHT => "╠", + boundary_type::HORIZONTAL_DOWN => "╦", + boundary_type::HORIZONTAL_UP => "╩", + boundary_type::CROSS => "╬", + _ => glyph, + }, + LineStyle::Heavy | LineStyle::HeavyDashed => { + let heavy_dashed = line_style == LineStyle::HeavyDashed; + match glyph { + boundary_type::HORIZONTAL => { + if heavy_dashed { + "┅" + } else { + "━" + } + }, + boundary_type::VERTICAL => { + if heavy_dashed { + "┇" + } else { + "┃" + } + }, + boundary_type::TOP_LEFT => "┏", + boundary_type::TOP_LEFT_ROUND => "┏", + boundary_type::TOP_RIGHT => "┓", + boundary_type::TOP_RIGHT_ROUND => "┓", + boundary_type::BOTTOM_LEFT => "┗", + boundary_type::BOTTOM_LEFT_ROUND => "┗", + boundary_type::BOTTOM_RIGHT => "┛", + boundary_type::BOTTOM_RIGHT_ROUND => "┛", + boundary_type::VERTICAL_LEFT => "┫", + boundary_type::VERTICAL_RIGHT => "┣", + boundary_type::HORIZONTAL_DOWN => "┳", + boundary_type::HORIZONTAL_UP => "┻", + boundary_type::CROSS => "╋", + _ => glyph, + } + }, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const ALL: [LineStyle; 5] = [ + LineStyle::Single, + LineStyle::Double, + LineStyle::Heavy, + LineStyle::Dashed, + LineStyle::HeavyDashed, + ]; + + const CORNERS: [Corner; 4] = [ + Corner::TopLeft, + Corner::TopRight, + Corner::BottomLeft, + Corner::BottomRight, + ]; + + #[test] + fn every_edge_style_has_its_own_glyph() { + let horizontals: Vec<&str> = ALL.iter().map(|s| horizontal(*s)).collect(); + let verticals: Vec<&str> = ALL.iter().map(|s| vertical(*s)).collect(); + assert_eq!(horizontals, vec!["─", "═", "━", "┄", "┅"]); + assert_eq!(verticals, vec!["│", "║", "┃", "┆", "┇"]); + } + + #[test] + fn every_corner_combination_resolves_to_a_single_width_glyph() { + for corner in CORNERS { + for horizontal_style in ALL { + for vertical_style in ALL { + for rounded in [false, true] { + let glyph = + super::corner(corner, horizontal_style, vertical_style, rounded); + assert_eq!( + glyph.chars().count(), + 1, + "{:?} {:?}/{:?} rounded={} produced {:?}", + corner, + horizontal_style, + vertical_style, + rounded, + glyph + ); + } + } + } + } + } + + #[test] + fn corners_only_round_when_both_arms_are_single_width() { + assert_eq!( + corner(Corner::TopLeft, LineStyle::Single, LineStyle::Single, true), + "╭" + ); + assert_eq!( + corner(Corner::TopLeft, LineStyle::Dashed, LineStyle::Dashed, true), + "╭" + ); + assert_eq!( + corner(Corner::TopLeft, LineStyle::Double, LineStyle::Single, true), + "╒" + ); + assert_eq!( + corner(Corner::TopLeft, LineStyle::Single, LineStyle::Heavy, true), + "┎" + ); + } + + #[test] + fn mixed_weight_corners_use_the_dedicated_glyphs() { + assert_eq!( + corner( + Corner::BottomRight, + LineStyle::Double, + LineStyle::Single, + false + ), + "╛" + ); + assert_eq!( + corner( + Corner::BottomRight, + LineStyle::Single, + LineStyle::Double, + false + ), + "╜" + ); + assert_eq!( + corner( + Corner::BottomRight, + LineStyle::Heavy, + LineStyle::Single, + false + ), + "┙" + ); + assert_eq!( + corner( + Corner::BottomRight, + LineStyle::Single, + LineStyle::Heavy, + false + ), + "┚" + ); + } + + #[test] + fn a_double_and_heavy_corner_uses_the_horizontal_arm_for_both() { + assert_eq!( + corner(Corner::TopLeft, LineStyle::Double, LineStyle::Heavy, false), + "╔" + ); + assert_eq!( + corner(Corner::TopLeft, LineStyle::Heavy, LineStyle::Double, false), + "┏" + ); + } + + #[test] + fn title_separators_follow_the_horizontal_weight() { + assert_eq!(title_separator_left(LineStyle::Single), "┤"); + assert_eq!(title_separator_right(LineStyle::Single), "├"); + assert_eq!(title_separator_left(LineStyle::Double), "╡"); + assert_eq!(title_separator_right(LineStyle::Double), "╞"); + assert_eq!(title_separator_left(LineStyle::HeavyDashed), "┥"); + assert_eq!(title_separator_right(LineStyle::HeavyDashed), "┝"); + } + + #[test] + fn remapping_covers_every_light_glyph_for_double_and_heavy() { + let light = [ + boundary_type::HORIZONTAL, + boundary_type::VERTICAL, + boundary_type::TOP_LEFT, + boundary_type::TOP_RIGHT, + boundary_type::BOTTOM_LEFT, + boundary_type::BOTTOM_RIGHT, + boundary_type::VERTICAL_LEFT, + boundary_type::VERTICAL_RIGHT, + boundary_type::HORIZONTAL_DOWN, + boundary_type::HORIZONTAL_UP, + boundary_type::CROSS, + ]; + for glyph in light { + for line_style in [LineStyle::Double, LineStyle::Heavy] { + assert_ne!( + remap_light_glyph(glyph, line_style), + glyph, + "{:?} left {} unmapped", + line_style, + glyph + ); + } + } + } + + #[test] + fn dashed_remapping_only_touches_the_straight_runs() { + assert_eq!( + remap_light_glyph(boundary_type::HORIZONTAL, LineStyle::Dashed), + "┄" + ); + assert_eq!( + remap_light_glyph(boundary_type::VERTICAL, LineStyle::Dashed), + "┆" + ); + assert_eq!( + remap_light_glyph(boundary_type::CROSS, LineStyle::Dashed), + boundary_type::CROSS + ); + assert_eq!( + remap_light_glyph(boundary_type::HORIZONTAL, LineStyle::HeavyDashed), + "┅" + ); + assert_eq!( + remap_light_glyph(boundary_type::CROSS, LineStyle::HeavyDashed), + "╋" + ); + } + + #[test] + fn a_single_style_remap_is_the_identity() { + assert_eq!( + remap_light_glyph(boundary_type::CROSS, LineStyle::Single), + boundary_type::CROSS + ); + } +} diff --git a/zellij-server/src/ui/boundaries.rs b/zellij-server/src/ui/boundaries.rs index 91dc76d4c..888bd5a17 100644 --- a/zellij-server/src/ui/boundaries.rs +++ b/zellij-server/src/ui/boundaries.rs @@ -3,10 +3,14 @@ use zellij_utils::pane_size::{Offset, Viewport}; use crate::output::CharacterChunk; use crate::panes::terminal_character::{TerminalCharacter, EMPTY_TERMINAL_CHARACTER, RESET_STYLES}; use crate::tab::Pane; +use crate::ui::border_glyphs::remap_light_glyph; use ansi_term::Colour::{Fixed, RGB}; use std::collections::HashMap; use zellij_utils::errors::prelude::*; -use zellij_utils::{data::PaletteColor, shared::colors}; +use zellij_utils::{ + data::{BorderStyle, LineStyle, PaletteColor}, + shared::colors, +}; use std::fmt::{Display, Error, Formatter}; pub mod boundary_type { @@ -34,6 +38,7 @@ pub struct BoundarySymbol { boundary_type: BoundaryType, invisible: bool, color: Option<(PaletteColor, usize)>, // (color, color_precedence) + line_style: Option, } impl BoundarySymbol { @@ -42,25 +47,39 @@ impl BoundarySymbol { boundary_type, invisible: false, color: Some((PaletteColor::EightBit(colors::GRAY), 0)), + line_style: Some(LineStyle::Single), } } pub fn color(&mut self, color: Option<(PaletteColor, usize)>) -> Self { self.color = color; *self } - pub fn as_terminal_character(&self) -> Result { + pub fn line_style(&mut self, line_style: LineStyle) -> Self { + self.line_style = Some(line_style); + *self + } + pub fn glyph(&self, fallback_line_style: LineStyle) -> BoundaryType { + remap_light_glyph( + self.boundary_type, + self.line_style.unwrap_or(fallback_line_style), + ) + } + pub fn as_terminal_character( + &self, + fallback_line_style: LineStyle, + ) -> Result { let tc = if self.invisible { EMPTY_TERMINAL_CHARACTER } else { - let character = self - .boundary_type + let glyph = self.glyph(fallback_line_style); + let character = glyph .chars() .next() .context("no boundary symbols defined") .with_context(|| { format!( "failed to convert boundary symbol {} into terminal character", - self.boundary_type + glyph ) })?; TerminalCharacter::new_singlewidth_styled( @@ -76,18 +95,15 @@ impl BoundarySymbol { impl Display for BoundarySymbol { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + let glyph = self.glyph(LineStyle::Single); match self.invisible { true => write!(f, " "), false => match self.color { Some(color) => match color.0 { - PaletteColor::Rgb((r, g, b)) => { - write!(f, "{}", RGB(r, g, b).paint(self.boundary_type)) - }, - PaletteColor::EightBit(color) => { - write!(f, "{}", Fixed(color).paint(self.boundary_type)) - }, + PaletteColor::Rgb((r, g, b)) => write!(f, "{}", RGB(r, g, b).paint(glyph)), + PaletteColor::EightBit(color) => write!(f, "{}", Fixed(color).paint(glyph)), }, - None => write!(f, "{}", self.boundary_type), + None => write!(f, "{}", glyph), }, } } @@ -110,6 +126,16 @@ fn combine_symbols( }, _ => current_symbol.color.or(next_symbol.color), }; + let line_style = match (current_symbol.line_style, next_symbol.line_style) { + (Some(current_line_style), Some(next_line_style)) => { + if current_line_style == next_line_style { + Some(current_line_style) + } else { + None + } + }, + _ => None, + }; match (current_symbol.boundary_type, next_symbol.boundary_type) { (CROSS, _) | (_, CROSS) => { // (┼, *) or (*, ┼) => Some(┼) @@ -118,6 +144,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (TOP_RIGHT, TOP_RIGHT) => { @@ -127,6 +154,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (TOP_RIGHT, VERTICAL) | (TOP_RIGHT, BOTTOM_RIGHT) | (TOP_RIGHT, VERTICAL_LEFT) => { @@ -138,6 +166,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (TOP_RIGHT, HORIZONTAL) | (TOP_RIGHT, TOP_LEFT) | (TOP_RIGHT, HORIZONTAL_DOWN) => { @@ -149,6 +178,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (TOP_RIGHT, BOTTOM_LEFT) | (TOP_RIGHT, VERTICAL_RIGHT) | (TOP_RIGHT, HORIZONTAL_UP) => { @@ -160,6 +190,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (HORIZONTAL, HORIZONTAL) => { @@ -169,6 +200,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (HORIZONTAL, VERTICAL) | (HORIZONTAL, VERTICAL_LEFT) | (HORIZONTAL, VERTICAL_RIGHT) => { @@ -180,6 +212,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (HORIZONTAL, TOP_LEFT) | (HORIZONTAL, HORIZONTAL_DOWN) => { @@ -190,6 +223,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (HORIZONTAL, BOTTOM_RIGHT) | (HORIZONTAL, BOTTOM_LEFT) | (HORIZONTAL, HORIZONTAL_UP) => { @@ -201,6 +235,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (VERTICAL, VERTICAL) => { @@ -210,6 +245,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (VERTICAL, TOP_LEFT) | (VERTICAL, BOTTOM_LEFT) | (VERTICAL, VERTICAL_RIGHT) => { @@ -221,6 +257,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (VERTICAL, BOTTOM_RIGHT) | (VERTICAL, VERTICAL_LEFT) => { @@ -231,6 +268,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (VERTICAL, HORIZONTAL_DOWN) | (VERTICAL, HORIZONTAL_UP) => { @@ -241,6 +279,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (TOP_LEFT, TOP_LEFT) => { @@ -250,6 +289,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (TOP_LEFT, BOTTOM_RIGHT) | (TOP_LEFT, VERTICAL_LEFT) | (TOP_LEFT, HORIZONTAL_UP) => { @@ -261,6 +301,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (TOP_LEFT, BOTTOM_LEFT) | (TOP_LEFT, VERTICAL_RIGHT) => { @@ -271,6 +312,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (TOP_LEFT, HORIZONTAL_DOWN) => { @@ -280,6 +322,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (BOTTOM_RIGHT, BOTTOM_RIGHT) => { @@ -289,6 +332,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (BOTTOM_RIGHT, BOTTOM_LEFT) | (BOTTOM_RIGHT, HORIZONTAL_UP) => { @@ -299,6 +343,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (BOTTOM_RIGHT, VERTICAL_LEFT) => { @@ -308,6 +353,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (BOTTOM_RIGHT, VERTICAL_RIGHT) | (BOTTOM_RIGHT, HORIZONTAL_DOWN) => { @@ -318,6 +364,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (BOTTOM_LEFT, BOTTOM_LEFT) => { @@ -327,6 +374,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (BOTTOM_LEFT, VERTICAL_LEFT) | (BOTTOM_LEFT, HORIZONTAL_DOWN) => { @@ -337,6 +385,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (BOTTOM_LEFT, VERTICAL_RIGHT) => { @@ -346,6 +395,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (BOTTOM_LEFT, HORIZONTAL_UP) => { @@ -355,6 +405,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (VERTICAL_LEFT, VERTICAL_LEFT) => { @@ -364,6 +415,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (VERTICAL_LEFT, VERTICAL_RIGHT) @@ -377,6 +429,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (VERTICAL_RIGHT, VERTICAL_RIGHT) => { @@ -386,6 +439,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (VERTICAL_RIGHT, HORIZONTAL_DOWN) | (VERTICAL_RIGHT, HORIZONTAL_UP) => { @@ -396,6 +450,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (HORIZONTAL_DOWN, HORIZONTAL_DOWN) => { @@ -405,6 +460,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (HORIZONTAL_DOWN, HORIZONTAL_UP) => { @@ -414,6 +470,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (HORIZONTAL_UP, HORIZONTAL_UP) => { @@ -423,6 +480,7 @@ fn combine_symbols( boundary_type, invisible, color, + line_style, }) }, (_, _) => combine_symbols(next_symbol, current_symbol), @@ -443,14 +501,16 @@ impl Coordinates { pub struct Boundaries { viewport: Viewport, + fallback_line_style: LineStyle, pub boundary_characters: HashMap, } #[allow(clippy::if_same_then_else)] impl Boundaries { - pub fn new(viewport: Viewport) -> Self { + pub fn new(viewport: Viewport, fallback_line_style: LineStyle) -> Self { Boundaries { viewport, + fallback_line_style, boundary_characters: HashMap::new(), } } @@ -458,6 +518,7 @@ impl Boundaries { &mut self, rect: &dyn Pane, color: Option<(PaletteColor, usize)>, // (color, color_precedence) + border_style: BorderStyle, pane_is_on_top_of_stack: bool, pane_is_on_bottom_of_stack: bool, pane_is_stacked_under: bool, @@ -479,19 +540,29 @@ impl Boundaries { let coordinates = Coordinates::new(boundary_x_coords, row); let symbol_to_add = if row == first_row_coordinates && row != self.viewport.y { if pane_is_stacked { - BoundarySymbol::new(boundary_type::VERTICAL_RIGHT).color(color) + BoundarySymbol::new(boundary_type::VERTICAL_RIGHT) + .color(color) + .line_style(border_style.left) } else { - BoundarySymbol::new(boundary_type::TOP_LEFT).color(color) + BoundarySymbol::new(boundary_type::TOP_LEFT) + .color(color) + .line_style(border_style.left) } } else if row == first_row_coordinates && pane_is_stacked { - BoundarySymbol::new(boundary_type::TOP_LEFT).color(color) + BoundarySymbol::new(boundary_type::TOP_LEFT) + .color(color) + .line_style(border_style.left) } else if row == last_row_coordinates - 1 && row != self.viewport.y + self.viewport.rows - 1 && content_offset.bottom > 0 { - BoundarySymbol::new(boundary_type::BOTTOM_LEFT).color(color) + BoundarySymbol::new(boundary_type::BOTTOM_LEFT) + .color(color) + .line_style(border_style.left) } else { - BoundarySymbol::new(boundary_type::VERTICAL).color(color) + BoundarySymbol::new(boundary_type::VERTICAL) + .color(color) + .line_style(border_style.left) }; let next_symbol = self .boundary_characters @@ -509,11 +580,17 @@ impl Boundaries { for col in first_col_coordinates..last_col_coordinates { let coordinates = Coordinates::new(col, boundary_y_coords); let symbol_to_add = if col == first_col_coordinates && col != self.viewport.x { - BoundarySymbol::new(boundary_type::TOP_LEFT).color(color) + BoundarySymbol::new(boundary_type::TOP_LEFT) + .color(color) + .line_style(border_style.top) } else if col == last_col_coordinates - 1 && col != self.viewport.cols - 1 { - BoundarySymbol::new(boundary_type::TOP_RIGHT).color(color) + BoundarySymbol::new(boundary_type::TOP_RIGHT) + .color(color) + .line_style(border_style.top) } else { - BoundarySymbol::new(boundary_type::HORIZONTAL).color(color) + BoundarySymbol::new(boundary_type::HORIZONTAL) + .color(color) + .line_style(border_style.top) }; let next_symbol = self .boundary_characters @@ -532,20 +609,30 @@ impl Boundaries { for row in first_row_coordinates..last_row_coordinates { let coordinates = Coordinates::new(boundary_x_coords, row); let symbol_to_add = if row == first_row_coordinates && pane_is_stacked { - BoundarySymbol::new(boundary_type::VERTICAL_LEFT).color(color) + BoundarySymbol::new(boundary_type::VERTICAL_LEFT) + .color(color) + .line_style(border_style.right) } else if row == first_row_coordinates && row != self.viewport.y { if pane_is_stacked { - BoundarySymbol::new(boundary_type::VERTICAL_LEFT).color(color) + BoundarySymbol::new(boundary_type::VERTICAL_LEFT) + .color(color) + .line_style(border_style.right) } else { - BoundarySymbol::new(boundary_type::TOP_RIGHT).color(color) + BoundarySymbol::new(boundary_type::TOP_RIGHT) + .color(color) + .line_style(border_style.right) } } else if row == last_row_coordinates - 1 && row != self.viewport.y + self.viewport.rows - 1 && content_offset.bottom > 0 { - BoundarySymbol::new(boundary_type::BOTTOM_RIGHT).color(color) + BoundarySymbol::new(boundary_type::BOTTOM_RIGHT) + .color(color) + .line_style(border_style.right) } else { - BoundarySymbol::new(boundary_type::VERTICAL).color(color) + BoundarySymbol::new(boundary_type::VERTICAL) + .color(color) + .line_style(border_style.right) }; let next_symbol = self .boundary_characters @@ -563,11 +650,17 @@ impl Boundaries { for col in first_col_coordinates..last_col_coordinates { let coordinates = Coordinates::new(col, boundary_y_coords); let symbol_to_add = if col == first_col_coordinates && col != self.viewport.x { - BoundarySymbol::new(boundary_type::BOTTOM_LEFT).color(color) + BoundarySymbol::new(boundary_type::BOTTOM_LEFT) + .color(color) + .line_style(border_style.bottom) } else if col == last_col_coordinates - 1 && col != self.viewport.cols - 1 { - BoundarySymbol::new(boundary_type::BOTTOM_RIGHT).color(color) + BoundarySymbol::new(boundary_type::BOTTOM_RIGHT) + .color(color) + .line_style(border_style.bottom) } else { - BoundarySymbol::new(boundary_type::HORIZONTAL).color(color) + BoundarySymbol::new(boundary_type::HORIZONTAL) + .color(color) + .line_style(border_style.bottom) }; let next_symbol = self .boundary_characters @@ -584,16 +677,25 @@ impl Boundaries { ) -> Result> { let mut character_chunks = vec![]; for (coordinates, boundary_character) in &self.boundary_characters { + let glyph = boundary_character.glyph(self.fallback_line_style); let already_on_screen = existing_boundaries_on_screen .and_then(|e| e.boundary_characters.get(coordinates)) - .map(|e| e == boundary_character) + .map(|e| { + e.color == boundary_character.color + && e.invisible == boundary_character.invisible + && e.glyph( + existing_boundaries_on_screen + .map(|b| b.fallback_line_style) + .unwrap_or(LineStyle::Single), + ) == glyph + }) .unwrap_or(false); if already_on_screen { continue; } character_chunks.push(CharacterChunk::new( vec![boundary_character - .as_terminal_character() + .as_terminal_character(self.fallback_line_style) .context("failed to render as terminal character")?], coordinates.x, coordinates.y, @@ -653,3 +755,57 @@ impl Boundaries { && rect.y() + rect.rows() <= self.viewport.y + self.viewport.rows } } + +#[cfg(test)] +mod tests { + use super::*; + + fn symbol(boundary_type: BoundaryType, line_style: LineStyle) -> BoundarySymbol { + BoundarySymbol::new(boundary_type).line_style(line_style) + } + + #[test] + fn matching_line_styles_are_kept_through_a_junction() { + let combined = combine_symbols( + symbol(boundary_type::HORIZONTAL, LineStyle::Double), + symbol(boundary_type::VERTICAL, LineStyle::Double), + ) + .unwrap(); + assert_eq!(combined.glyph(LineStyle::Single), "╬"); + } + + #[test] + fn conflicting_line_styles_fall_back_to_the_ambient_style() { + let combined = combine_symbols( + symbol(boundary_type::HORIZONTAL, LineStyle::Double), + symbol(boundary_type::VERTICAL, LineStyle::Heavy), + ) + .unwrap(); + assert_eq!(combined.glyph(LineStyle::Single), "┼"); + assert_eq!(combined.glyph(LineStyle::Double), "╬"); + } + + #[test] + fn a_style_without_a_glyph_for_the_junction_falls_back_to_single() { + let combined = combine_symbols( + symbol(boundary_type::HORIZONTAL, LineStyle::Dashed), + symbol(boundary_type::VERTICAL, LineStyle::Dashed), + ) + .unwrap(); + assert_eq!(combined.glyph(LineStyle::Single), "┼"); + assert_eq!( + symbol(boundary_type::HORIZONTAL, LineStyle::Dashed).glyph(LineStyle::Single), + "┄" + ); + } + + #[test] + fn heavy_junctions_are_kept() { + let combined = combine_symbols( + symbol(boundary_type::HORIZONTAL, LineStyle::Heavy), + symbol(boundary_type::TOP_LEFT, LineStyle::Heavy), + ) + .unwrap(); + assert_eq!(combined.glyph(LineStyle::Single), "┳"); + } +} diff --git a/zellij-server/src/ui/mod.rs b/zellij-server/src/ui/mod.rs index 1d3ba1f61..77344ac2c 100644 --- a/zellij-server/src/ui/mod.rs +++ b/zellij-server/src/ui/mod.rs @@ -1,3 +1,4 @@ +pub mod border_glyphs; pub mod boundaries; pub mod components; pub mod hint_text; diff --git a/zellij-server/src/ui/pane_boundaries_frame.rs b/zellij-server/src/ui/pane_boundaries_frame.rs index 16ba30534..780ec7f8d 100644 --- a/zellij-server/src/ui/pane_boundaries_frame.rs +++ b/zellij-server/src/ui/pane_boundaries_frame.rs @@ -3,13 +3,14 @@ use crate::panes::{ AnsiCode, CharacterStyles, RcCharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER, }; use crate::tab::GuestChoiceIndicator; +use crate::ui::border_glyphs::{self, Corner}; use crate::ui::boundaries::boundary_type; use crate::ui::hint_text::{ exit_code_segments, hover_segments, rerun_segments, resize_segments, HintExitStatus, HintLevel, HintSegment, HintTier, }; use crate::ClientId; -use zellij_utils::data::{client_id_to_colors, PaletteColor, Style}; +use zellij_utils::data::{client_slot_to_colors, BorderStyle, LineStyle, PaletteColor, Style}; use zellij_utils::errors::prelude::*; use zellij_utils::pane_size::{Offset, PaneGeom, Viewport}; use zellij_utils::position::Position; @@ -79,7 +80,9 @@ pub struct FrameParams { pub focused_client: Option, pub is_main_client: bool, // more accurately: is_focused_for_main_client pub other_focused_clients: Vec, + pub other_focused_client_slots: Vec, pub style: Style, + pub border_style: BorderStyle, pub color: Option, pub other_cursors_exist_in_session: bool, pub pane_is_stacked_under: bool, @@ -108,11 +111,13 @@ pub struct PaneFrame { pub title: String, pub scroll_position: (usize, usize), // (position, length) pub style: Style, + pub border_style: BorderStyle, pub color: Option, pub focused_client: Option, pub is_main_client: bool, pub other_cursors_exist_in_session: bool, pub other_focused_clients: Vec, + pub other_focused_client_slots: Vec, exit_status: Option, is_first_run: bool, pane_is_stacked_over: bool, @@ -147,10 +152,12 @@ impl PaneFrame { title: main_title, scroll_position, style: frame_params.style, + border_style: frame_params.border_style, color: frame_params.color, focused_client: frame_params.focused_client, is_main_client: frame_params.is_main_client, other_focused_clients: frame_params.other_focused_clients, + other_focused_client_slots: frame_params.other_focused_client_slots, other_cursors_exist_in_session: frame_params.other_cursors_exist_in_session, exit_status: None, is_first_run: false, @@ -191,33 +198,67 @@ impl PaneFrame { self.color = Some(color); self.color_override = Some(color); } - fn client_cursor(&self, client_id: ClientId) -> Vec { - let color = client_id_to_colors(client_id, self.style.colors.multiplayer_user_colors); + fn client_cursor(&self, display_slot: usize) -> Vec { + let color = client_slot_to_colors(display_slot, self.style.colors.multiplayer_user_colors); background_color(" ", color.map(|c| c.0)) } + fn top_horizontal(&self) -> &'static str { + border_glyphs::horizontal(self.border_style.top) + } + fn bottom_horizontal(&self) -> &'static str { + border_glyphs::horizontal(self.border_style.bottom) + } + fn left_vertical(&self) -> &'static str { + border_glyphs::vertical(self.border_style.left) + } + fn right_vertical(&self) -> &'static str { + border_glyphs::vertical(self.border_style.right) + } + fn title_separator_left(&self) -> &'static str { + border_glyphs::title_separator_left(self.border_style.top) + } + fn title_separator_right(&self) -> &'static str { + border_glyphs::title_separator_right(self.border_style.top) + } + fn horizontal_style_of_corner(&self, corner: Corner) -> LineStyle { + match corner { + Corner::TopLeft | Corner::TopRight => self.border_style.top, + Corner::BottomLeft | Corner::BottomRight => self.border_style.bottom, + } + } + fn vertical_style_of_corner(&self, corner: Corner) -> LineStyle { + match corner { + Corner::TopLeft | Corner::BottomLeft => self.border_style.left, + Corner::TopRight | Corner::BottomRight => self.border_style.right, + } + } fn get_corner(&self, corner: &'static str) -> &'static str { - let corner = if !self.should_draw_pane_frames + if !self.should_draw_pane_frames && (corner == boundary_type::TOP_LEFT || corner == boundary_type::TOP_RIGHT) { - boundary_type::HORIZONTAL - } else if self.pane_is_stacked_under && corner == boundary_type::TOP_RIGHT { - boundary_type::BOTTOM_RIGHT - } else if self.pane_is_stacked_under && corner == boundary_type::TOP_LEFT { - boundary_type::BOTTOM_LEFT - } else { - corner + return self.top_horizontal(); + } + let style_corner = match corner { + boundary_type::TOP_LEFT => Corner::TopLeft, + boundary_type::TOP_RIGHT => Corner::TopRight, + boundary_type::BOTTOM_LEFT => Corner::BottomLeft, + _ => Corner::BottomRight, }; - if self.style.rounded_corners { - match corner { - boundary_type::TOP_RIGHT => boundary_type::TOP_RIGHT_ROUND, - boundary_type::TOP_LEFT => boundary_type::TOP_LEFT_ROUND, - boundary_type::BOTTOM_RIGHT => boundary_type::BOTTOM_RIGHT_ROUND, - boundary_type::BOTTOM_LEFT => boundary_type::BOTTOM_LEFT_ROUND, - _ => corner, + let shape_corner = if self.pane_is_stacked_under { + match style_corner { + Corner::TopRight => Corner::BottomRight, + Corner::TopLeft => Corner::BottomLeft, + other => other, } } else { - corner - } + style_corner + }; + border_glyphs::corner( + shape_corner, + self.horizontal_style_of_corner(style_corner), + self.vertical_style_of_corner(style_corner), + self.border_style.rounded_corners, + ) } fn render_title_right_side( &self, @@ -310,8 +351,8 @@ impl PaneFrame { } } fn render_my_focus(&self, max_length: usize) -> Option<(Vec, usize)> { - let mut left_separator = foreground_color(boundary_type::VERTICAL_LEFT, self.color); - let mut right_separator = foreground_color(boundary_type::VERTICAL_RIGHT, self.color); + let mut left_separator = foreground_color(self.title_separator_left(), self.color); + let mut right_separator = foreground_color(self.title_separator_right(), self.color); let full_indication_text = "MY FOCUS"; let mut full_indication = vec![]; full_indication.append(&mut left_separator); @@ -340,16 +381,16 @@ impl PaneFrame { &self, max_length: usize, ) -> Option<(Vec, usize)> { - let mut left_separator = foreground_color(boundary_type::VERTICAL_LEFT, self.color); - let mut right_separator = foreground_color(boundary_type::VERTICAL_RIGHT, self.color); + let mut left_separator = foreground_color(self.title_separator_left(), self.color); + let mut right_separator = foreground_color(self.title_separator_right(), self.color); let full_indication_text = "MY FOCUS AND:"; let short_indication_text = "+"; let mut full_indication = foreground_color(full_indication_text, self.color); let mut full_indication_len = full_indication_text.width(); let mut short_indication = foreground_color(short_indication_text, self.color); let mut short_indication_len = short_indication_text.width(); - for client_id in &self.other_focused_clients { - let mut text = self.client_cursor(*client_id); + for display_slot in &self.other_focused_client_slots { + let mut text = self.client_cursor(*display_slot); full_indication_len += 2; full_indication.push(EMPTY_TERMINAL_CHARACTER); full_indication.append(&mut text.clone()); @@ -383,8 +424,8 @@ impl PaneFrame { &self, max_length: usize, ) -> Option<(Vec, usize)> { - let mut left_separator = foreground_color(boundary_type::VERTICAL_LEFT, self.color); - let mut right_separator = foreground_color(boundary_type::VERTICAL_RIGHT, self.color); + let mut left_separator = foreground_color(self.title_separator_left(), self.color); + let mut right_separator = foreground_color(self.title_separator_right(), self.color); let full_indication_text = if self.other_focused_clients.len() == 1 { "FOCUSED USER:" } else { @@ -397,8 +438,8 @@ impl PaneFrame { let mut middle_indication_len = middle_indication_text.width(); let mut short_indication = vec![]; let mut short_indication_len = 0; - for client_id in &self.other_focused_clients { - let mut text = self.client_cursor(*client_id); + for display_slot in &self.other_focused_client_slots { + let mut text = self.client_cursor(*display_slot); full_indication_len += 2; full_indication.push(EMPTY_TERMINAL_CHARACTER); full_indication.append(&mut text.clone()); @@ -563,7 +604,7 @@ impl PaneFrame { col += right_side_len; continue; } else { - title_line.append(&mut foreground_color(boundary_type::HORIZONTAL, self.color)); + title_line.append(&mut foreground_color(self.top_horizontal(), self.color)); } if col == self.geom.x + self.geom.cols - 1 { break; @@ -605,7 +646,7 @@ impl PaneFrame { col += *middle_len; continue; } else { - title_line.append(&mut foreground_color(boundary_type::HORIZONTAL, self.color)); + title_line.append(&mut foreground_color(self.top_horizontal(), self.color)); } if col == self.geom.x + self.geom.cols - 1 { break; @@ -640,7 +681,7 @@ impl PaneFrame { col += *middle_len; continue; } else { - title_line.append(&mut foreground_color(boundary_type::HORIZONTAL, self.color)); + title_line.append(&mut foreground_color(self.top_horizontal(), self.color)); } if col == self.geom.x + self.geom.cols - 1 { break; @@ -663,7 +704,7 @@ impl PaneFrame { let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners let mut middle = String::new(); for _ in (left_side_len + right_side_len)..total_title_length { - middle.push_str(boundary_type::HORIZONTAL); + middle.push_str(self.top_horizontal()); } let mut ret = vec![]; ret.append(&mut left_boundary); @@ -685,7 +726,7 @@ impl PaneFrame { let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners let mut middle_padding = String::new(); for _ in *left_side_len..total_title_length { - middle_padding.push_str(boundary_type::HORIZONTAL); + middle_padding.push_str(self.top_horizontal()); } let mut ret = vec![]; ret.append(&mut left_boundary); @@ -702,7 +743,7 @@ impl PaneFrame { let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners let mut middle_padding = String::new(); for _ in 0..total_title_length { - middle_padding.push_str(boundary_type::HORIZONTAL); + middle_padding.push_str(self.top_horizontal()); } let mut ret = vec![]; ret.append(&mut left_boundary); @@ -812,7 +853,7 @@ impl PaneFrame { line.push(EMPTY_TERMINAL_CHARACTER); } } - line.append(&mut foreground_color(boundary_type::VERTICAL, None)); + line.append(&mut foreground_color(self.left_vertical(), None)); for _ in 0..corner_padding { line.push(EMPTY_TERMINAL_CHARACTER); } @@ -824,7 +865,7 @@ impl PaneFrame { for _ in 0..corner_padding { line.push(EMPTY_TERMINAL_CHARACTER); } - line.append(&mut foreground_color(boundary_type::VERTICAL, None)); + line.append(&mut foreground_color(self.right_vertical(), None)); let mut occupied_columns = entry_start + full_width_entry_length; let (mut scroll_part, scroll_length) = self .bracketed_scroll_indicator(usable_cols.saturating_sub(occupied_columns)) @@ -973,9 +1014,9 @@ impl PaneFrame { fn focused_users_part(&self, label: &str) -> (Vec, usize) { let mut content = foreground_color(label, self.color); let mut content_length = label.width(); - for client_id in &self.other_focused_clients { + for display_slot in &self.other_focused_client_slots { content.push(EMPTY_TERMINAL_CHARACTER); - content.append(&mut self.client_cursor(*client_id)); + content.append(&mut self.client_cursor(*display_slot)); content_length += 2; } self.bracketed_title_part_from_characters(content, content_length) @@ -983,12 +1024,12 @@ impl PaneFrame { fn focused_users_cursors_part(&self) -> (Vec, usize) { let mut content = vec![]; let mut content_length = 0; - for client_id in &self.other_focused_clients { + for display_slot in &self.other_focused_client_slots { if content_length > 0 { content.push(EMPTY_TERMINAL_CHARACTER); content_length += 1; } - content.append(&mut self.client_cursor(*client_id)); + content.append(&mut self.client_cursor(*display_slot)); content_length += 1; } self.bracketed_title_part_from_characters(content, content_length) @@ -1045,7 +1086,7 @@ impl PaneFrame { }; let right_start = width.saturating_sub(right_length).max(middle_end); let fill_character = if self.pane_is_stacked { - foreground_color(boundary_type::HORIZONTAL, self.color) + foreground_color(self.bottom_horizontal(), self.color) .into_iter() .next() .unwrap_or(EMPTY_TERMINAL_CHARACTER) @@ -1096,7 +1137,7 @@ impl PaneFrame { let padding_len = max_undertitle_length.saturating_sub(text_len); let mut padding = String::new(); for _ in 0..padding_len { - padding.push_str(boundary_type::HORIZONTAL); + padding.push_str(self.bottom_horizontal()); } let mut ret = vec![]; @@ -1130,7 +1171,7 @@ impl PaneFrame { let padding_len = max_undertitle_length.saturating_sub(help_text_len); let mut padding = String::new(); for _ in 0..padding_len { - padding.push_str(boundary_type::HORIZONTAL); + padding.push_str(self.bottom_horizontal()); } let mut ret = vec![]; @@ -1183,7 +1224,7 @@ impl PaneFrame { // render exit status and tips let mut padding = String::new(); for _ in full_text_len..max_undertitle_length { - padding.push_str(boundary_type::HORIZONTAL); + padding.push_str(self.bottom_horizontal()); } let mut ret = vec![]; ret.append(&mut left_boundary); @@ -1196,7 +1237,7 @@ impl PaneFrame { // render only exit status let mut padding = String::new(); for _ in first_part_len..max_undertitle_length { - padding.push_str(boundary_type::HORIZONTAL); + padding.push_str(self.bottom_horizontal()); } let mut ret = vec![]; ret.append(&mut left_boundary); @@ -1213,7 +1254,7 @@ impl PaneFrame { let full_text_len = first_part_len; let mut padding = String::new(); for _ in full_text_len..max_undertitle_length { - padding.push_str(boundary_type::HORIZONTAL); + padding.push_str(self.bottom_horizontal()); } let mut ret = vec![]; ret.append(&mut left_boundary); @@ -1241,7 +1282,7 @@ impl PaneFrame { // render exit status and tips let mut padding = String::new(); for _ in hover_shortcuts_len..max_undertitle_length { - padding.push_str(boundary_type::HORIZONTAL); + padding.push_str(self.bottom_horizontal()); } let mut ret = vec![]; ret.append(&mut left_boundary); @@ -1369,7 +1410,7 @@ impl PaneFrame { // bottom right corner self.get_corner(boundary_type::BOTTOM_RIGHT) } else { - boundary_type::HORIZONTAL + self.bottom_horizontal() }; let mut boundary_character = foreground_color(boundary, self.color); @@ -1381,9 +1422,9 @@ impl PaneFrame { } } else { let boundary_character_left = - foreground_color(boundary_type::VERTICAL, self.color); + foreground_color(self.left_vertical(), self.color); let boundary_character_right = - foreground_color(boundary_type::VERTICAL, self.color); + foreground_color(self.right_vertical(), self.color); let x = self.geom.x; let y = self.geom.y + row; @@ -1421,7 +1462,7 @@ impl PaneFrame { Some(ExitStatus::Exited) => { self.render_hint_segments(&exit_code_segments(HintExitStatus::Exited)) }, - None => (foreground_color(boundary_type::HORIZONTAL, self.color), 1), + None => (foreground_color(self.bottom_horizontal(), self.color), 1), } } fn second_held_title_part_full(&self) -> (Vec, usize) { @@ -1438,7 +1479,7 @@ impl PaneFrame { let mut ret = vec![]; let mut padding = String::new(); for _ in 0..max_undertitle_length { - padding.push_str(boundary_type::HORIZONTAL); + padding.push_str(self.bottom_horizontal()); } ret.append(&mut left_boundary); ret.append(&mut foreground_color(&padding, self.color)); @@ -1467,7 +1508,9 @@ mod tests { focused_client: None, is_main_client: true, other_focused_clients: vec![], + other_focused_client_slots: vec![], style: Style::default(), + border_style: BorderStyle::default(), color: None, other_cursors_exist_in_session: false, pane_is_stacked_over: false, @@ -1496,6 +1539,94 @@ mod tests { chars.iter().map(|c| c.character).collect() } + fn frame_rows(border_style: BorderStyle) -> Vec { + let mut frame = pane_frame_with(false, false, 10); + frame.border_style = border_style; + frame.geom.rows = 3; + frame.mouse_hover_tips = false; + frame.show_help_text = false; + let (chunks, _) = frame.render().unwrap(); + let mut rows: Vec<(usize, usize, String)> = chunks + .iter() + .map(|c| (c.y, c.x, characters_to_string(&c.terminal_characters))) + .collect(); + rows.sort(); + let mut by_row: Vec = vec![String::new(); 3]; + for (y, _x, text) in rows { + by_row[y].push_str(&text); + } + by_row + } + + #[test] + fn single_line_frame_with_rounded_corners() { + let rows = frame_rows(BorderStyle { + rounded_corners: true, + ..Default::default() + }); + assert!(rows[0].starts_with('╭'), "{}", rows[0]); + assert!(rows[0].ends_with('╮'), "{}", rows[0]); + assert_eq!(rows[1], "││"); + assert_eq!(rows[2], "╰────────╯"); + } + + #[test] + fn double_top_border_with_single_sides() { + let rows = frame_rows(BorderStyle { + top: LineStyle::Double, + rounded_corners: true, + ..Default::default() + }); + assert!(rows[0].starts_with('╒'), "{}", rows[0]); + assert!(rows[0].ends_with('╕'), "{}", rows[0]); + assert_eq!(rows[1], "││"); + assert_eq!(rows[2], "╰────────╯"); + } + + #[test] + fn all_double_border() { + let rows = frame_rows(BorderStyle { + top: LineStyle::Double, + right: LineStyle::Double, + bottom: LineStyle::Double, + left: LineStyle::Double, + rounded_corners: true, + }); + assert!(rows[0].starts_with('╔'), "{}", rows[0]); + assert!(rows[0].ends_with('╗'), "{}", rows[0]); + assert_eq!(rows[1], "║║"); + assert_eq!(rows[2], "╚════════╝"); + } + + #[test] + fn heavy_and_double_corner_falls_back_to_the_horizontal_arm() { + let rows = frame_rows(BorderStyle { + top: LineStyle::Double, + right: LineStyle::Heavy, + bottom: LineStyle::Heavy, + left: LineStyle::Heavy, + rounded_corners: false, + }); + assert!(rows[0].starts_with('╔'), "{}", rows[0]); + assert!(rows[0].ends_with('╗'), "{}", rows[0]); + assert_eq!(rows[1], "┃┃"); + assert_eq!(rows[2], "┗━━━━━━━━┛"); + } + + #[test] + fn dashed_border_uses_light_corners() { + let rows = frame_rows(BorderStyle { + top: LineStyle::Dashed, + right: LineStyle::Dashed, + bottom: LineStyle::Dashed, + left: LineStyle::Dashed, + rounded_corners: false, + }); + assert!(rows[0].starts_with('┌'), "{}", rows[0]); + assert_eq!(rows[1], "┆┆"); + assert_eq!(rows[2], "└┄┄┄┄┄┄┄┄┘"); + } + #[test] fn help_text_full_includes_ctrl_scroll_when_enabled_for_tiled_pane() { let frame = pane_frame_with(true, false, 80); diff --git a/zellij-server/src/ui/pane_contents_and_ui.rs b/zellij-server/src/ui/pane_contents_and_ui.rs index dad3765d4..758874fd9 100644 --- a/zellij-server/src/ui/pane_contents_and_ui.rs +++ b/zellij-server/src/ui/pane_contents_and_ui.rs @@ -6,7 +6,7 @@ use crate::ui::boundaries::Boundaries; use crate::ui::pane_boundaries_frame::{FrameParams, StackListEntry}; use crate::ClientId; use std::collections::{HashMap, HashSet}; -use zellij_utils::data::{client_id_to_colors, InputMode, PaletteColor, Style}; +use zellij_utils::data::{client_slot_to_colors, BorderStyle, InputMode, PaletteColor, Style}; use zellij_utils::errors::prelude::*; use zellij_utils::pane_size::PaneGeom; @@ -25,6 +25,7 @@ pub struct PaneContentsAndUi<'a> { output: &'a mut Output, style: Style, focused_clients: Vec, + client_display_slots: HashMap, multiple_users_exist_in_session: bool, z_index: Option, pane_is_stacked_under: bool, @@ -62,6 +63,7 @@ impl<'a> PaneContentsAndUi<'a> { mouse_scroll_resize: bool, mouse_hover_tips: bool, dimmed_for_clients: HashSet, + client_display_slots: &HashMap, ) -> Self { let mut focused_clients: Vec = active_panes .iter() @@ -69,6 +71,7 @@ impl<'a> PaneContentsAndUi<'a> { .map(|(c_id, _p_id)| *c_id) .collect(); focused_clients.sort_unstable(); + let client_display_slots = client_display_slots.clone(); let mouse_is_hovering_over_pane_for_clients = mouse_hover_pane_id .iter() .filter_map(|(client_id, pane_id)| { @@ -84,6 +87,7 @@ impl<'a> PaneContentsAndUi<'a> { output, style, focused_clients, + client_display_slots, multiple_users_exist_in_session, z_index, pane_is_stacked_under, @@ -103,6 +107,12 @@ impl<'a> PaneContentsAndUi<'a> { dimmed_for_clients, } } + fn display_slot_of(&self, client_id: ClientId) -> usize { + self.client_display_slots + .get(&client_id) + .copied() + .unwrap_or(0) + } fn frame_is_dimmed_for_client(&self, client_id: ClientId) -> bool { self.dimmed_for_clients.contains(&client_id) && !self.focused_clients.contains(&client_id) } @@ -256,8 +266,8 @@ impl<'a> PaneContentsAndUi<'a> { .with_context(|| { format!("failed to render fake cursor if needed for client {client_id}") })?; - if let Some(colors) = client_id_to_colors( - *fake_cursor_client_id, + if let Some(colors) = client_slot_to_colors( + self.display_slot_of(*fake_cursor_client_id), self.style.colors.multiplayer_user_colors, ) { let cursor_is_visible = self @@ -358,12 +368,15 @@ impl<'a> PaneContentsAndUi<'a> { }); let frame_is_dimmed = self.frame_is_dimmed_for_client(client_id); let guest_choice_indicator = self.pane.guest_choice_indicator(client_id); + let border_style = self.border_style(pane_is_floating); let frame_params = if session_is_mirrored { FrameParams { focused_client, is_main_client: pane_focused_for_client_id, other_focused_clients: vec![], + other_focused_client_slots: vec![], style: self.style, + border_style, color: frame_color.map(|c| c.0), other_cursors_exist_in_session: false, pane_is_stacked_over: self.pane_is_stacked_over, @@ -391,8 +404,13 @@ impl<'a> PaneContentsAndUi<'a> { FrameParams { focused_client, is_main_client: pane_focused_for_client_id, + other_focused_client_slots: other_focused_clients + .iter() + .map(|c_id| self.display_slot_of(*c_id)) + .collect(), other_focused_clients, style: self.style, + border_style, color: frame_color.map(|c| c.0), other_cursors_exist_in_session: self.multiple_users_exist_in_session, pane_is_stacked_over: self.pane_is_stacked_over, @@ -437,6 +455,14 @@ impl<'a> PaneContentsAndUi<'a> { Ok(()) } + pub fn border_style(&self, pane_is_floating: bool) -> BorderStyle { + let base = if pane_is_floating { + self.style.floating_border_style + } else { + self.style.border_style + }; + self.pane.border_style_override().apply_to(base) + } pub fn render_pane_boundaries( &self, client_id: ClientId, @@ -447,9 +473,11 @@ impl<'a> PaneContentsAndUi<'a> { pane_is_on_bottom_of_stack: bool, ) { let color = self.frame_color(client_id, client_mode, session_is_mirrored); + let pane_is_floating = false; boundaries.add_rect( self.pane.as_ref(), color, + self.border_style(pane_is_floating), pane_is_on_top_of_stack, pane_is_on_bottom_of_stack, self.pane_is_stacked_under, @@ -483,8 +511,8 @@ impl<'a> PaneContentsAndUi<'a> { if session_is_mirrored || !self.multiple_users_exist_in_session { Some((self.style.colors.frame_selected.base, 3)) } else { - let colors = client_id_to_colors( - client_id, + let colors = client_slot_to_colors( + self.display_slot_of(client_id), self.style.colors.multiplayer_user_colors, ); colors.map(|colors| (colors.0, 3)) diff --git a/zellij-server/src/unit/pty_tests.rs b/zellij-server/src/unit/pty_tests.rs index 39463a467..d9e83b202 100644 --- a/zellij-server/src/unit/pty_tests.rs +++ b/zellij-server/src/unit/pty_tests.rs @@ -85,19 +85,18 @@ impl ServerOsApi for MockOsApi { fn send_to_client(&self, _: ClientId, _: ServerToClientMsg) -> anyhow::Result<()> { Ok(()) } - fn new_client( + fn register_client( &mut self, _: ClientId, - _: LocalSocketStream, - ) -> anyhow::Result> { + _: &IpcReceiverWithContext, + ) -> anyhow::Result<()> { unimplemented!() } - fn new_client_with_reply( + fn register_client_with_reply( &mut self, _: ClientId, _: LocalSocketStream, - _: LocalSocketStream, - ) -> anyhow::Result> { + ) -> anyhow::Result<()> { unimplemented!() } fn remove_client(&mut self, _: ClientId) -> anyhow::Result<()> { diff --git a/zellij-server/src/unit/pty_writer_tests.rs b/zellij-server/src/unit/pty_writer_tests.rs index 4c104063f..c505a7141 100644 --- a/zellij-server/src/unit/pty_writer_tests.rs +++ b/zellij-server/src/unit/pty_writer_tests.rs @@ -129,19 +129,18 @@ impl ServerOsApi for MockServerOsApi { fn send_to_client(&self, _client_id: ClientId, _msg: ServerToClientMsg) -> Result<()> { Ok(()) } - fn new_client( + fn register_client( &mut self, _client_id: ClientId, - _stream: LocalSocketStream, - ) -> Result> { + _receiver: &IpcReceiverWithContext, + ) -> Result<()> { unimplemented!() } - fn new_client_with_reply( + fn register_client_with_reply( &mut self, _client_id: ClientId, - _stream: LocalSocketStream, _reply_stream: LocalSocketStream, - ) -> Result> { + ) -> Result<()> { unimplemented!() } fn remove_client(&mut self, _client_id: ClientId) -> Result<()> { diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 88586bfde..647a30286 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -236,19 +236,18 @@ impl ServerOsApi for FakeInputOutput { .push(msg); Ok(()) } - fn new_client( + fn register_client( &mut self, _client_id: ClientId, - _stream: LocalSocketStream, - ) -> Result> { + _receiver: &IpcReceiverWithContext, + ) -> Result<()> { unimplemented!() } - fn new_client_with_reply( + fn register_client_with_reply( &mut self, _client_id: ClientId, - _stream: LocalSocketStream, _reply_stream: LocalSocketStream, - ) -> Result> { + ) -> Result<()> { unimplemented!() } fn remove_client(&mut self, _client_id: ClientId) -> Result<()> { @@ -408,7 +407,7 @@ fn seed_first_client_size(mut screen: Screen, size: Size) -> Screen { } struct MockScreen { - pub main_client_id: u16, + pub main_client_id: ClientId, pub pty_receiver: Option>, pub pty_writer_receiver: Option>, #[allow(dead_code)] @@ -1744,6 +1743,7 @@ fn open_new_floating_pane_with_custom_coordinates() { height: Some(PercentOrFixed::Fixed(2)), pinned: None, borderless: Some(false), + border_style: None, })), Some(1), None, @@ -1780,6 +1780,7 @@ fn open_new_floating_pane_with_custom_coordinates_exceeding_viewport() { height: Some(PercentOrFixed::Fixed(10)), pinned: None, borderless: Some(false), + border_style: None, })), Some(1), None, @@ -1816,6 +1817,7 @@ fn floating_pane_auto_centers_horizontally_with_only_width() { height: Some(PercentOrFixed::Fixed(10)), pinned: None, borderless: Some(false), + border_style: None, })), Some(1), None, @@ -1852,6 +1854,7 @@ fn floating_pane_auto_centers_vertically_with_only_height() { height: Some(PercentOrFixed::Fixed(20)), pinned: None, borderless: Some(false), + border_style: None, })), Some(1), None, @@ -1888,6 +1891,7 @@ fn floating_pane_auto_centers_both_axes_with_only_size() { height: Some(PercentOrFixed::Fixed(30)), pinned: None, borderless: Some(false), + border_style: None, })), Some(1), None, @@ -1924,6 +1928,7 @@ fn floating_pane_respects_explicit_coordinates_with_size() { height: Some(PercentOrFixed::Fixed(30)), pinned: None, borderless: Some(false), + border_style: None, })), Some(1), None, @@ -1960,6 +1965,7 @@ fn floating_pane_centers_with_percentage_width() { height: Some(PercentOrFixed::Fixed(20)), pinned: None, borderless: Some(false), + border_style: None, })), Some(1), None, @@ -2001,6 +2007,7 @@ fn floating_pane_centers_large_pane_safely() { height: Some(PercentOrFixed::Fixed(50)), pinned: None, borderless: Some(false), + border_style: None, })), Some(1), None, @@ -2413,6 +2420,7 @@ fn group_panes_following_focus() { NewPanePlacement::Tiled { direction: None, borderless: None, + border_style: None, }, Some(client_id), None, @@ -2474,6 +2482,7 @@ fn break_group_with_mouse() { NewPanePlacement::Tiled { direction: None, borderless: None, + border_style: None, }, Some(client_id), None, @@ -3572,6 +3581,7 @@ pub fn send_cli_new_pane_action_with_default_parameters() { no_focus: false, borderless: Some(false), tab_id: None, + border_style: None, }; send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be @@ -3607,6 +3617,7 @@ pub fn web_new_pane_in_tab_action_targets_requested_tab() { no_focus: false, borderless: None, tab_id: Some(0), + border_style: None, }; route_arbitrary_action_to_server(&session_metadata, action, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); @@ -3682,6 +3693,7 @@ pub fn send_cli_new_pane_action_with_split_direction() { no_focus: false, borderless: Some(false), tab_id: None, + border_style: None, }; send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be @@ -3738,6 +3750,7 @@ pub fn send_cli_new_pane_action_with_command_and_cwd() { no_focus: false, borderless: Some(false), tab_id: None, + border_style: None, }; send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be @@ -3805,6 +3818,7 @@ pub fn send_cli_new_pane_action_with_floating_pane_and_coordinates() { no_focus: false, borderless: Some(false), tab_id: None, + border_style: None, }; send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be @@ -3849,6 +3863,7 @@ pub fn send_cli_edit_action_with_default_parameters() { near_current_pane: false, no_focus: false, tab_id: None, + border_style: None, }; send_cli_action_to_server(&session_metadata, cli_edit_action, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be @@ -3893,6 +3908,7 @@ pub fn send_cli_edit_action_with_line_number() { near_current_pane: false, no_focus: false, tab_id: None, + border_style: None, }; send_cli_action_to_server(&session_metadata, cli_edit_action, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be @@ -3937,6 +3953,7 @@ pub fn send_cli_edit_action_with_split_direction() { near_current_pane: false, no_focus: false, tab_id: None, + border_style: None, }; send_cli_action_to_server(&session_metadata, cli_edit_action, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be @@ -5310,6 +5327,7 @@ pub fn send_cli_change_floating_pane_coordinates_action() { height: Some("10".to_owned()), pinned: None, borderless: Some(false), + border_style: None, }; send_cli_action_to_server( &session_metadata, @@ -5329,6 +5347,80 @@ pub fn send_cli_change_floating_pane_coordinates_action() { assert_snapshot!(format!("{}", snapshot_count)); } +#[test] +pub fn send_cli_set_pane_border_style_action() { + let size = Size { cols: 80, rows: 10 }; + let client_id = 10; + let mut mock_screen = MockScreen::new(size); + let session_metadata = mock_screen.clone_session_metadata(); + let screen_thread = mock_screen.run(Some(TiledPaneLayout::default()), vec![]); + + let received_server_instructions = Arc::new(Mutex::new(vec![])); + let server_receiver = mock_screen.server_receiver.take().unwrap(); + let server_thread = log_actions_in_thread!( + received_server_instructions, + ServerInstruction::KillSession, + server_receiver + ); + let set_pane_border_style_action = CliAction::SetPaneBorderStyle { + pane_id: "0".to_owned(), + border_style: Some("double,rounded:false".to_owned()), + }; + send_cli_action_to_server(&session_metadata, set_pane_border_style_action, client_id); + std::thread::sleep(std::time::Duration::from_millis(100)); + mock_screen.teardown(vec![server_thread, screen_thread]); + let snapshots = take_snapshots_and_cursor_coordinates_from_render_events( + received_server_instructions.lock().unwrap().iter(), + size, + ); + let snapshot_count = snapshots.len(); + for (_cursor_coordinates, snapshot) in snapshots { + assert_snapshot!(format!("{}", snapshot)); + } + assert_snapshot!(format!("{}", snapshot_count)); +} + +#[test] +pub fn set_pane_border_style_reports_an_unknown_pane() { + let size = Size { cols: 80, rows: 10 }; + let client_id = 10; + let mut mock_screen = MockScreen::new(size); + let session_metadata = mock_screen.clone_session_metadata(); + let screen_thread = mock_screen.run(Some(TiledPaneLayout::default()), vec![]); + let server_receiver = mock_screen.server_receiver.take().unwrap(); + let server_thread = std::thread::spawn(move || { + while let Ok(instruction) = server_receiver.recv() { + if matches!(instruction, (ServerInstruction::KillSession, _)) { + break; + } + } + }); + + let (_, completion) = route_action( + Action::SetPaneBorderStyle { + pane_id: PaneId::Terminal(999).into(), + border_style: Default::default(), + }, + client_id, + None, + None, + session_metadata.senders.clone(), + None, + None, + InputMode::Normal, + None, + ) + .unwrap(); + let completion = completion.unwrap(); + assert_eq!(completion.exit_status, Some(1)); + assert_eq!( + completion.error_message.as_deref(), + Some("Pane with id Terminal(999) not found") + ); + + mock_screen.teardown(vec![server_thread, screen_thread]); +} + #[test] pub fn go_to_tab_by_id_verifies_screen_state() { let size = Size { @@ -5633,6 +5725,7 @@ pub fn send_cli_new_pane_in_place_with_close_replaced_pane() { no_focus: false, borderless: None, tab_id: None, + border_style: None, }; send_cli_action_to_server(&session_metadata, cli_action, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); @@ -5685,6 +5778,7 @@ pub fn send_cli_edit_in_place_with_close_replaced_pane() { no_focus: false, borderless: None, tab_id: None, + border_style: None, }; send_cli_action_to_server(&session_metadata, cli_action, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); @@ -8424,6 +8518,7 @@ pub fn send_cli_new_pane_action_with_tab_id() { no_focus: false, borderless: Some(false), tab_id: Some(0), + border_style: None, }; send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); @@ -8487,6 +8582,7 @@ pub fn send_cli_new_floating_pane_action_with_tab_id() { no_focus: false, borderless: None, tab_id: Some(0), + border_style: None, }; send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); @@ -8537,6 +8633,7 @@ pub fn send_cli_edit_action_with_tab_id() { no_focus: false, borderless: None, tab_id: Some(0), + border_style: None, }; send_cli_action_to_server(&session_metadata, cli_edit_action, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); @@ -8599,6 +8696,7 @@ pub fn send_cli_new_pane_action_with_tab_id_and_direction() { no_focus: false, borderless: Some(false), tab_id: Some(0), + border_style: None, }; send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); @@ -8661,6 +8759,7 @@ pub fn send_cli_new_pane_action_with_tab_id_and_stacked() { no_focus: false, borderless: None, tab_id: Some(0), + border_style: None, }; send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_default_parameters.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_default_parameters.snap index 8996ded86..5cf42d136 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_default_parameters.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_default_parameters.snap @@ -2,4 +2,4 @@ source: zellij-server/src/./unit/screen_tests.rs expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())" --- -[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: None, cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled { direction: None, borderless: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit] +[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: None, cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled { direction: None, borderless: Some(false), border_style: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit] diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_line_number.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_line_number.snap index 2866060be..5e9b73444 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_line_number.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_line_number.snap @@ -2,4 +2,4 @@ source: zellij-server/src/./unit/screen_tests.rs expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())" --- -[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: Some(100), cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled { direction: None, borderless: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit] +[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: Some(100), cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled { direction: None, borderless: Some(false), border_style: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit] diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_split_direction.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_split_direction.snap index 036f62bfc..920731822 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_split_direction.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_edit_action_with_split_direction.snap @@ -2,4 +2,4 @@ source: zellij-server/src/./unit/screen_tests.rs expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())" --- -[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: None, cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled { direction: Some(Down), borderless: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit] +[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: None, cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled { direction: Some(Down), borderless: Some(false), border_style: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit] diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_command_and_cwd.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_command_and_cwd.snap index 43dd40d13..e77a4a2b6 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_command_and_cwd.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_command_and_cwd.snap @@ -2,4 +2,4 @@ source: zellij-server/src/./unit/screen_tests.rs expression: "format!(\"{:?}\", new_pane_instruction)" --- -Some(SpawnTerminal(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true, hold_on_start: false, originating_plugin: None, use_terminal_title: false })), None, Tiled { direction: Some(Right), borderless: Some(false) }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false)) +Some(SpawnTerminal(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true, hold_on_start: false, originating_plugin: None, use_terminal_title: false })), None, Tiled { direction: Some(Right), borderless: Some(false), border_style: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false)) diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_default_parameters.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_default_parameters.snap index 433f4d932..88c147f9e 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_default_parameters.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_default_parameters.snap @@ -2,4 +2,4 @@ source: zellij-server/src/./unit/screen_tests.rs expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())" --- -[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(None, None, Tiled { direction: None, borderless: Some(false) }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit] +[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(None, None, Tiled { direction: None, borderless: Some(false), border_style: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit] diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_floating_pane_and_coordinates.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_floating_pane_and_coordinates.snap index f33730b20..085ac9a99 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_floating_pane_and_coordinates.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_floating_pane_and_coordinates.snap @@ -2,4 +2,4 @@ source: zellij-server/src/./unit/screen_tests.rs expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())" --- -[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true, hold_on_start: false, originating_plugin: None, use_terminal_title: false })), None, Floating(Some(FloatingPaneCoordinates { x: Some(Fixed(10)), y: None, width: Some(Percent(20)), height: None, pinned: None, borderless: Some(false) })), false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit] +[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true, hold_on_start: false, originating_plugin: None, use_terminal_title: false })), None, Floating(Some(FloatingPaneCoordinates { x: Some(Fixed(10)), y: None, width: Some(Percent(20)), height: None, pinned: None, borderless: Some(false), border_style: None })), false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit] diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_split_direction.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_split_direction.snap index 1aebde3e7..55cd8cbb3 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_split_direction.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_pane_action_with_split_direction.snap @@ -2,4 +2,4 @@ source: zellij-server/src/./unit/screen_tests.rs expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())" --- -[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(None, None, Tiled { direction: Some(Right), borderless: Some(false) }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit] +[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(None, None, Tiled { direction: Some(Right), borderless: Some(false), border_style: None }, false, ClientId(10), Some(NotificationEnd { channel: None, exit_status: None, unblock_condition: None, affected_pane_id: None, affected_tab_id: None, error_message: None, stdout_message: None }), false), Exit] diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap index aa0e35966..c99cb2d37 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap @@ -1,6 +1,5 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 3515 expression: "format!(\"{:#?}\", new_tab_action)" --- Some( @@ -19,6 +18,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -37,6 +37,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -52,6 +53,7 @@ Some( split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_layout_string.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_layout_string.snap index b12db36c0..6a5bf0f11 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_layout_string.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_layout_string.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -expression: "format!(\"{:#?}\", new_tab_instruction)" +expression: output --- NewTab( None, @@ -21,6 +21,7 @@ NewTab( ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -43,6 +44,7 @@ NewTab( ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -65,6 +67,7 @@ NewTab( ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -84,6 +87,7 @@ NewTab( ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_layout_string_and_name.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_layout_string_and_name.snap index b12db36c0..6a5bf0f11 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_layout_string_and_name.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_layout_string_and_name.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -expression: "format!(\"{:#?}\", new_tab_instruction)" +expression: output --- NewTab( None, @@ -21,6 +21,7 @@ NewTab( ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -43,6 +44,7 @@ NewTab( ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -65,6 +67,7 @@ NewTab( ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -84,6 +87,7 @@ NewTab( ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap index efad1fca0..6a5bf0f11 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap @@ -21,6 +21,7 @@ NewTab( ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -43,6 +44,7 @@ NewTab( ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -65,6 +67,7 @@ NewTab( ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -84,6 +87,7 @@ NewTab( ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_set_pane_border_style_action-2.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_set_pane_border_style_action-2.snap new file mode 100644 index 000000000..79d38b17c --- /dev/null +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_set_pane_border_style_action-2.snap @@ -0,0 +1,14 @@ +--- +source: zellij-server/src/./unit/screen_tests.rs +expression: "format!(\"{}\", snapshot)" +--- +00 (C): ╔ Pane #1 ═════════════════════════════════════════════════════════════════════╗ +01 (C): ║ ║ +02 (C): ║ ║ +03 (C): ║ ║ +04 (C): ║ ║ +05 (C): ║ ║ +06 (C): ║ ║ +07 (C): ║ ║ +08 (C): ║ ║ +09 (C): ╚══════════════════════════════════════════════════════════════════════════════╝ diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_set_pane_border_style_action-3.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_set_pane_border_style_action-3.snap new file mode 100644 index 000000000..45361b56c --- /dev/null +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_set_pane_border_style_action-3.snap @@ -0,0 +1,5 @@ +--- +source: zellij-server/src/./unit/screen_tests.rs +expression: "format!(\"{}\", snapshot_count)" +--- +2 diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_set_pane_border_style_action.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_set_pane_border_style_action.snap new file mode 100644 index 000000000..7b7638500 --- /dev/null +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_set_pane_border_style_action.snap @@ -0,0 +1,14 @@ +--- +source: zellij-server/src/./unit/screen_tests.rs +expression: "format!(\"{}\", snapshot)" +--- +00 (C): ┌ Pane #1 ─────────────────────────────────────────────────────────────────────┐ +01 (C): │ │ +02 (C): │ │ +03 (C): │ │ +04 (C): │ │ +05 (C): │ │ +06 (C): │ │ +07 (C): │ │ +08 (C): │ │ +09 (C): └──────────────────────────────────────────────────────────────────────────────┘ diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs index 941a30703..79aea4093 100644 --- a/zellij-tile/src/shim.rs +++ b/zellij-tile/src/shim.rs @@ -1404,6 +1404,24 @@ pub fn next_swap_layout() { unsafe { host_run_plugin_command() }; } +/// Applies the named tiled [swap layout](https://zellij.dev/documentation/swap-layouts.html) +/// without changing floating pane visibility or focus. Unknown or incompatible names are a no-op. +pub fn apply_tiled_swap_layout(layout_name: &str) { + let plugin_command = PluginCommand::ApplyTiledSwapLayout(layout_name.to_owned()); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Applies the named floating [swap layout](https://zellij.dev/documentation/swap-layouts.html) +/// without changing floating pane visibility or focus. Unknown or incompatible names are a no-op. +pub fn apply_floating_swap_layout(layout_name: &str) { + let plugin_command = PluginCommand::ApplyFloatingSwapLayout(layout_name.to_owned()); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + /// Change focus to the tab with the specified name pub fn go_to_tab_name(tab_name: &str) { let plugin_command = PluginCommand::GoToTabName(tab_name.to_owned()); @@ -2585,6 +2603,13 @@ pub fn set_pane_borderless(pane_id: PaneId, borderless: bool) { unsafe { host_run_plugin_command() }; } +pub fn set_pane_border_style(pane_id: PaneId, border_style: BorderStyleOverride) { + let plugin_command = PluginCommand::SetPaneBorderStyle(pane_id, border_style); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + /// Set the default foreground and/or background color of a pane /// /// # Arguments diff --git a/zellij-utils/assets/config/default.kdl b/zellij-utils/assets/config/default.kdl index 5ec86b2d7..5e5806187 100644 --- a/zellij-utils/assets/config/default.kdl +++ b/zellij-utils/assets/config/default.kdl @@ -312,6 +312,33 @@ load_plugins { // // pane_frame_style "titles" +// Configure the look of the pane frame borders +// +// ui { +// pane_frames { +// // round the corners of single-line borders +// rounded_corners true +// // line style for all four sides of tiled pane frames +// // one of: single, double, heavy, dashed, heavy_dashed +// border_style "single" +// // per-side overrides, eg. a double top border with single sides +// border_top "double" +// border_right "single" +// border_bottom "single" +// border_left "single" +// // when two neighbouring panes disagree about a shared grid line +// // (pane frames off), that line falls back to border_style above +// // the same keys prefixed with "floating_" apply to floating panes, +// // they default to the values above +// floating_border_style "double" +// floating_border_top "double" +// floating_border_right "double" +// floating_border_bottom "double" +// floating_border_left "double" +// floating_border_rounded_corners false +// } +// } + // Toggle between having Zellij lay out panes according to a predefined set of layouts whenever possible // Options: // - true (default) diff --git a/zellij-utils/assets/plugins/about.wasm b/zellij-utils/assets/plugins/about.wasm index 950ba3859..7e34d61cf 100755 Binary files a/zellij-utils/assets/plugins/about.wasm and b/zellij-utils/assets/plugins/about.wasm differ diff --git a/zellij-utils/assets/plugins/compact-bar.wasm b/zellij-utils/assets/plugins/compact-bar.wasm index 581beb47c..287f80ed1 100755 Binary files a/zellij-utils/assets/plugins/compact-bar.wasm and b/zellij-utils/assets/plugins/compact-bar.wasm differ diff --git a/zellij-utils/assets/plugins/configuration.wasm b/zellij-utils/assets/plugins/configuration.wasm index 87f273b15..331f277b7 100755 Binary files a/zellij-utils/assets/plugins/configuration.wasm and b/zellij-utils/assets/plugins/configuration.wasm differ diff --git a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm index 671848372..f852076e3 100755 Binary files a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm and b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm differ diff --git a/zellij-utils/assets/plugins/layout-manager.wasm b/zellij-utils/assets/plugins/layout-manager.wasm index 59055a833..1e55bd1d7 100755 Binary files a/zellij-utils/assets/plugins/layout-manager.wasm and b/zellij-utils/assets/plugins/layout-manager.wasm differ diff --git a/zellij-utils/assets/plugins/link.wasm b/zellij-utils/assets/plugins/link.wasm index fa925fa83..3277169ea 100755 Binary files a/zellij-utils/assets/plugins/link.wasm and b/zellij-utils/assets/plugins/link.wasm differ diff --git a/zellij-utils/assets/plugins/multiple-select.wasm b/zellij-utils/assets/plugins/multiple-select.wasm index b2b084cc5..1530d6f77 100755 Binary files a/zellij-utils/assets/plugins/multiple-select.wasm and b/zellij-utils/assets/plugins/multiple-select.wasm differ diff --git a/zellij-utils/assets/plugins/plugin-manager.wasm b/zellij-utils/assets/plugins/plugin-manager.wasm index bd666969e..a3e143fe4 100755 Binary files a/zellij-utils/assets/plugins/plugin-manager.wasm and b/zellij-utils/assets/plugins/plugin-manager.wasm differ diff --git a/zellij-utils/assets/plugins/session-manager.wasm b/zellij-utils/assets/plugins/session-manager.wasm index 75bcb04a0..b9c9404d2 100755 Binary files a/zellij-utils/assets/plugins/session-manager.wasm and b/zellij-utils/assets/plugins/session-manager.wasm differ diff --git a/zellij-utils/assets/plugins/share.wasm b/zellij-utils/assets/plugins/share.wasm index 46576ab7c..65b57d39a 100755 Binary files a/zellij-utils/assets/plugins/share.wasm and b/zellij-utils/assets/plugins/share.wasm differ diff --git a/zellij-utils/assets/plugins/status-bar.wasm b/zellij-utils/assets/plugins/status-bar.wasm index 526b62bab..5c2849496 100755 Binary files a/zellij-utils/assets/plugins/status-bar.wasm and b/zellij-utils/assets/plugins/status-bar.wasm differ diff --git a/zellij-utils/assets/plugins/strider.wasm b/zellij-utils/assets/plugins/strider.wasm index 54df76682..9a1a05f59 100755 Binary files a/zellij-utils/assets/plugins/strider.wasm and b/zellij-utils/assets/plugins/strider.wasm differ diff --git a/zellij-utils/assets/plugins/tab-bar.wasm b/zellij-utils/assets/plugins/tab-bar.wasm index 9e3a51245..9e23b9de3 100755 Binary files a/zellij-utils/assets/plugins/tab-bar.wasm and b/zellij-utils/assets/plugins/tab-bar.wasm differ diff --git a/zellij-utils/assets/prost/api.action.rs b/zellij-utils/assets/prost/api.action.rs index 761cd6701..5153c4107 100644 --- a/zellij-utils/assets/prost/api.action.rs +++ b/zellij-utils/assets/prost/api.action.rs @@ -158,6 +158,8 @@ pub struct TiledPaneLayout { pub hide_floating_panes: bool, #[prost(string, optional, tag="13")] pub pane_initial_contents: ::core::option::Option<::prost::alloc::string::String>, + #[prost(message, optional, tag="14")] + pub border_style: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -186,6 +188,8 @@ pub struct FloatingPaneLayout { pub logical_position: ::core::option::Option, #[prost(bool, optional, tag="12")] pub borderless: ::core::option::Option, + #[prost(message, optional, tag="13")] + pub border_style: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -303,7 +307,7 @@ pub struct OverrideLayoutPayload { pub struct Action { #[prost(enumeration="ActionName", tag="1")] pub name: i32, - #[prost(oneof="action::OptionalPayload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61")] + #[prost(oneof="action::OptionalPayload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64")] pub optional_payload: ::core::option::Option, } /// Nested message and enum types in `Action`. @@ -425,10 +429,24 @@ pub mod action { AreFloatingPanesVisiblePayload(super::AreFloatingPanesVisiblePayload), #[prost(message, tag="61")] SetPaneFrameStylePayload(super::SetPaneFrameStylePayload), + #[prost(string, tag="62")] + ApplyTiledSwapLayoutPayload(::prost::alloc::string::String), + #[prost(string, tag="63")] + ApplyFloatingSwapLayoutPayload(::prost::alloc::string::String), + #[prost(message, tag="64")] + SetPaneBorderStylePayload(super::SetPaneBorderStylePayload), } } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetPaneBorderStylePayload { + #[prost(message, optional, tag="1")] + pub pane_id: ::core::option::Option, + #[prost(message, optional, tag="2")] + pub border_style: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct SetPaneFrameStylePayload { #[prost(enumeration="super::pane_frame_style::PaneFrameStyle", tag="1")] pub pane_frame_style: i32, @@ -542,6 +560,8 @@ pub struct NewTiledPanePayload { pub near_current_pane: bool, #[prost(bool, optional, tag="4")] pub borderless: ::core::option::Option, + #[prost(message, optional, tag="5")] + pub border_style: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -705,6 +725,8 @@ pub struct FloatingPaneCoordinates { pub pinned: ::core::option::Option, #[prost(bool, optional, tag="6")] pub borderless: ::core::option::Option, + #[prost(message, optional, tag="7")] + pub border_style: ::core::option::Option, } /// SetPaneBorderlessPayload specifies the pane and borderless state #[allow(clippy::derive_partial_eq_without_eq)] @@ -744,6 +766,8 @@ pub mod new_pane_placement { pub struct NoPreferenceOptions { #[prost(bool, optional, tag="1")] pub borderless: ::core::option::Option, + #[prost(message, optional, tag="2")] + pub border_style: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -752,6 +776,8 @@ pub struct TiledPlacement { pub direction: ::core::option::Option, #[prost(bool, optional, tag="2")] pub borderless: ::core::option::Option, + #[prost(message, optional, tag="3")] + pub border_style: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -768,6 +794,8 @@ pub struct InPlaceConfig { pub close_replaced_pane: bool, #[prost(bool, optional, tag="3")] pub borderless: ::core::option::Option, + #[prost(message, optional, tag="4")] + pub border_style: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -776,6 +804,8 @@ pub struct StackedPlacement { pub pane_id: ::core::option::Option, #[prost(bool, optional, tag="2")] pub borderless: ::core::option::Option, + #[prost(message, optional, tag="3")] + pub border_style: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -1143,6 +1173,9 @@ pub enum ActionName { ScrollToNextPrompt = 111, SelectCommandAtScrollPosition = 112, CopyLastCommandOutput = 113, + ApplyTiledSwapLayout = 114, + ApplyFloatingSwapLayout = 115, + SetPaneBorderStyle = 116, } impl ActionName { /// String value of the enum field names used in the ProtoBuf definition. @@ -1262,6 +1295,9 @@ impl ActionName { ActionName::ScrollToNextPrompt => "ScrollToNextPrompt", ActionName::SelectCommandAtScrollPosition => "SelectCommandAtScrollPosition", ActionName::CopyLastCommandOutput => "CopyLastCommandOutput", + ActionName::ApplyTiledSwapLayout => "ApplyTiledSwapLayout", + ActionName::ApplyFloatingSwapLayout => "ApplyFloatingSwapLayout", + ActionName::SetPaneBorderStyle => "SetPaneBorderStyle", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1378,6 +1414,9 @@ impl ActionName { "ScrollToNextPrompt" => Some(Self::ScrollToNextPrompt), "SelectCommandAtScrollPosition" => Some(Self::SelectCommandAtScrollPosition), "CopyLastCommandOutput" => Some(Self::CopyLastCommandOutput), + "ApplyTiledSwapLayout" => Some(Self::ApplyTiledSwapLayout), + "ApplyFloatingSwapLayout" => Some(Self::ApplyFloatingSwapLayout), + "SetPaneBorderStyle" => Some(Self::SetPaneBorderStyle), _ => None, } } diff --git a/zellij-utils/assets/prost/api.command.rs b/zellij-utils/assets/prost/api.command.rs index bbb66d4ae..e130f97f9 100644 --- a/zellij-utils/assets/prost/api.command.rs +++ b/zellij-utils/assets/prost/api.command.rs @@ -8,4 +8,6 @@ pub struct Command { pub args: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, #[prost(string, optional, tag="3")] pub cwd: ::core::option::Option<::prost::alloc::string::String>, + #[prost(message, optional, tag="4")] + pub border_style: ::core::option::Option, } diff --git a/zellij-utils/assets/prost/api.event.rs b/zellij-utils/assets/prost/api.event.rs index bd025f1ac..945b7bd6b 100644 --- a/zellij-utils/assets/prost/api.event.rs +++ b/zellij-utils/assets/prost/api.event.rs @@ -666,6 +666,8 @@ pub struct TabInfo { pub has_bell_notification: bool, #[prost(bool, tag="19")] pub is_flashing_bell: bool, + #[prost(uint32, repeated, tag="20")] + pub other_focused_client_slots: ::prost::alloc::vec::Vec, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/zellij-utils/assets/prost/api.file.rs b/zellij-utils/assets/prost/api.file.rs index a87d0e7e0..8846ad407 100644 --- a/zellij-utils/assets/prost/api.file.rs +++ b/zellij-utils/assets/prost/api.file.rs @@ -8,4 +8,6 @@ pub struct File { pub line_number: ::core::option::Option, #[prost(string, optional, tag="3")] pub cwd: ::core::option::Option<::prost::alloc::string::String>, + #[prost(message, optional, tag="4")] + pub border_style: ::core::option::Option, } diff --git a/zellij-utils/assets/prost/api.plugin_command.rs b/zellij-utils/assets/prost/api.plugin_command.rs index 5166e82f5..6c0864734 100644 --- a/zellij-utils/assets/prost/api.plugin_command.rs +++ b/zellij-utils/assets/prost/api.plugin_command.rs @@ -4,7 +4,7 @@ pub struct PluginCommand { #[prost(enumeration="CommandName", tag="1")] pub name: i32, - #[prost(oneof="plugin_command::Payload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 168, 169, 170, 171")] + #[prost(oneof="plugin_command::Payload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 168, 169, 170, 171, 172, 173, 174")] pub payload: ::core::option::Option, } /// Nested message and enum types in `PluginCommand`. @@ -320,6 +320,12 @@ pub mod plugin_command { SetPaneFrameStylePayload(super::SetPaneFrameStylePayload), #[prost(message, tag="171")] ToggleFloatingPanesPayload(super::ToggleFloatingPanesPayload), + #[prost(string, tag="172")] + ApplyTiledSwapLayoutPayload(::prost::alloc::string::String), + #[prost(string, tag="173")] + ApplyFloatingSwapLayoutPayload(::prost::alloc::string::String), + #[prost(message, tag="174")] + SetPaneBorderStylePayload(super::SetPaneBorderStylePayload), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -570,6 +576,14 @@ pub struct SetPaneBorderlessPayload { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetPaneBorderStylePayload { + #[prost(message, optional, tag="1")] + pub pane_id: ::core::option::Option, + #[prost(message, optional, tag="2")] + pub border_style: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ChangeHostFolderPayload { #[prost(string, tag="1")] pub new_host_folder: ::prost::alloc::string::String, @@ -1002,6 +1016,8 @@ pub struct FloatingPaneCoordinates { pub pinned: ::core::option::Option, #[prost(bool, optional, tag="6")] pub borderless: ::core::option::Option, + #[prost(message, optional, tag="7")] + pub border_style: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -2227,6 +2243,9 @@ pub enum CommandName { NewPane = 226, ToggleFocusNoUiFullscreen = 227, FocusHostSession = 228, + ApplyTiledSwapLayout = 229, + ApplyFloatingSwapLayout = 230, + SetPaneBorderStyle = 231, } impl CommandName { /// String value of the enum field names used in the ProtoBuf definition. @@ -2438,6 +2457,9 @@ impl CommandName { CommandName::NewPane => "NewPane", CommandName::ToggleFocusNoUiFullscreen => "ToggleFocusNoUiFullscreen", CommandName::FocusHostSession => "FocusHostSession", + CommandName::ApplyTiledSwapLayout => "ApplyTiledSwapLayout", + CommandName::ApplyFloatingSwapLayout => "ApplyFloatingSwapLayout", + CommandName::SetPaneBorderStyle => "SetPaneBorderStyle", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -2646,6 +2668,9 @@ impl CommandName { "NewPane" => Some(Self::NewPane), "ToggleFocusNoUiFullscreen" => Some(Self::ToggleFocusNoUiFullscreen), "FocusHostSession" => Some(Self::FocusHostSession), + "ApplyTiledSwapLayout" => Some(Self::ApplyTiledSwapLayout), + "ApplyFloatingSwapLayout" => Some(Self::ApplyFloatingSwapLayout), + "SetPaneBorderStyle" => Some(Self::SetPaneBorderStyle), _ => None, } } diff --git a/zellij-utils/assets/prost/api.style.rs b/zellij-utils/assets/prost/api.style.rs index 8329d8f08..71ce9fb9d 100644 --- a/zellij-utils/assets/prost/api.style.rs +++ b/zellij-utils/assets/prost/api.style.rs @@ -11,6 +11,40 @@ pub struct Style { pub hide_session_name: bool, #[prost(message, optional, tag="4")] pub styling: ::core::option::Option, + #[prost(message, optional, tag="5")] + pub border_style: ::core::option::Option, + #[prost(message, optional, tag="6")] + pub floating_border_style: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BorderStyle { + #[prost(enumeration="LineStyle", tag="1")] + pub top: i32, + #[prost(enumeration="LineStyle", tag="2")] + pub right: i32, + #[prost(enumeration="LineStyle", tag="3")] + pub bottom: i32, + #[prost(enumeration="LineStyle", tag="4")] + pub left: i32, + #[prost(bool, tag="5")] + pub rounded_corners: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BorderStyleOverride { + #[prost(enumeration="LineStyle", optional, tag="1")] + pub all: ::core::option::Option, + #[prost(enumeration="LineStyle", optional, tag="2")] + pub top: ::core::option::Option, + #[prost(enumeration="LineStyle", optional, tag="3")] + pub right: ::core::option::Option, + #[prost(enumeration="LineStyle", optional, tag="4")] + pub bottom: ::core::option::Option, + #[prost(enumeration="LineStyle", optional, tag="5")] + pub left: ::core::option::Option, + #[prost(bool, optional, tag="6")] + pub rounded_corners: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -117,6 +151,41 @@ pub struct Styling { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] +pub enum LineStyle { + Single = 0, + Double = 1, + Heavy = 2, + Dashed = 3, + HeavyDashed = 4, +} +impl LineStyle { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + LineStyle::Single => "Single", + LineStyle::Double => "Double", + LineStyle::Heavy => "Heavy", + LineStyle::Dashed => "Dashed", + LineStyle::HeavyDashed => "HeavyDashed", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "Single" => Some(Self::Single), + "Double" => Some(Self::Double), + "Heavy" => Some(Self::Heavy), + "Dashed" => Some(Self::Dashed), + "HeavyDashed" => Some(Self::HeavyDashed), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] pub enum ColorType { Rgb = 0, EightBit = 1, diff --git a/zellij-utils/assets/prost_ipc/client_server_contract.rs b/zellij-utils/assets/prost_ipc/client_server_contract.rs index a50f86a13..98104b5e7 100644 --- a/zellij-utils/assets/prost_ipc/client_server_contract.rs +++ b/zellij-utils/assets/prost_ipc/client_server_contract.rs @@ -71,6 +71,40 @@ pub struct Style { pub rounded_corners: bool, #[prost(bool, tag="3")] pub hide_session_name: bool, + #[prost(message, optional, tag="4")] + pub border_style: ::core::option::Option, + #[prost(message, optional, tag="5")] + pub floating_border_style: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BorderStyle { + #[prost(enumeration="LineStyle", tag="1")] + pub top: i32, + #[prost(enumeration="LineStyle", tag="2")] + pub right: i32, + #[prost(enumeration="LineStyle", tag="3")] + pub bottom: i32, + #[prost(enumeration="LineStyle", tag="4")] + pub left: i32, + #[prost(bool, tag="5")] + pub rounded_corners: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BorderStyleOverride { + #[prost(enumeration="LineStyle", optional, tag="1")] + pub all: ::core::option::Option, + #[prost(enumeration="LineStyle", optional, tag="2")] + pub top: ::core::option::Option, + #[prost(enumeration="LineStyle", optional, tag="3")] + pub right: ::core::option::Option, + #[prost(enumeration="LineStyle", optional, tag="4")] + pub bottom: ::core::option::Option, + #[prost(enumeration="LineStyle", optional, tag="5")] + pub left: ::core::option::Option, + #[prost(bool, optional, tag="6")] + pub rounded_corners: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -120,7 +154,7 @@ pub struct RgbColor { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Action { - #[prost(oneof="action::ActionType", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151")] + #[prost(oneof="action::ActionType", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 156, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155")] pub action_type: ::core::option::Option, } /// Nested message and enum types in `Action`. @@ -320,6 +354,8 @@ pub mod action { TogglePaneBorderless(super::TogglePaneBorderlessAction), #[prost(message, tag="96")] SetPaneBorderless(super::SetPaneBorderlessAction), + #[prost(message, tag="156")] + SetPaneBorderStyle(super::SetPaneBorderStyleAction), #[prost(message, tag="97")] SaveSession(super::SaveSessionAction), #[prost(message, tag="98")] @@ -430,6 +466,14 @@ pub mod action { SelectCommandAtScrollPosition(super::SelectCommandAtScrollPositionAction), #[prost(message, tag="151")] CopyLastCommandOutput(super::CopyLastCommandOutputAction), + #[prost(message, tag="152")] + ApplyTiledSwapLayout(super::ApplyTiledSwapLayoutAction), + #[prost(message, tag="153")] + ApplyFloatingSwapLayout(super::ApplyFloatingSwapLayoutAction), + #[prost(message, tag="154")] + ApplyTiledSwapLayoutByTabId(super::ApplyTiledSwapLayoutByTabIdAction), + #[prost(message, tag="155")] + ApplyFloatingSwapLayoutByTabId(super::ApplyFloatingSwapLayoutByTabIdAction), } } // Action message definitions (all 92 variants) @@ -659,6 +703,18 @@ pub struct NextSwapLayoutAction { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct ApplyTiledSwapLayoutAction { + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ApplyFloatingSwapLayoutAction { + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct TabLayoutInfo { #[prost(uint32, tag="1")] pub tab_index: u32, @@ -929,6 +985,8 @@ pub struct NewTiledPaneAction { pub tab_id: ::core::option::Option, #[prost(bool, tag="10")] pub no_focus: bool, + #[prost(message, optional, tag="11")] + pub border_style: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -1346,6 +1404,14 @@ pub struct SetPaneColorAction { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetPaneBorderStyleAction { + #[prost(message, optional, tag="1")] + pub pane_id: ::core::option::Option, + #[prost(message, optional, tag="2")] + pub border_style: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Position { /// Changed from uint32 to int32 to support negative line numbers #[prost(int32, tag="1")] @@ -1498,6 +1564,8 @@ pub mod new_pane_placement { pub struct NoPreferencePlacement { #[prost(bool, optional, tag="1")] pub borderless: ::core::option::Option, + #[prost(message, optional, tag="2")] + pub border_style: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -1506,6 +1574,8 @@ pub struct TiledPlacement { pub direction: ::core::option::Option, #[prost(bool, optional, tag="2")] pub borderless: ::core::option::Option, + #[prost(message, optional, tag="3")] + pub border_style: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -1514,6 +1584,8 @@ pub struct StackedPlacement { pub pane_id_to_stack_under: ::core::option::Option, #[prost(bool, optional, tag="2")] pub borderless: ::core::option::Option, + #[prost(message, optional, tag="3")] + pub border_style: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -1524,6 +1596,8 @@ pub struct NewPanePlacementInPlace { pub close_replaced_pane: bool, #[prost(bool, optional, tag="3")] pub borderless: ::core::option::Option, + #[prost(message, optional, tag="4")] + pub border_style: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -1562,6 +1636,8 @@ pub struct FloatingPaneCoordinates { pub pinned: ::core::option::Option, #[prost(bool, optional, tag="6")] pub borderless: ::core::option::Option, + #[prost(message, optional, tag="7")] + pub border_style: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -1636,9 +1712,11 @@ pub struct TiledPaneLayout { pub pane_initial_contents: ::core::option::Option<::prost::alloc::string::String>, #[prost(string, optional, tag="14")] pub default_fg: ::core::option::Option<::prost::alloc::string::String>, - /// NOTE: run_instructions_to_ignore is not represented here because it's a field used only inside the server itself and not part of the server/client contract #[prost(string, optional, tag="15")] pub default_bg: ::core::option::Option<::prost::alloc::string::String>, + /// NOTE: run_instructions_to_ignore is not represented here because it's a field used only inside the server itself and not part of the server/client contract + #[prost(message, optional, tag="16")] + pub border_style: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -1671,6 +1749,8 @@ pub struct FloatingPaneLayout { pub default_fg: ::core::option::Option<::prost::alloc::string::String>, #[prost(string, optional, tag="14")] pub default_bg: ::core::option::Option<::prost::alloc::string::String>, + #[prost(message, optional, tag="15")] + pub border_style: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -2237,6 +2317,22 @@ pub struct NextSwapLayoutByTabIdAction { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct ApplyTiledSwapLayoutByTabIdAction { + #[prost(uint64, tag="1")] + pub id: u64, + #[prost(string, tag="2")] + pub name: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ApplyFloatingSwapLayoutByTabIdAction { + #[prost(uint64, tag="1")] + pub id: u64, + #[prost(string, tag="2")] + pub name: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct MoveTabByTabIdAction { #[prost(uint64, tag="1")] pub id: u64, @@ -2402,6 +2498,41 @@ impl KeyModifier { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] +pub enum LineStyle { + Single = 0, + Double = 1, + Heavy = 2, + Dashed = 3, + HeavyDashed = 4, +} +impl LineStyle { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + LineStyle::Single => "LINE_STYLE_SINGLE", + LineStyle::Double => "LINE_STYLE_DOUBLE", + LineStyle::Heavy => "LINE_STYLE_HEAVY", + LineStyle::Dashed => "LINE_STYLE_DASHED", + LineStyle::HeavyDashed => "LINE_STYLE_HEAVY_DASHED", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "LINE_STYLE_SINGLE" => Some(Self::Single), + "LINE_STYLE_DOUBLE" => Some(Self::Double), + "LINE_STYLE_HEAVY" => Some(Self::Heavy), + "LINE_STYLE_DASHED" => Some(Self::Dashed), + "LINE_STYLE_HEAVY_DASHED" => Some(Self::HeavyDashed), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] pub enum AnsiCode { Unspecified = 0, Black = 1, diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs index a05a0e2bb..93ac23ab7 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -124,6 +124,35 @@ impl CliArgs { } None } + /// The clap command, named after the running distribution rather than after Zellij + pub fn command_for_distribution() -> clap::Command { + use clap::CommandFactory; + let distribution = crate::distribution::distribution(); + let command = CliArgs::command() + .name(distribution.name) + .bin_name(distribution.name); + if distribution.is_stock_zellij() { + command + } else { + static VERSION_STRING: std::sync::OnceLock = std::sync::OnceLock::new(); + let version = VERSION_STRING.get_or_init(|| { + format!( + "{} (zellij {})", + distribution.version, + crate::consts::VERSION + ) + }); + command.version(version.as_str()) + } + } + pub fn parse_for_distribution() -> Self { + use clap::FromArgMatches; + let matches = CliArgs::command_for_distribution().get_matches(); + match CliArgs::from_arg_matches(&matches) { + Ok(cli_args) => cli_args, + Err(e) => e.exit(), + } + } } #[derive(Debug, Subcommand, Clone, Serialize, Deserialize)] @@ -537,6 +566,9 @@ pub enum Sessions { conflicts_with("in_place") )] tab_id: Option, + /// Border line style, eg. "double" or "top:double,left:single,rounded" + #[clap(long, value_parser)] + border_style: Option, }, /// Load a plugin /// Returns: Created pane ID (format: plugin_) @@ -592,6 +624,9 @@ pub enum Sessions { /// Target a specific tab by ID #[clap(long, value_parser, conflicts_with("in_place"))] tab_id: Option, + /// Border line style, eg. "double" or "top:double,left:single,rounded" + #[clap(long, value_parser)] + border_style: Option, }, /// Edit file with default $EDITOR / $VISUAL /// Returns: Created pane ID (format: terminal_) @@ -657,6 +692,9 @@ pub enum Sessions { conflicts_with("in_place") )] tab_id: Option, + /// Border line style, eg. "double" or "top:double,left:single,rounded" + #[clap(long, value_parser)] + border_style: Option, }, /// Send data to one or more plugins, launch them if they are not running. #[clap(override_usage( @@ -1014,6 +1052,9 @@ pub enum CliAction { conflicts_with("in_place") )] tab_id: Option, + /// Border line style, eg. "double" or "top:double,left:single,rounded" + #[clap(long, value_parser)] + border_style: Option, }, /// Open the specified file in a new zellij pane with your default EDITOR /// Returns: Created pane ID (format: terminal_) @@ -1078,6 +1119,9 @@ pub enum CliAction { conflicts_with("in_place") )] tab_id: Option, + /// Border line style, eg. "double" or "top:double,left:single,rounded" + #[clap(long, value_parser)] + border_style: Option, }, /// Switch input mode of all connected clients [locked|pane|tab|resize|move|search|session] SwitchMode { @@ -1275,6 +1319,30 @@ pub enum CliAction { #[clap(short, long, value_parser)] tab_id: Option, }, + /// Apply the tiled swap layout with the given name, leaving floating pane visibility and focus + /// untouched + /// + /// Exits with status 1 if no tiled swap layout with that name fits the current pane count + ApplyTiledSwapLayout { + /// The name of the tiled swap layout, as declared in the layout file + #[clap(value_parser)] + name: String, + /// Target a specific tab by ID + #[clap(short, long, value_parser)] + tab_id: Option, + }, + /// Apply the floating swap layout with the given name, leaving floating pane visibility and + /// focus untouched + /// + /// Exits with status 1 if no floating swap layout with that name fits the current pane count + ApplyFloatingSwapLayout { + /// The name of the floating swap layout, as declared in the layout file + #[clap(value_parser)] + name: String, + /// Target a specific tab by ID + #[clap(short, long, value_parser)] + tab_id: Option, + }, /// Override the layout of the active tab OverrideLayout { /// Path to the layout file @@ -1525,6 +1593,9 @@ tail -f /tmp/my-live-logfile | zellij action pipe --name logs --plugin https://e /// mouse if without a border) #[clap(short, long, value_parser)] borderless: Option, + /// Border line style, eg. "double" or "top:double,left:single,rounded" + #[clap(long, value_parser)] + border_style: Option, }, TogglePaneBorderless { /// The pane_id of the pane, eg. terminal_1, plugin_2 or 3 (equivalent to terminal_3) @@ -1539,6 +1610,15 @@ tail -f /tmp/my-live-logfile | zellij action pipe --name logs --plugin https://e #[clap(short, long, value_parser)] borderless: bool, }, + /// Change the border line style of an existing pane + SetPaneBorderStyle { + /// The pane_id of the pane, eg. terminal_1, plugin_2 or 3 (equivalent to terminal_3) + #[clap(short, long, value_parser)] + pane_id: String, + /// Border line style, eg. "double" or "top:double,left:single,rounded" + #[clap(long, value_parser)] + border_style: Option, + }, /// Detach from the current session Detach, /// Switch the theme to dark (uses configured `theme_dark`). @@ -1593,10 +1673,25 @@ mod tests { use super::*; use clap::Parser; + const CLI_PARSE_TEST_STACK_SIZE: usize = 16 * 1024 * 1024; + + fn on_large_stack(f: impl FnOnce() -> T + Send + 'static) -> T { + std::thread::Builder::new() + .stack_size(CLI_PARSE_TEST_STACK_SIZE) + .spawn(f) + .expect("failed to spawn cli parse test thread") + .join() + .expect("cli parse test thread panicked") + } + + fn try_parse(args: &[&str]) -> Result { + let mut full_args = vec!["zellij".to_owned()]; + full_args.extend(args.iter().map(|arg| arg.to_string())); + on_large_stack(move || CliArgs::try_parse_from(full_args)) + } + fn parse_subscribe(args: &[&str]) -> SubscribeCli { - let mut full_args = vec!["zellij"]; - full_args.extend_from_slice(args); - let cli = CliArgs::try_parse_from(full_args).unwrap(); + let cli = try_parse(args).unwrap(); match cli.command { Some(Command::Subscribe(s)) => s, other => panic!("Expected Subscribe, got {:?}", other), @@ -1656,7 +1751,7 @@ mod tests { #[test] fn subscribe_requires_pane_id() { - let result = CliArgs::try_parse_from(["zellij", "subscribe"]); + let result = try_parse(&["subscribe"]); assert!(result.is_err()); } } diff --git a/zellij-utils/src/client_server_contract/common_types.proto b/zellij-utils/src/client_server_contract/common_types.proto index eb184748d..d792109f5 100644 --- a/zellij-utils/src/client_server_contract/common_types.proto +++ b/zellij-utils/src/client_server_contract/common_types.proto @@ -89,6 +89,33 @@ message Style { Styling colors = 1; bool rounded_corners = 2; bool hide_session_name = 3; + BorderStyle border_style = 4; + BorderStyle floating_border_style = 5; +} + +enum LineStyle { + LINE_STYLE_SINGLE = 0; + LINE_STYLE_DOUBLE = 1; + LINE_STYLE_HEAVY = 2; + LINE_STYLE_DASHED = 3; + LINE_STYLE_HEAVY_DASHED = 4; +} + +message BorderStyle { + LineStyle top = 1; + LineStyle right = 2; + LineStyle bottom = 3; + LineStyle left = 4; + bool rounded_corners = 5; +} + +message BorderStyleOverride { + optional LineStyle all = 1; + optional LineStyle top = 2; + optional LineStyle right = 3; + optional LineStyle bottom = 4; + optional LineStyle left = 5; + optional bool rounded_corners = 6; } message Styling { @@ -233,6 +260,7 @@ message Action { OverrideLayoutAction override_layout = 94; TogglePaneBorderlessAction toggle_pane_borderless = 95; SetPaneBorderlessAction set_pane_borderless = 96; + SetPaneBorderStyleAction set_pane_border_style = 156; SaveSessionAction save_session = 97; ListPanesAction list_panes = 98; WriteCharsToPaneIdAction write_chars_to_pane_id = 99; @@ -289,6 +317,10 @@ message Action { ScrollToNextPromptAction scroll_to_next_prompt = 149; SelectCommandAtScrollPositionAction select_command_at_scroll_position = 150; CopyLastCommandOutputAction copy_last_command_output = 151; + ApplyTiledSwapLayoutAction apply_tiled_swap_layout = 152; + ApplyFloatingSwapLayoutAction apply_floating_swap_layout = 153; + ApplyTiledSwapLayoutByTabIdAction apply_tiled_swap_layout_by_tab_id = 154; + ApplyFloatingSwapLayoutByTabIdAction apply_floating_swap_layout_by_tab_id = 155; } } @@ -358,6 +390,8 @@ message DenyAction {} message ToggleMouseModeAction {} message PreviousSwapLayoutAction {} message NextSwapLayoutAction {} +message ApplyTiledSwapLayoutAction { string name = 1; } +message ApplyFloatingSwapLayoutAction { string name = 1; } message TabLayoutInfo { uint32 tab_index = 1; optional string tab_name = 2; @@ -505,6 +539,7 @@ message NewTiledPaneAction { optional bool borderless = 8; optional uint32 tab_id = 9; bool no_focus = 10; + optional BorderStyleOverride border_style = 11; } message NewInPlacePaneAction { @@ -753,6 +788,11 @@ message SetPaneColorAction { optional string bg = 3; } +message SetPaneBorderStyleAction { + PaneId pane_id = 1; + BorderStyleOverride border_style = 2; +} + // Supporting enums and messages enum InputMode { INPUT_MODE_UNSPECIFIED = 0; @@ -874,22 +914,26 @@ message NewPanePlacement { message NoPreferencePlacement { optional bool borderless = 1; + optional BorderStyleOverride border_style = 2; } message TiledPlacement { optional Direction direction = 1; optional bool borderless = 2; + optional BorderStyleOverride border_style = 3; } message StackedPlacement { optional PaneId pane_id_to_stack_under = 1; optional bool borderless = 2; + optional BorderStyleOverride border_style = 3; } message NewPanePlacementInPlace { optional PaneId pane_id_to_replace = 1; bool close_replaced_pane = 2; optional bool borderless = 3; + optional BorderStyleOverride border_style = 4; } message PaneIdWithPlugin { @@ -926,6 +970,7 @@ message FloatingPaneCoordinates { optional FloatingCoordinate height = 4; optional bool pinned = 5; optional bool borderless = 6; + optional BorderStyleOverride border_style = 7; } message FloatingCoordinate { @@ -962,6 +1007,7 @@ message TiledPaneLayout { optional string pane_initial_contents = 13; optional string default_fg = 14; optional string default_bg = 15; + optional BorderStyleOverride border_style = 16; // NOTE: run_instructions_to_ignore is not represented here because it's a field used only inside the server itself and not part of the server/client contract } @@ -980,6 +1026,7 @@ message FloatingPaneLayout { optional bool borderless = 12; optional string default_fg = 13; optional string default_bg = 14; + optional BorderStyleOverride border_style = 15; } message SwapTiledLayout { @@ -1300,4 +1347,6 @@ message ToggleActiveSyncTabByTabIdAction { uint64 id = 1; } message ToggleFloatingPanesByTabIdAction { uint64 id = 1; } message PreviousSwapLayoutByTabIdAction { uint64 id = 1; } message NextSwapLayoutByTabIdAction { uint64 id = 1; } +message ApplyTiledSwapLayoutByTabIdAction { uint64 id = 1; string name = 2; } +message ApplyFloatingSwapLayoutByTabIdAction { uint64 id = 1; string name = 2; } message MoveTabByTabIdAction { uint64 id = 1; Direction direction = 2; } diff --git a/zellij-utils/src/consts.rs b/zellij-utils/src/consts.rs index 98d6e5a50..54cc7798f 100644 --- a/zellij-utils/src/consts.rs +++ b/zellij-utils/src/consts.rs @@ -15,10 +15,18 @@ pub const DEFAULT_SCROLL_BUFFER_SIZE: usize = 10_000; pub static SCROLL_BUFFER_SIZE: OnceLock = OnceLock::new(); pub static DEBUG_MODE: OnceLock = OnceLock::new(); +/// System-wide configuration directory, named after the running distribution +/// (`/etc/zellij`, `C:\ProgramData\Zellij`) #[cfg(not(windows))] -pub const SYSTEM_DEFAULT_CONFIG_DIR: &str = "/etc/zellij"; +pub fn system_default_config_dir() -> PathBuf { + PathBuf::from("/etc").join(crate::distribution::name()) +} + #[cfg(windows)] -pub const SYSTEM_DEFAULT_CONFIG_DIR: &str = "C:\\ProgramData\\Zellij"; +pub fn system_default_config_dir() -> PathBuf { + PathBuf::from("C:\\ProgramData").join(crate::distribution::display_name()) +} + 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"); @@ -90,11 +98,18 @@ lazy_static! { pub static ref CLIENT_SERVER_CONTRACT_DIR: String = format!("contract_version_{}", CLIENT_SERVER_CONTRACT_VERSION); pub static ref ZELLIJ_PROJ_DIR: ProjectDirs = { - if cfg!(windows) { - ProjectDirs::from("", "", "Zellij").unwrap() - } else { - ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap() - } + let (qualifier, organization, application) = + crate::distribution::distribution().project_dirs(); + ProjectDirs::from(qualifier, organization, application).unwrap_or_else(|| { + panic!( + "could not determine the platform directories for distribution '{}' \ + (qualifier: {:?}, organization: {:?}, application: {:?})", + crate::distribution::name(), + qualifier, + organization, + application + ) + }) }; pub static ref ZELLIJ_CACHE_DIR: PathBuf = ZELLIJ_PROJ_DIR.cache_dir().to_path_buf(); pub static ref ZELLIJ_SESSION_CACHE_DIR: PathBuf = ZELLIJ_PROJ_DIR @@ -116,114 +131,6 @@ pub const FEATURES: &[&str] = &[ "disable_automatic_asset_installation", ]; -pub const BUILTIN_PLUGIN_NAMES: &[&str] = &[ - "compact-bar", - "status-bar", - "tab-bar", - "strider", - "session-manager", - "configuration", - "plugin-manager", - "about", - "share", - "multiple-select", - "layout-manager", - "link", -]; - -#[cfg(not(target_family = "wasm"))] -pub use not_wasm::*; - -#[cfg(not(target_family = "wasm"))] -mod not_wasm { - use lazy_static::lazy_static; - use std::collections::HashMap; - use std::path::PathBuf; - - // Convenience macro to add plugins to the asset map (see `ASSET_MAP`) - // - // Plugins are taken from: - // - // - `zellij-utils/assets/plugins`: When building in release mode OR when the - // `plugins_from_target` feature IS NOT set - // - `zellij-utils/../target/wasm32-wasip1/debug`: When building in debug mode AND the - // `plugins_from_target` feature IS set - // - // When the `disable_automatic_asset_installation` feature is set, no plugins are embedded at - // all and `ASSET_MAP` is empty. Builtin plugins must then be provided in the plugin directory. - #[cfg(not(feature = "disable_automatic_asset_installation"))] - macro_rules! add_plugin { - ($assets:expr, $plugin:literal) => { - $assets.insert( - PathBuf::from("plugins").join($plugin), - #[cfg(any(not(feature = "plugins_from_target"), not(debug_assertions)))] - include_bytes!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/assets/plugins/", - $plugin - )), - #[cfg(all(feature = "plugins_from_target", debug_assertions))] - include_bytes!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/../target/wasm32-wasip1/debug/", - $plugin - )), - ); - }; - } - - lazy_static! { - // Zellij asset map - pub static ref ASSET_MAP: HashMap = { - #[allow(unused_mut)] - let mut assets: HashMap = std::collections::HashMap::new(); - #[cfg(not(feature = "disable_automatic_asset_installation"))] - { - add_plugin!(assets, "compact-bar.wasm"); - add_plugin!(assets, "status-bar.wasm"); - add_plugin!(assets, "tab-bar.wasm"); - add_plugin!(assets, "strider.wasm"); - add_plugin!(assets, "session-manager.wasm"); - add_plugin!(assets, "configuration.wasm"); - add_plugin!(assets, "plugin-manager.wasm"); - add_plugin!(assets, "about.wasm"); - add_plugin!(assets, "share.wasm"); - add_plugin!(assets, "multiple-select.wasm"); - add_plugin!(assets, "layout-manager.wasm"); - add_plugin!(assets, "link.wasm"); - } - assets - }; - } - - #[cfg(all(test, not(feature = "disable_automatic_asset_installation")))] - mod asset_map_test { - use super::ASSET_MAP; - use crate::consts::BUILTIN_PLUGIN_NAMES; - use std::path::PathBuf; - - #[test] - fn asset_map_matches_builtin_plugin_names() { - let mut embedded: Vec = ASSET_MAP - .keys() - .filter_map(|path| { - path.file_stem() - .map(|stem| stem.to_string_lossy().to_string()) - }) - .collect(); - let mut expected: Vec = - BUILTIN_PLUGIN_NAMES.iter().map(|s| s.to_string()).collect(); - embedded.sort(); - expected.sort(); - assert_eq!(embedded, expected); - for name in BUILTIN_PLUGIN_NAMES { - assert!(ASSET_MAP - .contains_key(&PathBuf::from("plugins").join(format!("{}.wasm", name)))); - } - } - } -} - #[cfg(unix)] pub fn is_ipc_socket(file_type: &std::fs::FileType) -> bool { use std::os::unix::fs::FileTypeExt; @@ -328,9 +235,12 @@ mod unix_only { lazy_static! { static ref UID: Uid = Uid::current(); - pub static ref ZELLIJ_TMP_DIR: PathBuf = temp_dir().join(format!("zellij-{}", *UID)); - pub static ref ZELLIJ_TMP_LOG_DIR: PathBuf = ZELLIJ_TMP_DIR.join("zellij-log"); - pub static ref ZELLIJ_TMP_LOG_FILE: PathBuf = ZELLIJ_TMP_LOG_DIR.join("zellij.log"); + pub static ref ZELLIJ_TMP_DIR: PathBuf = + temp_dir().join(format!("{}-{}", crate::distribution::name(), *UID)); + pub static ref ZELLIJ_TMP_LOG_DIR: PathBuf = + ZELLIJ_TMP_DIR.join(format!("{}-log", crate::distribution::name())); + pub static ref ZELLIJ_TMP_LOG_FILE: PathBuf = + ZELLIJ_TMP_LOG_DIR.join(format!("{}.log", crate::distribution::name())); pub static ref ZELLIJ_SOCK_DIR: PathBuf = { let mut ipc_dir = envs::get_socket_dir().map_or_else( |_| { @@ -375,10 +285,12 @@ mod not_unix { lazy_static! { pub static ref ZELLIJ_TMP_DIR: PathBuf = { let tmp_dir = canonicalize_path(temp_dir()); - tmp_dir.join("zellij") + tmp_dir.join(crate::distribution::name()) }; - pub static ref ZELLIJ_TMP_LOG_DIR: PathBuf = ZELLIJ_TMP_DIR.join("zellij-log"); - pub static ref ZELLIJ_TMP_LOG_FILE: PathBuf = ZELLIJ_TMP_LOG_DIR.join("zellij.log"); + pub static ref ZELLIJ_TMP_LOG_DIR: PathBuf = + ZELLIJ_TMP_DIR.join(format!("{}-log", crate::distribution::name())); + pub static ref ZELLIJ_TMP_LOG_FILE: PathBuf = + ZELLIJ_TMP_LOG_DIR.join(format!("{}.log", crate::distribution::name())); pub static ref ZELLIJ_SOCK_DIR: PathBuf = { let mut ipc_dir = canonicalize_path(envs::get_socket_dir().map_or_else( |_| { diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index b3c84bd73..45b15fcb3 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -28,7 +28,7 @@ use crate::vendored::termwiz::{ input::{KeyCode, KeyCodeEncodeModes, KeyboardEncoding, Modifiers}, }; -pub type ClientId = u16; // TODO: merge with crate type? +pub type ClientId = u32; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum UnblockCondition { @@ -64,13 +64,13 @@ impl CommandOrPlugin { } } -pub fn client_id_to_colors( - client_id: ClientId, +pub fn client_slot_to_colors( + display_slot: usize, colors: MultiplayerColors, ) -> Option<(PaletteColor, PaletteColor)> { // (primary color, secondary color) let black = PaletteColor::EightBit(default_colors::BLACK); - match client_id { + match display_slot { 1 => Some((colors.player_1, black)), 2 => Some((colors.player_2, black)), 3 => Some((colors.player_3, black)), @@ -85,6 +85,13 @@ pub fn client_id_to_colors( } } +pub fn client_id_to_colors( + client_id: ClientId, + colors: MultiplayerColors, +) -> Option<(PaletteColor, PaletteColor)> { + client_slot_to_colors(client_id as usize, colors) +} + pub fn single_client_color(colors: Palette) -> (PaletteColor, PaletteColor) { (colors.green, colors.black) } @@ -1426,11 +1433,210 @@ pub struct Palette { pub brown: PaletteColor, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] +pub enum LineStyle { + #[default] + Single, + Double, + Heavy, + Dashed, + HeavyDashed, +} + +impl LineStyle { + pub fn is_heavy(&self) -> bool { + matches!(self, LineStyle::Heavy | LineStyle::HeavyDashed) + } + pub fn is_double(&self) -> bool { + matches!(self, LineStyle::Double) + } + pub fn is_single(&self) -> bool { + matches!(self, LineStyle::Single | LineStyle::Dashed) + } +} + +impl FromStr for LineStyle { + type Err = String; + fn from_str(s: &str) -> Result { + match s.trim().to_lowercase().as_str() { + "single" | "light" | "normal" => Ok(LineStyle::Single), + "double" => Ok(LineStyle::Double), + "heavy" | "bold" | "thick" => Ok(LineStyle::Heavy), + "dashed" => Ok(LineStyle::Dashed), + "heavy_dashed" | "heavy-dashed" | "heavydashed" => Ok(LineStyle::HeavyDashed), + _ => Err(format!( + "Unknown line style: '{}', expected one of: single, double, heavy, dashed, heavy_dashed", + s + )), + } + } +} + +impl fmt::Display for LineStyle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let name = match self { + LineStyle::Single => "single", + LineStyle::Double => "double", + LineStyle::Heavy => "heavy", + LineStyle::Dashed => "dashed", + LineStyle::HeavyDashed => "heavy_dashed", + }; + write!(f, "{}", name) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] +pub struct BorderStyle { + pub top: LineStyle, + pub right: LineStyle, + pub bottom: LineStyle, + pub left: LineStyle, + pub rounded_corners: bool, +} + +impl BorderStyle { + pub fn with_rounded_corners(rounded_corners: bool) -> Self { + BorderStyle { + rounded_corners, + ..Default::default() + } + } + pub fn uniform_style(&self) -> Option { + if self.top == self.right && self.right == self.bottom && self.bottom == self.left { + Some(self.top) + } else { + None + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] +pub struct BorderStyleOverride { + pub all: Option, + pub top: Option, + pub right: Option, + pub bottom: Option, + pub left: Option, + pub rounded_corners: Option, +} + +impl BorderStyleOverride { + pub fn is_empty(&self) -> bool { + self.all.is_none() + && self.top.is_none() + && self.right.is_none() + && self.bottom.is_none() + && self.left.is_none() + && self.rounded_corners.is_none() + } + pub fn none_if_empty(self) -> Option { + if self.is_empty() { + None + } else { + Some(self) + } + } + pub fn from_cli_string(value: &str) -> Result { + let mut border_style_override = BorderStyleOverride::default(); + for token in value.split(',') { + let token = token.trim(); + if token.is_empty() { + continue; + } + let (key, value) = match token.split_once(|c| c == ':' || c == '=') { + Some((key, value)) => (key.trim().to_lowercase(), Some(value.trim())), + None => (token.to_lowercase(), None), + }; + match (key.as_str(), value) { + ("rounded" | "rounded_corners" | "rounded-corners", value) => { + let rounded = match value { + None => true, + Some(value) => value.parse::().map_err(|_| { + format!("Expected true or false for rounded corners, got '{}'", value) + })?, + }; + border_style_override.rounded_corners = Some(rounded); + }, + ("all", Some(value)) => border_style_override.all = Some(LineStyle::from_str(value)?), + ("top", Some(value)) => border_style_override.top = Some(LineStyle::from_str(value)?), + ("right", Some(value)) => { + border_style_override.right = Some(LineStyle::from_str(value)?) + }, + ("bottom", Some(value)) => { + border_style_override.bottom = Some(LineStyle::from_str(value)?) + }, + ("left", Some(value)) => { + border_style_override.left = Some(LineStyle::from_str(value)?) + }, + (_, Some(_)) => { + return Err(format!( + "Unknown border side: '{}', expected one of: all, top, right, bottom, left, rounded", + key + )) + }, + (line_style, None) => border_style_override.all = Some(LineStyle::from_str(line_style)?), + } + } + Ok(border_style_override) + } + pub fn from_optional_cli_string(value: Option<&str>) -> Result, String> { + match value { + Some(value) => Self::from_cli_string(value).map(|b| b.none_if_empty()), + None => Ok(None), + } + } + pub fn from_strings( + all: Option, + top: Option, + right: Option, + bottom: Option, + left: Option, + rounded_corners: Option, + ) -> Result { + let parse = |value: Option| -> Result, String> { + match value { + Some(value) => LineStyle::from_str(&value).map(Some), + None => Ok(None), + } + }; + Ok(BorderStyleOverride { + all: parse(all)?, + top: parse(top)?, + right: parse(right)?, + bottom: parse(bottom)?, + left: parse(left)?, + rounded_corners, + }) + } + pub fn apply_to(&self, base: BorderStyle) -> BorderStyle { + let all = self.all; + BorderStyle { + top: self.top.or(all).unwrap_or(base.top), + right: self.right.or(all).unwrap_or(base.right), + bottom: self.bottom.or(all).unwrap_or(base.bottom), + left: self.left.or(all).unwrap_or(base.left), + rounded_corners: self.rounded_corners.unwrap_or(base.rounded_corners), + } + } + pub fn merge(&self, other: &BorderStyleOverride) -> BorderStyleOverride { + BorderStyleOverride { + all: other.all.or(self.all), + top: other.top.or(self.top), + right: other.right.or(self.right), + bottom: other.bottom.or(self.bottom), + left: other.left.or(self.left), + rounded_corners: other.rounded_corners.or(self.rounded_corners), + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub struct Style { pub colors: Styling, pub rounded_corners: bool, pub hide_session_name: bool, + pub border_style: BorderStyle, + pub floating_border_style: BorderStyle, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] @@ -1827,6 +2033,14 @@ impl ModeInfo { pub fn update_rounded_corners(&mut self, rounded_corners: bool) { self.style.rounded_corners = rounded_corners; } + pub fn update_border_styles( + &mut self, + border_style: BorderStyle, + floating_border_style: BorderStyle, + ) { + self.style.border_style = border_style; + self.style.floating_border_style = floating_border_style; + } pub fn update_arrow_fonts(&mut self, should_support_arrow_fonts: bool) { // it is honestly quite baffling to me how "arrow_fonts: false" can mean "I support arrow // fonts", but since this is a public API... ¯\_(ツ)_/¯ @@ -2311,6 +2525,7 @@ pub struct TabInfo { pub is_sync_panes_active: bool, pub are_floating_panes_visible: bool, pub other_focused_clients: Vec, + pub other_focused_client_slots: Vec, pub active_swap_layout_name: Option, /// Whether the user manually changed the layout, moving out of the swap layout scheme pub is_swap_layout_dirty: bool, @@ -2821,6 +3036,8 @@ pub struct FileToOpen { pub path: PathBuf, pub line_number: Option, pub cwd: Option, + #[serde(default)] + pub border_style: Option, } impl FileToOpen { @@ -2838,6 +3055,10 @@ impl FileToOpen { self.cwd = Some(cwd); self } + pub fn with_border_style(mut self, border_style: BorderStyleOverride) -> Self { + self.border_style = Some(border_style); + self + } } #[derive(Debug, Default, Clone)] @@ -2845,6 +3066,7 @@ pub struct CommandToRun { pub path: PathBuf, pub args: Vec, pub cwd: Option, + pub border_style: Option, } impl CommandToRun { @@ -2861,6 +3083,10 @@ impl CommandToRun { ..Default::default() } } + pub fn with_border_style(mut self, border_style: BorderStyleOverride) -> Self { + self.border_style = Some(border_style); + self + } } #[derive(Debug, Default, Clone)] @@ -3095,6 +3321,8 @@ pub struct FloatingPaneCoordinates { pub height: Option, pub pinned: Option, pub borderless: Option, + #[serde(default)] + pub border_style: Option, } impl FloatingPaneCoordinates { @@ -3146,9 +3374,27 @@ impl FloatingPaneCoordinates { height, pinned, borderless, + border_style: None, }) } } + pub fn with_border_style(mut self, border_style: Option) -> Self { + self.border_style = border_style; + self + } + pub fn merge_border_style( + coordinates: Option, + border_style: Option, + ) -> Option { + match border_style { + Some(border_style) => Some( + coordinates + .unwrap_or_default() + .with_border_style(Some(border_style)), + ), + None => coordinates, + } + } pub fn with_x_fixed(mut self, x: usize) -> Self { self.x = Some(PercentOrFixed::Fixed(x)); self @@ -3208,6 +3454,7 @@ impl From for FloatingPaneCoordinates { height: Some(PercentOrFixed::Fixed(pane_geom.rows.as_usize())), pinned: Some(pane_geom.is_pinned), borderless: None, + border_style: None, } } } @@ -3304,26 +3551,33 @@ impl FromStr for WebSharing { pub enum NewPanePlacement { NoPreference { borderless: Option, + border_style: Option, }, Tiled { direction: Option, borderless: Option, + border_style: Option, }, Floating(Option), InPlace { pane_id_to_replace: Option, close_replaced_pane: bool, borderless: Option, + border_style: Option, }, Stacked { pane_id_to_stack_under: Option, borderless: Option, + border_style: Option, }, } impl Default for NewPanePlacement { fn default() -> Self { - NewPanePlacement::NoPreference { borderless: None } + NewPanePlacement::NoPreference { + borderless: None, + border_style: None, + } } } @@ -3343,6 +3597,7 @@ impl NewPanePlacement { pane_id_to_replace: None, close_replaced_pane, borderless: None, + border_style: None, } } else { self @@ -3356,6 +3611,7 @@ impl NewPanePlacement { pane_id_to_replace, close_replaced_pane, borderless: None, + border_style: None, } } pub fn should_float(&self) -> Option { @@ -3390,13 +3646,67 @@ impl NewPanePlacement { } pub fn get_borderless(&self) -> Option { match self { - NewPanePlacement::NoPreference { borderless } => *borderless, + NewPanePlacement::NoPreference { borderless, .. } => *borderless, NewPanePlacement::Tiled { borderless, .. } => *borderless, NewPanePlacement::Floating(coords) => coords.as_ref().and_then(|c| c.borderless), NewPanePlacement::InPlace { borderless, .. } => *borderless, NewPanePlacement::Stacked { borderless, .. } => *borderless, } } + pub fn get_border_style(&self) -> Option { + match self { + NewPanePlacement::NoPreference { border_style, .. } => *border_style, + NewPanePlacement::Tiled { border_style, .. } => *border_style, + NewPanePlacement::Floating(coords) => { + coords.as_ref().and_then(|c| c.border_style.clone()) + }, + NewPanePlacement::InPlace { border_style, .. } => *border_style, + NewPanePlacement::Stacked { border_style, .. } => *border_style, + } + } + pub fn with_border_style(self, border_style: Option) -> Self { + if border_style.is_none() { + return self; + } + match self { + NewPanePlacement::NoPreference { borderless, .. } => NewPanePlacement::NoPreference { + borderless, + border_style, + }, + NewPanePlacement::Tiled { + direction, + borderless, + .. + } => NewPanePlacement::Tiled { + direction, + borderless, + border_style, + }, + NewPanePlacement::Floating(coords) => NewPanePlacement::Floating(Some( + coords.unwrap_or_default().with_border_style(border_style), + )), + NewPanePlacement::InPlace { + pane_id_to_replace, + close_replaced_pane, + borderless, + .. + } => NewPanePlacement::InPlace { + pane_id_to_replace, + close_replaced_pane, + borderless, + border_style, + }, + NewPanePlacement::Stacked { + pane_id_to_stack_under, + borderless, + .. + } => NewPanePlacement::Stacked { + pane_id_to_stack_under, + borderless, + border_style, + }, + } + } } type Context = BTreeMap; @@ -3477,6 +3787,8 @@ pub enum PluginCommand { QuitZellij, PreviousSwapLayout, NextSwapLayout, + ApplyTiledSwapLayout(String), + ApplyFloatingSwapLayout(String), GoToTabName(String), FocusOrCreateTab(String), GoToTab(u32), // tab index @@ -3591,6 +3903,7 @@ pub enum PluginCommand { ChangeFloatingPanesCoordinates(Vec<(PaneId, FloatingPaneCoordinates)>), TogglePaneBorderless(PaneId), SetPaneBorderless(PaneId, bool), + SetPaneBorderStyle(PaneId, BorderStyleOverride), OpenCommandPaneNearPlugin(CommandToRun, Context), OpenTerminalNearPlugin(FileToOpen), OpenTerminalFloatingNearPlugin(FileToOpen, Option), @@ -3757,3 +4070,185 @@ pub fn can_parse_unicode_bare_keys() { "Can parse a bare 'ъ' keypress" ); } + +#[test] +fn line_style_names_round_trip() { + for line_style in [ + LineStyle::Single, + LineStyle::Double, + LineStyle::Heavy, + LineStyle::Dashed, + LineStyle::HeavyDashed, + ] { + assert_eq!(LineStyle::from_str(&line_style.to_string()), Ok(line_style)); + } + assert!(LineStyle::from_str("squiggly").is_err()); +} + +#[test] +fn border_style_override_from_a_bare_cli_style() { + assert_eq!( + BorderStyleOverride::from_cli_string("double"), + Ok(BorderStyleOverride { + all: Some(LineStyle::Double), + ..Default::default() + }) + ); +} + +#[test] +fn border_style_override_from_a_compound_cli_string() { + assert_eq!( + BorderStyleOverride::from_cli_string("top:double, left=heavy ,rounded"), + Ok(BorderStyleOverride { + top: Some(LineStyle::Double), + left: Some(LineStyle::Heavy), + rounded_corners: Some(true), + ..Default::default() + }) + ); + assert_eq!( + BorderStyleOverride::from_cli_string("all:dashed,rounded:false"), + Ok(BorderStyleOverride { + all: Some(LineStyle::Dashed), + rounded_corners: Some(false), + ..Default::default() + }) + ); +} + +#[test] +fn border_style_override_rejects_unknown_cli_input() { + assert!(BorderStyleOverride::from_cli_string("diagonal:double").is_err()); + assert!(BorderStyleOverride::from_cli_string("top:squiggly").is_err()); + assert!(BorderStyleOverride::from_cli_string("rounded:maybe").is_err()); +} + +#[test] +fn empty_cli_border_style_is_treated_as_no_override() { + assert_eq!( + BorderStyleOverride::from_optional_cli_string(Some("")), + Ok(None) + ); + assert_eq!( + BorderStyleOverride::from_optional_cli_string(None), + Ok(None) + ); +} + +#[test] +fn border_style_override_applies_per_side_over_the_shorthand() { + let base = BorderStyle::with_rounded_corners(true); + let applied = BorderStyleOverride { + all: Some(LineStyle::Double), + top: Some(LineStyle::Heavy), + ..Default::default() + } + .apply_to(base); + assert_eq!( + applied, + BorderStyle { + top: LineStyle::Heavy, + right: LineStyle::Double, + bottom: LineStyle::Double, + left: LineStyle::Double, + rounded_corners: true, + } + ); +} + +#[test] +fn an_empty_border_style_override_leaves_the_base_untouched() { + let base = BorderStyle { + top: LineStyle::Double, + right: LineStyle::Heavy, + bottom: LineStyle::Dashed, + left: LineStyle::Single, + rounded_corners: true, + }; + assert!(BorderStyleOverride::default().is_empty()); + assert_eq!(BorderStyleOverride::default().apply_to(base), base); +} + +#[test] +fn border_style_overrides_merge_with_the_later_one_winning() { + let merged = BorderStyleOverride { + all: Some(LineStyle::Single), + top: Some(LineStyle::Single), + ..Default::default() + } + .merge(&BorderStyleOverride { + top: Some(LineStyle::Double), + rounded_corners: Some(true), + ..Default::default() + }); + assert_eq!( + merged, + BorderStyleOverride { + all: Some(LineStyle::Single), + top: Some(LineStyle::Double), + rounded_corners: Some(true), + ..Default::default() + } + ); +} + +#[test] +fn a_uniform_border_style_reports_its_line_style() { + assert_eq!( + BorderStyle { + top: LineStyle::Double, + right: LineStyle::Double, + bottom: LineStyle::Double, + left: LineStyle::Double, + rounded_corners: false, + } + .uniform_style(), + Some(LineStyle::Double) + ); + assert_eq!( + BorderStyle { + top: LineStyle::Double, + ..Default::default() + } + .uniform_style(), + None + ); +} + +#[test] +fn new_pane_placement_carries_a_border_style() { + let placement = NewPanePlacement::Tiled { + direction: None, + borderless: None, + border_style: None, + } + .with_border_style(Some(BorderStyleOverride { + all: Some(LineStyle::Heavy), + ..Default::default() + })); + assert_eq!( + placement.get_border_style(), + Some(BorderStyleOverride { + all: Some(LineStyle::Heavy), + ..Default::default() + }) + ); +} + +#[test] +fn a_floating_placement_carries_its_border_style_in_the_coordinates() { + let placement = NewPanePlacement::Floating(None).with_border_style(Some(BorderStyleOverride { + all: Some(LineStyle::Double), + ..Default::default() + })); + let coordinates = placement.floating_pane_coordinates().unwrap(); + assert_eq!( + coordinates.border_style, + Some(BorderStyleOverride { + all: Some(LineStyle::Double), + ..Default::default() + }) + ); + assert_eq!(placement.get_border_style(), coordinates.border_style); +} diff --git a/zellij-utils/src/distribution.rs b/zellij-utils/src/distribution.rs new file mode 100644 index 000000000..8ca07c5bf --- /dev/null +++ b/zellij-utils/src/distribution.rs @@ -0,0 +1,655 @@ +//! Zellij distributions. +//! +//! A distribution is a build of Zellij that bundles its own plugins, configuration and layouts +//! into the executable. Bundled plugins are indistinguishable from Zellij's own builtin plugins: +//! they are referenced as `zellij:`, loaded from memory rather than from disk, and are not +//! subject to the plugin permission system. +//! +//! Stock Zellij is itself expressed as a distribution (see [`Distribution::zellij`]), so there is +//! only ever one code path. +//! +//! Distributions are declared at compile time by depending on the `zellij` crate and calling +//! `zellij::run` with a [`Distribution`] instead of using the `zellij` binary directly. + +use std::sync::OnceLock; + +/// A plugin bundled into the executable. +/// +/// `bytes` is `None` when the build does not embed plugin assets (the +/// `disable_automatic_asset_installation` feature), in which case the plugin is looked up in the +/// plugin directory on disk instead. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DistributionPlugin { + pub name: &'static str, + pub bytes: Option<&'static [u8]>, +} + +impl DistributionPlugin { + pub fn new(name: &'static str, bytes: &'static [u8]) -> Self { + DistributionPlugin { + name, + bytes: Some(bytes), + } + } + pub fn from_plugin_dir(name: &'static str) -> Self { + DistributionPlugin { name, bytes: None } + } +} + +/// A layout bundled into the executable, addressable by name wherever a builtin layout name is +/// accepted (`--layout`, `default_layout`, `zellij setup --dump-layout`). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DistributionLayout { + pub name: &'static str, + pub layout: &'static str, + pub swap_layout: Option<&'static str>, +} + +impl DistributionLayout { + pub fn new(name: &'static str, layout: &'static str) -> Self { + DistributionLayout { + name, + layout, + swap_layout: None, + } + } + pub fn with_swap_layout(mut self, swap_layout: &'static str) -> Self { + self.swap_layout = Some(swap_layout); + self + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Distribution { + /// Lowercase, filesystem-safe identifier. Every path this distribution owns is named after + /// it: `~/.config/`, `/etc/`, `/share/`, the temporary directory, + /// the log file, and the command name shown in help output. + pub name: &'static str, + /// Human-readable name, shown in `zellij setup --check`. Defaults to `name`. + pub display_name: &'static str, + pub version: &'static str, + /// `(qualifier, organization, application)` for the platform-specific config, cache, data and + /// runtime directories. Defaults to `("", "", name)`. + pub project_dirs: Option<(&'static str, &'static str, &'static str)>, + /// Plugins this distribution adds on top of Zellij's builtins + pub plugins: Vec, + /// Zellij builtins this distribution drops + pub removed_builtin_plugins: Vec<&'static str>, + /// Layered between Zellij's default configuration and the user's `config.kdl` + pub config: Option<&'static str>, + pub layouts: Vec, + is_stock_zellij: bool, +} + +impl Distribution { + pub fn new(name: &'static str, version: &'static str) -> Self { + Distribution { + name, + display_name: name, + version, + project_dirs: None, + plugins: vec![], + removed_builtin_plugins: vec![], + config: None, + layouts: vec![], + is_stock_zellij: false, + } + } + pub fn with_display_name(mut self, display_name: &'static str) -> Self { + self.display_name = display_name; + self + } + /// Override the `(qualifier, organization, application)` triple used to derive the platform + /// config, cache, data and runtime directories. Only `application` is significant on Linux, + /// where it is lowercased. + pub fn with_project_dirs( + mut self, + qualifier: &'static str, + organization: &'static str, + application: &'static str, + ) -> Self { + self.project_dirs = Some((qualifier, organization, application)); + self + } + pub fn with_plugin(mut self, name: &'static str, bytes: &'static [u8]) -> Self { + self.plugins.push(DistributionPlugin::new(name, bytes)); + self + } + /// Drop one of Zellij's builtin plugins. `zellij:` then stops resolving, and anything + /// still referring to it (a keybinding, an alias, a layout) fails to load with an error in + /// that pane. Combine with [`Self::with_plugin`] under the same name to replace it, and + /// remember to point the corresponding alias in the bundled configuration somewhere valid. + /// + /// This does not make the executable smaller: the bytes of every Zellij builtin are linked in + /// regardless. + pub fn without_plugin(mut self, name: &'static str) -> Self { + self.removed_builtin_plugins.push(name); + self + } + /// Drop every one of Zellij's builtin plugins, leaving only the ones this distribution + /// bundles itself. Zellij's default configuration refers to several of them, so a + /// distribution doing this almost certainly wants to supply its own configuration and layouts. + pub fn without_builtin_plugins(mut self) -> Self { + self.removed_builtin_plugins + .extend_from_slice(ZELLIJ_BUILTIN_PLUGIN_NAMES); + self + } + pub fn with_config(mut self, config: &'static str) -> Self { + self.config = Some(config); + self + } + pub fn with_layout(mut self, layout: DistributionLayout) -> Self { + self.layouts.push(layout); + self + } + /// Stock Zellij, with its own builtin plugins and no bundled configuration or layouts. + /// + /// Its `project_dirs` are pinned to Zellij's historical values: changing them would move + /// every existing installation's configuration and cache. + pub fn zellij() -> Self { + Distribution { + name: "zellij", + display_name: "Zellij", + version: crate::consts::VERSION, + project_dirs: None, + plugins: zellij_builtin_plugins(), + removed_builtin_plugins: vec![], + config: None, + layouts: vec![], + is_stock_zellij: true, + } + } + pub fn is_stock_zellij(&self) -> bool { + self.is_stock_zellij + } + /// The `(qualifier, organization, application)` triple for the platform directories + pub fn project_dirs(&self) -> (&'static str, &'static str, &'static str) { + if let Some(project_dirs) = self.project_dirs { + return project_dirs; + } + if self.is_stock_zellij { + if cfg!(windows) { + return ("", "", "Zellij"); + } + return ("org", "Zellij Contributors", "Zellij"); + } + ("", "", self.name) + } + fn plugin(&self, name: &str) -> Option<&DistributionPlugin> { + self.plugins.iter().find(|plugin| plugin.name == name) + } + fn layout(&self, name: &str) -> Option<&DistributionLayout> { + self.layouts.iter().find(|layout| layout.name == name) + } +} + +pub const ZELLIJ_BUILTIN_PLUGIN_NAMES: &[&str] = &[ + "compact-bar", + "status-bar", + "tab-bar", + "strider", + "session-manager", + "configuration", + "plugin-manager", + "about", + "share", + "multiple-select", + "layout-manager", + "link", +]; + +// Plugins are taken from: +// +// - `zellij-utils/assets/plugins`: When building in release mode OR when the +// `plugins_from_target` feature IS NOT set +// - `zellij-utils/../target/wasm32-wasip1/debug`: When building in debug mode AND the +// `plugins_from_target` feature IS set +#[cfg(all( + not(target_family = "wasm"), + not(feature = "disable_automatic_asset_installation") +))] +fn zellij_builtin_plugins() -> Vec { + macro_rules! builtin_plugin { + ($name:literal) => { + DistributionPlugin::new( + $name, + #[cfg(any(not(feature = "plugins_from_target"), not(debug_assertions)))] + include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/assets/plugins/", + $name, + ".wasm" + )), + #[cfg(all(feature = "plugins_from_target", debug_assertions))] + include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../target/wasm32-wasip1/debug/", + $name, + ".wasm" + )), + ) + }; + } + vec![ + builtin_plugin!("compact-bar"), + builtin_plugin!("status-bar"), + builtin_plugin!("tab-bar"), + builtin_plugin!("strider"), + builtin_plugin!("session-manager"), + builtin_plugin!("configuration"), + builtin_plugin!("plugin-manager"), + builtin_plugin!("about"), + builtin_plugin!("share"), + builtin_plugin!("multiple-select"), + builtin_plugin!("layout-manager"), + builtin_plugin!("link"), + ] +} + +// When the `disable_automatic_asset_installation` feature is set, no plugins are embedded at all. +// Builtin plugins must then be provided in the plugin directory. +#[cfg(any( + target_family = "wasm", + feature = "disable_automatic_asset_installation" +))] +fn zellij_builtin_plugins() -> Vec { + ZELLIJ_BUILTIN_PLUGIN_NAMES + .iter() + .map(|name| DistributionPlugin::from_plugin_dir(name)) + .collect() +} + +static DISTRIBUTION: OnceLock = OnceLock::new(); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum DistributionError { + AlreadySet, + InvalidName(String), + PluginNameCollidesWithBuiltin(String), + DuplicatePluginName(String), + DuplicateLayoutName(String), + UnknownBuiltinPlugin(String), +} + +impl std::fmt::Display for DistributionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DistributionError::AlreadySet => { + write!(f, "A distribution has already been set for this process") + }, + DistributionError::InvalidName(name) => write!( + f, + "'{}' is not a usable distribution name. It names the configuration, cache, data \ + and temporary directories as well as the command itself, so it must be non-empty \ + and contain only lowercase letters, digits, '-' and '_'. Use with_display_name() \ + for a human-readable name.", + name + ), + DistributionError::PluginNameCollidesWithBuiltin(name) => write!( + f, + "Plugin name '{}' collides with a Zellij builtin plugin. To replace it, call \ + without_plugin(\"{}\") as well. To leave the builtin alone and use a different \ + implementation, give your plugin its own name and point the '{}' alias at it in \ + the 'plugins' block of the bundled configuration.", + name, name, name + ), + DistributionError::UnknownBuiltinPlugin(name) => write!( + f, + "'{}' is not a Zellij builtin plugin, so it cannot be removed. The builtins are: \ + {}", + name, + ZELLIJ_BUILTIN_PLUGIN_NAMES.join(", ") + ), + DistributionError::DuplicatePluginName(name) => { + write!(f, "Plugin name '{}' is declared more than once", name) + }, + DistributionError::DuplicateLayoutName(name) => { + write!(f, "Layout name '{}' is declared more than once", name) + }, + } + } +} + +impl std::error::Error for DistributionError {} + +/// Install a distribution for this process. Zellij's own builtin plugins are always included in +/// addition to the distribution's own. +/// +/// Must be called before anything reads the distribution, and may only be called once. +pub fn set_distribution(mut distribution: Distribution) -> Result<(), DistributionError> { + distribution.plugins = resolve_plugins(zellij_builtin_plugins(), &distribution)?; + DISTRIBUTION + .set(distribution) + .map_err(|_| DistributionError::AlreadySet) +} + +/// Validate a distribution and compute its effective plugin set: `builtins`, minus the builtins +/// it removes, plus the plugins it supplies itself. +fn resolve_plugins( + builtins: Vec, + distribution: &Distribution, +) -> Result, DistributionError> { + if !is_usable_name(distribution.name) { + return Err(DistributionError::InvalidName(distribution.name.to_owned())); + } + for removed in &distribution.removed_builtin_plugins { + if !builtins.iter().any(|builtin| builtin.name == *removed) { + return Err(DistributionError::UnknownBuiltinPlugin( + (*removed).to_owned(), + )); + } + } + for (i, layout) in distribution.layouts.iter().enumerate() { + if distribution.layouts[..i] + .iter() + .any(|existing| existing.name == layout.name) + { + return Err(DistributionError::DuplicateLayoutName( + layout.name.to_owned(), + )); + } + } + let builtin_names: Vec<&'static str> = builtins.iter().map(|plugin| plugin.name).collect(); + let mut plugins = builtins; + plugins.retain(|plugin| !distribution.removed_builtin_plugins.contains(&plugin.name)); + for plugin in &distribution.plugins { + if plugins.iter().any(|existing| existing.name == plugin.name) { + return if builtin_names.contains(&plugin.name) { + Err(DistributionError::PluginNameCollidesWithBuiltin( + plugin.name.to_owned(), + )) + } else { + Err(DistributionError::DuplicatePluginName( + plugin.name.to_owned(), + )) + }; + } + plugins.push(plugin.clone()); + } + Ok(plugins) +} + +fn is_usable_name(name: &str) -> bool { + !name.is_empty() + && name + .chars() + .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-' || c == '_') +} + +/// The distribution this process is running, defaulting to stock Zellij +pub fn distribution() -> &'static Distribution { + DISTRIBUTION.get_or_init(Distribution::zellij) +} + +/// Whether the given name refers to a plugin bundled into this executable, and may therefore be +/// referenced as `zellij:` +pub fn is_builtin_plugin_name(name: &str) -> bool { + distribution().plugin(name).is_some() +} + +/// The bytes of a bundled plugin, if this build embeds them +pub fn builtin_plugin_bytes(name: &str) -> Option<&'static [u8]> { + distribution().plugin(name).and_then(|plugin| plugin.bytes) +} + +pub fn builtin_plugin_names() -> Vec<&'static str> { + distribution() + .plugins + .iter() + .map(|plugin| plugin.name) + .collect() +} + +/// Every bundled plugin whose bytes are embedded in this executable +pub fn embedded_plugins() -> Vec<(&'static str, &'static [u8])> { + distribution() + .plugins + .iter() + .filter_map(|plugin| plugin.bytes.map(|bytes| (plugin.name, bytes))) + .collect() +} + +/// The configuration bundled by this distribution, layered between Zellij's defaults and the +/// user's own configuration +pub fn bundled_config() -> Option<&'static str> { + distribution().config +} + +/// A layout bundled by this distribution, addressable by name +pub fn builtin_layout(name: &str) -> Option<&'static DistributionLayout> { + distribution().layout(name) +} + +/// The plugins this distribution supplies itself, including any replacing a Zellij builtin +pub fn own_plugin_names() -> Vec<&'static str> { + let distribution = distribution(); + distribution + .plugins + .iter() + .map(|plugin| plugin.name) + .filter(|name| { + !ZELLIJ_BUILTIN_PLUGIN_NAMES.contains(name) + || distribution.removed_builtin_plugins.contains(name) + }) + .collect() +} + +/// The Zellij builtin plugins this distribution dropped and did not replace +pub fn removed_builtin_plugin_names() -> Vec<&'static str> { + let distribution = distribution(); + distribution + .removed_builtin_plugins + .iter() + .copied() + .filter(|name| distribution.plugin(name).is_none()) + .collect() +} + +/// The lowercase identifier every path and the command name are derived from +pub fn name() -> &'static str { + distribution().name +} + +/// The human-readable name +pub fn display_name() -> &'static str { + distribution().display_name +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn zellij_distribution_declares_every_builtin_plugin() { + let distribution = Distribution::zellij(); + let mut declared: Vec<&str> = distribution.plugins.iter().map(|p| p.name).collect(); + let mut expected: Vec<&str> = ZELLIJ_BUILTIN_PLUGIN_NAMES.to_vec(); + declared.sort(); + expected.sort(); + assert_eq!(declared, expected); + } + + #[test] + #[cfg(not(feature = "disable_automatic_asset_installation"))] + fn zellij_builtin_plugins_are_embedded() { + for plugin in Distribution::zellij().plugins { + assert!( + plugin.bytes.map(|b| !b.is_empty()).unwrap_or(false), + "builtin plugin '{}' has no embedded bytes", + plugin.name + ); + } + } + + #[test] + fn only_bundled_plugin_names_are_builtin() { + assert!(is_builtin_plugin_name("tab-bar")); + assert!(!is_builtin_plugin_name("yazelix-sidebar")); + assert!(!is_builtin_plugin_name("")); + } + + fn fake_builtins() -> Vec { + vec![ + DistributionPlugin::new("tab-bar", b"tab-bar bytes"), + DistributionPlugin::new("status-bar", b"status-bar bytes"), + DistributionPlugin::new("strider", b"strider bytes"), + ] + } + + fn resolved_names(distribution: &Distribution) -> Vec<&'static str> { + let mut names: Vec<&'static str> = resolve_plugins(fake_builtins(), distribution) + .expect("distribution should be valid") + .iter() + .map(|plugin| plugin.name) + .collect(); + names.sort(); + names + } + + #[test] + fn a_distribution_adds_its_plugins_to_the_builtins() { + let distribution = Distribution::new("test", "0.0.0").with_plugin("sidebar", b"sidebar"); + assert_eq!( + resolved_names(&distribution), + vec!["sidebar", "status-bar", "strider", "tab-bar"] + ); + } + + #[test] + fn a_distribution_can_remove_a_builtin() { + let distribution = Distribution::new("test", "0.0.0").without_plugin("strider"); + assert_eq!(resolved_names(&distribution), vec!["status-bar", "tab-bar"]); + } + + #[test] + fn a_distribution_can_remove_every_builtin() { + let distribution = Distribution::new("test", "0.0.0") + .without_plugin("tab-bar") + .without_plugin("status-bar") + .without_plugin("strider") + .with_plugin("only-mine", b"mine"); + assert_eq!(resolved_names(&distribution), vec!["only-mine"]); + } + + #[test] + fn without_builtin_plugins_removes_all_of_them() { + let distribution = Distribution::new("test", "0.0.0").without_builtin_plugins(); + let resolved = resolve_plugins(zellij_builtin_plugins(), &distribution) + .expect("distribution should be valid"); + assert!(resolved.is_empty(), "resolved to {:?}", resolved); + } + + #[test] + fn a_removed_builtin_may_be_replaced_under_the_same_name() { + let distribution = Distribution::new("test", "0.0.0") + .without_plugin("tab-bar") + .with_plugin("tab-bar", b"replacement"); + let resolved = + resolve_plugins(fake_builtins(), &distribution).expect("distribution should be valid"); + let tab_bar = resolved + .iter() + .find(|plugin| plugin.name == "tab-bar") + .expect("tab-bar should still be present"); + assert_eq!(tab_bar.bytes, Some(b"replacement".as_slice())); + assert_eq!( + resolved.iter().filter(|p| p.name == "tab-bar").count(), + 1, + "the replacement should not sit alongside the original" + ); + } + + #[test] + fn removal_order_does_not_matter() { + let after = Distribution::new("test", "0.0.0") + .with_plugin("tab-bar", b"replacement") + .without_plugin("tab-bar"); + let before = Distribution::new("test", "0.0.0") + .without_plugin("tab-bar") + .with_plugin("tab-bar", b"replacement"); + assert_eq!(resolved_names(&after), resolved_names(&before)); + } + + #[test] + fn distribution_plugin_may_not_shadow_a_builtin() { + let distribution = Distribution::new("test", "0.0.0").with_plugin("tab-bar", b"mine"); + assert_eq!( + resolve_plugins(fake_builtins(), &distribution), + Err(DistributionError::PluginNameCollidesWithBuiltin( + "tab-bar".to_owned() + )) + ); + } + + #[test] + fn a_distribution_may_not_declare_the_same_plugin_twice() { + let distribution = Distribution::new("test", "0.0.0") + .with_plugin("sidebar", b"one") + .with_plugin("sidebar", b"two"); + assert_eq!( + resolve_plugins(fake_builtins(), &distribution), + Err(DistributionError::DuplicatePluginName("sidebar".to_owned())) + ); + } + + #[test] + fn a_distribution_may_not_declare_the_same_layout_twice() { + let distribution = Distribution::new("test", "0.0.0") + .with_layout(DistributionLayout::new("mine", "layout {}")) + .with_layout(DistributionLayout::new("mine", "layout {}")); + assert_eq!( + resolve_plugins(fake_builtins(), &distribution), + Err(DistributionError::DuplicateLayoutName("mine".to_owned())) + ); + } + + #[test] + fn only_real_builtins_can_be_removed() { + let distribution = Distribution::new("test", "0.0.0").without_plugin("not-a-plugin"); + assert_eq!( + resolve_plugins(fake_builtins(), &distribution), + Err(DistributionError::UnknownBuiltinPlugin( + "not-a-plugin".to_owned() + )) + ); + } + + #[test] + fn distribution_name_must_be_usable_as_a_directory_name() { + for name in ["", "Yazelix", "my distribution", "dist/ro", "diström"] { + assert_eq!( + resolve_plugins(fake_builtins(), &Distribution::new(name, "0.0.0")), + Err(DistributionError::InvalidName(name.to_owned())), + "'{}' should have been rejected", + name + ); + } + assert!(is_usable_name("yazelix")); + assert!(is_usable_name("my-dist_2")); + } + + #[test] + fn stock_zellij_keeps_its_historical_paths() { + let zellij = Distribution::zellij(); + assert_eq!(zellij.name, "zellij"); + assert_eq!(zellij.display_name, "Zellij"); + if cfg!(windows) { + assert_eq!(zellij.project_dirs(), ("", "", "Zellij")); + } else { + assert_eq!( + zellij.project_dirs(), + ("org", "Zellij Contributors", "Zellij") + ); + } + } + + #[test] + fn a_distribution_derives_its_platform_dirs_from_its_name() { + let distribution = Distribution::new("yazelix", "0.9.0").with_display_name("Yazelix"); + assert_eq!(distribution.project_dirs(), ("", "", "yazelix")); + let overridden = distribution.with_project_dirs("dev", "Yazelix Authors", "Yazelix"); + assert_eq!( + overridden.project_dirs(), + ("dev", "Yazelix Authors", "Yazelix") + ); + } +} diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index cdf439dce..109d3cb08 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -358,6 +358,8 @@ pub enum ScreenContext { ForwardDesktopNotifications, PreviousSwapLayout, NextSwapLayout, + ApplyTiledSwapLayout, + ApplyFloatingSwapLayout, OverrideLayout, OverrideLayoutComplete, QueryTabNames, @@ -427,6 +429,7 @@ pub enum ScreenContext { ChangeFloatingPanesCoordinates, TogglePaneBorderless, SetPaneBorderless, + SetPaneBorderStyle, AddHighlightPaneFrameColorOverride, GroupAndUngroupPanes, HighlightAndUnhighlightPanes, @@ -476,6 +479,8 @@ pub enum ScreenContext { ToggleFloatingPanesWithTabId, PreviousSwapLayoutWithTabId, NextSwapLayoutWithTabId, + ApplyTiledSwapLayoutWithTabId, + ApplyFloatingSwapLayoutWithTabId, MoveTabWithTabId, UpdateBackgroundPluginSubscriptions, ClearHintTextCache, @@ -535,6 +540,7 @@ pub enum PluginContext { Exit, AddClient, RemoveClient, + UpdatePluginTabIndices, NewTab, OverrideLayout, ApplyCachedEvents, diff --git a/zellij-utils/src/home.rs b/zellij-utils/src/home.rs index 3cef1106a..4bfef36d8 100644 --- a/zellij-utils/src/home.rs +++ b/zellij-utils/src/home.rs @@ -2,9 +2,9 @@ //! # This module contain everything you'll need to access local system paths //! containing configuration and layouts -use crate::consts::{SYSTEM_DEFAULT_CONFIG_DIR, ZELLIJ_PROJ_DIR}; +use crate::consts::{system_default_config_dir, ZELLIJ_PROJ_DIR}; -use std::{path::Path, path::PathBuf}; +use std::path::PathBuf; #[cfg(not(windows))] use crate::home_unix as platform; @@ -32,7 +32,7 @@ pub(crate) fn default_config_dirs() -> Vec> { vec![ home_config_dir(), Some(xdg_config_dir()), - Some(Path::new(SYSTEM_DEFAULT_CONFIG_DIR).to_path_buf()), + Some(system_default_config_dir()), ] } diff --git a/zellij-utils/src/home_unix.rs b/zellij-utils/src/home_unix.rs index 30922b184..1d7cdccd9 100644 --- a/zellij-utils/src/home_unix.rs +++ b/zellij-utils/src/home_unix.rs @@ -1,15 +1,17 @@ use directories::BaseDirs; use std::path::PathBuf; -const CONFIG_LOCATION: &str = ".config/zellij"; +fn config_location() -> PathBuf { + PathBuf::from(".config").join(crate::distribution::name()) +} pub(crate) fn home_config_dir() -> Option { - BaseDirs::new().map(|dirs| dirs.home_dir().join(CONFIG_LOCATION)) + BaseDirs::new().map(|dirs| dirs.home_dir().join(config_location())) } pub(crate) fn try_create_home_config_dir() { if let Some(user_dirs) = BaseDirs::new() { - let config_dir = user_dirs.home_dir().join(CONFIG_LOCATION); + let config_dir = user_dirs.home_dir().join(config_location()); if let Err(e) = std::fs::create_dir_all(config_dir) { log::error!("Failed to create config dir: {:?}", e); } @@ -19,5 +21,7 @@ pub(crate) fn try_create_home_config_dir() { /// System-wide data directory (e.g. `/usr/share/zellij` from distro packages). pub(crate) fn system_data_dir() -> PathBuf { use crate::consts::SYSTEM_DEFAULT_DATA_DIR_PREFIX; - std::path::Path::new(SYSTEM_DEFAULT_DATA_DIR_PREFIX).join("share/zellij") + std::path::Path::new(SYSTEM_DEFAULT_DATA_DIR_PREFIX) + .join("share") + .join(crate::distribution::name()) } diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index 43525e20b..f5e9a5c07 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -7,8 +7,8 @@ use super::layout::{ }; use crate::cli::CliAction; use crate::data::{ - CommandOrPlugin, Direction, KeyWithModifier, LayoutInfo, NewPanePlacement, OriginatingPlugin, - PaneId, Resize, UnblockCondition, + BorderStyleOverride, CommandOrPlugin, Direction, KeyWithModifier, LayoutInfo, NewPanePlacement, + OriginatingPlugin, PaneId, Resize, UnblockCondition, }; use crate::data::{FloatingPaneCoordinates, InputMode}; use crate::home::{find_default_config_dir, get_layout_dir}; @@ -320,6 +320,7 @@ pub enum Action { near_current_pane: bool, no_focus: bool, borderless: Option, + border_style: Option, tab_id: Option, }, /// Open a new pane in place of the focused one, suppressing it instead @@ -474,6 +475,12 @@ pub enum Action { ToggleMouseMode, PreviousSwapLayout, NextSwapLayout, + ApplyTiledSwapLayout { + name: String, + }, + ApplyFloatingSwapLayout { + name: String, + }, /// Override the layout of the active tab OverrideLayout { tabs: Vec, @@ -625,6 +632,10 @@ pub enum Action { pane_id: PaneId, borderless: bool, }, + SetPaneBorderStyle { + pane_id: PaneId, + border_style: BorderStyleOverride, + }, TogglePaneInGroup, ToggleGroupMarking, // Pane-targeting CLI-only variants @@ -712,6 +723,14 @@ pub enum Action { NextSwapLayoutByTabId { id: u64, }, + ApplyTiledSwapLayoutByTabId { + id: u64, + name: String, + }, + ApplyFloatingSwapLayoutByTabId { + id: u64, + name: String, + }, MoveTabByTabId { id: u64, direction: Direction, @@ -1116,8 +1135,11 @@ impl Action { near_current_pane, no_focus, borderless, + border_style, tab_id, } => { + let border_style = + BorderStyleOverride::from_optional_cli_string(border_style.as_deref())?; let pane_id_to_replace = match pane_id { Some(pane_id_str) => match PaneId::from_str(&pane_id_str) { Ok(parsed_pane_id) => Some(parsed_pane_id), @@ -1173,24 +1195,28 @@ impl Action { }; let placement = if floating { - NewPanePlacement::Floating(FloatingPaneCoordinates::new( - x, y, width, height, pinned, borderless, + NewPanePlacement::Floating(FloatingPaneCoordinates::merge_border_style( + FloatingPaneCoordinates::new(x, y, width, height, pinned, borderless), + border_style, )) } else if in_place { NewPanePlacement::InPlace { pane_id_to_replace, close_replaced_pane, borderless, + border_style, } } else if stacked { NewPanePlacement::Stacked { pane_id_to_stack_under: None, borderless, + border_style, } } else { NewPanePlacement::Tiled { direction, borderless, + border_style, } }; @@ -1230,8 +1256,11 @@ impl Action { pane_name: name, skip_cache: skip_plugin_cache, cwd, - coordinates: FloatingPaneCoordinates::new( - x, y, width, height, pinned, borderless, + coordinates: FloatingPaneCoordinates::merge_border_style( + FloatingPaneCoordinates::new( + x, y, width, height, pinned, borderless, + ), + border_style, ), no_focus, tab_id, @@ -1281,8 +1310,11 @@ impl Action { Ok(vec![Action::NewFloatingPane { command: Some(run_command_action), pane_name: name, - coordinates: FloatingPaneCoordinates::new( - x, y, width, height, pinned, borderless, + coordinates: FloatingPaneCoordinates::merge_border_style( + FloatingPaneCoordinates::new( + x, y, width, height, pinned, borderless, + ), + border_style, ), near_current_pane, no_focus, @@ -1314,6 +1346,7 @@ impl Action { near_current_pane, no_focus, borderless, + border_style, tab_id, }]) } @@ -1322,8 +1355,11 @@ impl Action { Ok(vec![Action::NewFloatingPane { command: None, pane_name: name, - coordinates: FloatingPaneCoordinates::new( - x, y, width, height, pinned, borderless, + coordinates: FloatingPaneCoordinates::merge_border_style( + FloatingPaneCoordinates::new( + x, y, width, height, pinned, borderless, + ), + border_style, ), near_current_pane, no_focus, @@ -1355,6 +1391,7 @@ impl Action { near_current_pane, no_focus, borderless, + border_style, tab_id, }]) } @@ -1376,8 +1413,11 @@ impl Action { near_current_pane, no_focus, borderless, + border_style, tab_id, } => { + let border_style = + BorderStyleOverride::from_optional_cli_string(border_style.as_deref())?; let mut file = file; let current_dir = get_current_dir(); let cwd = cwd @@ -1396,8 +1436,9 @@ impl Action { in_place, close_replaced_pane, start_suppressed, - coordinates: FloatingPaneCoordinates::new( - x, y, width, height, pinned, borderless, + coordinates: FloatingPaneCoordinates::merge_border_style( + FloatingPaneCoordinates::new(x, y, width, height, pinned, borderless), + border_style, ), near_current_pane, no_focus, @@ -1759,6 +1800,20 @@ impl Action { Some(id) => Ok(vec![Action::NextSwapLayoutByTabId { id: id as u64 }]), None => Ok(vec![Action::NextSwapLayout]), }, + CliAction::ApplyTiledSwapLayout { name, tab_id } => match tab_id { + Some(id) => Ok(vec![Action::ApplyTiledSwapLayoutByTabId { + id: id as u64, + name, + }]), + None => Ok(vec![Action::ApplyTiledSwapLayout { name }]), + }, + CliAction::ApplyFloatingSwapLayout { name, tab_id } => match tab_id { + Some(id) => Ok(vec![Action::ApplyFloatingSwapLayoutByTabId { + id: id as u64, + name, + }]), + None => Ok(vec![Action::ApplyFloatingSwapLayout { name }]), + }, CliAction::OverrideLayout { layout, layout_string, @@ -2049,10 +2104,14 @@ impl Action { height, pinned, borderless, + border_style, } => { - let Some(coordinates) = - FloatingPaneCoordinates::new(x, y, width, height, pinned, borderless) - else { + let border_style = + BorderStyleOverride::from_optional_cli_string(border_style.as_deref())?; + let Some(coordinates) = FloatingPaneCoordinates::merge_border_style( + FloatingPaneCoordinates::new(x, y, width, height, pinned, borderless), + border_style, + ) else { return Err(format!("Failed to parse floating pane coordinates")); }; let parsed_pane_id = PaneId::from_str(&pane_id); @@ -2071,6 +2130,29 @@ impl Action { } } }, + CliAction::SetPaneBorderStyle { + pane_id, + border_style, + } => { + let border_style = + BorderStyleOverride::from_optional_cli_string(border_style.as_deref())? + .unwrap_or_default(); + let parsed_pane_id = PaneId::from_str(&pane_id); + match parsed_pane_id { + Ok(parsed_pane_id) => { + Ok(vec![Action::SetPaneBorderStyle { + pane_id: parsed_pane_id, + border_style, + }]) + }, + Err(_e) => { + Err(format!( + "Malformed pane id: {}, expecting either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)", + pane_id + )) + } + } + }, CliAction::TogglePaneBorderless { pane_id } => { let parsed_pane_id = PaneId::from_str(&pane_id); match parsed_pane_id { @@ -3670,6 +3752,194 @@ mod tests { assert!(result.is_err()); } + fn new_pane_cli_action(floating: bool, border_style: Option<&str>) -> CliAction { + CliAction::NewPane { + direction: None, + command: vec![], + plugin: None, + cwd: None, + floating, + in_place: false, + close_replaced_pane: false, + pane_id: None, + name: None, + close_on_exit: false, + start_suspended: false, + configuration: None, + skip_plugin_cache: false, + x: None, + y: None, + width: None, + height: None, + pinned: None, + stacked: false, + blocking: false, + block_until_exit_success: false, + block_until_exit_failure: false, + block_until_exit: false, + unblock_condition: None, + near_current_pane: false, + no_focus: false, + borderless: None, + tab_id: None, + border_style: border_style.map(|s| s.to_string()), + } + } + + #[test] + fn test_new_tiled_pane_with_border_style() { + let result = Action::actions_from_cli( + new_pane_cli_action(false, Some("top:double,rounded")), + Box::new(|| PathBuf::from("/tmp")), + None, + ); + let actions = result.unwrap(); + match &actions[0] { + Action::NewTiledPane { border_style, .. } => { + assert_eq!( + *border_style, + Some(BorderStyleOverride { + top: Some(crate::data::LineStyle::Double), + rounded_corners: Some(true), + ..Default::default() + }) + ); + }, + other => panic!("Expected NewTiledPane action, got {:?}", other), + } + } + + #[test] + fn test_new_floating_pane_with_border_style() { + let result = Action::actions_from_cli( + new_pane_cli_action(true, Some("heavy")), + Box::new(|| PathBuf::from("/tmp")), + None, + ); + let actions = result.unwrap(); + match &actions[0] { + Action::NewFloatingPane { coordinates, .. } => { + assert_eq!( + coordinates.as_ref().unwrap().border_style, + Some(BorderStyleOverride { + all: Some(crate::data::LineStyle::Heavy), + ..Default::default() + }) + ); + }, + other => panic!("Expected NewFloatingPane action, got {:?}", other), + } + } + + #[test] + fn test_new_pane_without_border_style() { + let result = Action::actions_from_cli( + new_pane_cli_action(false, None), + Box::new(|| PathBuf::from("/tmp")), + None, + ); + let actions = result.unwrap(); + match &actions[0] { + Action::NewTiledPane { border_style, .. } => assert_eq!(*border_style, None), + other => panic!("Expected NewTiledPane action, got {:?}", other), + } + } + + #[test] + fn test_new_pane_with_malformed_border_style_is_an_error() { + let result = Action::actions_from_cli( + new_pane_cli_action(false, Some("squiggly")), + Box::new(|| PathBuf::from("/tmp")), + None, + ); + assert!(result.is_err()); + } + + #[test] + fn test_set_pane_border_style() { + let cli_action = CliAction::SetPaneBorderStyle { + pane_id: "plugin_4".to_string(), + border_style: Some("bottom:heavy".to_string()), + }; + let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); + let actions = result.unwrap(); + assert_eq!(actions.len(), 1); + match &actions[0] { + Action::SetPaneBorderStyle { + pane_id, + border_style, + } => { + assert_eq!(*pane_id, PaneId::Plugin(4)); + assert_eq!( + *border_style, + BorderStyleOverride { + bottom: Some(crate::data::LineStyle::Heavy), + ..Default::default() + } + ); + }, + other => panic!("Expected SetPaneBorderStyle action, got {:?}", other), + } + } + + #[test] + fn test_set_pane_border_style_without_a_style_clears_the_override() { + let cli_action = CliAction::SetPaneBorderStyle { + pane_id: "3".to_string(), + border_style: None, + }; + let actions = + Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None).unwrap(); + match &actions[0] { + Action::SetPaneBorderStyle { border_style, .. } => { + assert!(border_style.is_empty()); + }, + other => panic!("Expected SetPaneBorderStyle action, got {:?}", other), + } + } + + #[test] + fn test_set_pane_border_style_with_a_malformed_pane_id_is_an_error() { + let cli_action = CliAction::SetPaneBorderStyle { + pane_id: "not_a_pane".to_string(), + border_style: None, + }; + assert!( + Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None).is_err() + ); + } + + #[test] + fn test_change_floating_pane_coordinates_with_border_style() { + let cli_action = CliAction::ChangeFloatingPaneCoordinates { + pane_id: "terminal_2".to_string(), + x: Some("10".to_string()), + y: None, + width: None, + height: None, + pinned: None, + borderless: None, + border_style: Some("double".to_string()), + }; + let actions = + Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None).unwrap(); + match &actions[0] { + Action::ChangeFloatingPaneCoordinates { coordinates, .. } => { + assert_eq!( + coordinates.border_style, + Some(BorderStyleOverride { + all: Some(crate::data::LineStyle::Double), + ..Default::default() + }) + ); + }, + other => panic!( + "Expected ChangeFloatingPaneCoordinates action, got {:?}", + other + ), + } + } + // Tab-targeting for pane creation commands #[test] @@ -3703,6 +3973,7 @@ mod tests { no_focus: false, borderless: None, tab_id: Some(3), + border_style: None, }; let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); assert!(result.is_ok()); @@ -3747,6 +4018,7 @@ mod tests { no_focus: false, borderless: None, tab_id: None, + border_style: None, }; let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); assert!(result.is_ok()); @@ -3791,6 +4063,7 @@ mod tests { no_focus: false, borderless: None, tab_id: None, + border_style: None, }; let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); assert!(result.is_ok()); @@ -3837,6 +4110,7 @@ mod tests { no_focus: false, borderless: None, tab_id: None, + border_style: None, }; let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); assert!(result.is_err()); @@ -3874,6 +4148,7 @@ mod tests { no_focus: false, borderless: None, tab_id: Some(5), + border_style: None, }; let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); assert!(result.is_ok()); @@ -3918,6 +4193,7 @@ mod tests { no_focus: false, borderless: None, tab_id: Some(1), + border_style: None, }; let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); assert!(result.is_ok()); @@ -3962,6 +4238,7 @@ mod tests { no_focus: false, borderless: None, tab_id: Some(2), + border_style: None, }; let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); assert!(result.is_ok()); @@ -3994,6 +4271,7 @@ mod tests { no_focus: false, borderless: None, tab_id: Some(4), + border_style: None, }; let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); assert!(result.is_ok()); @@ -4026,6 +4304,7 @@ mod tests { no_focus: false, borderless: None, tab_id: None, + border_style: None, }; let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); assert!(result.is_ok()); @@ -4070,6 +4349,7 @@ mod tests { no_focus: false, borderless: None, tab_id: Some(2), + border_style: None, }; let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); assert!(result.is_ok()); @@ -4114,6 +4394,7 @@ mod tests { no_focus: false, borderless: None, tab_id: Some(1), + border_style: None, }; let result = Action::actions_from_cli(cli_action, Box::new(|| PathBuf::from("/tmp")), None); assert!(result.is_ok()); diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs index 8ad16db13..d4421bb83 100644 --- a/zellij-utils/src/input/config.rs +++ b/zellij-utils/src/input/config.rs @@ -207,15 +207,32 @@ impl Config { None => self.themes.get_theme("default").map(|theme| theme.palette), } } - /// Gets default configuration from assets + /// Gets default configuration from assets, layered with the configuration bundled by the + /// running distribution (if any) pub fn from_default_assets() -> ConfigResult { let cfg = String::from_utf8(setup::DEFAULT_CONFIG.to_vec())?; - match Self::from_kdl(&cfg, None) { - Ok(config) => Ok(config), - Err(ConfigError::KdlError(kdl_error)) => Err(ConfigError::KdlError( - kdl_error.add_src("Default built-in-configuration".into(), cfg), - )), - Err(e) => Err(e), + let config = match Self::from_kdl(&cfg, None) { + Ok(config) => config, + Err(ConfigError::KdlError(kdl_error)) => { + return Err(ConfigError::KdlError( + kdl_error.add_src("Default built-in-configuration".into(), cfg), + )) + }, + Err(e) => return Err(e), + }; + let distribution = crate::distribution::distribution(); + match distribution.config { + Some(distribution_config) => match Self::from_kdl(distribution_config, Some(config)) { + Ok(config) => Ok(config), + Err(ConfigError::KdlError(kdl_error)) => { + Err(ConfigError::KdlError(kdl_error.add_src( + format!("{} bundled configuration", distribution.name), + distribution_config.to_owned(), + ))) + }, + Err(e) => Err(e), + }, + None => Ok(config), } } pub fn from_path(path: &PathBuf, default_config: Option) -> ConfigResult { @@ -1409,6 +1426,7 @@ mod config_test { pane_frames: FrameConfig { rounded_corners: true, hide_session_name: true, + ..Default::default() }, }; assert_eq!(config.ui, expected_ui_config, "Ui config defined in config"); diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index 08287d42d..04d6302fa 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -11,7 +11,10 @@ #[cfg(not(target_family = "wasm"))] use crate::downloader::Downloader; use crate::{ - data::{Direction, LayoutInfo, LayoutMetadata, LayoutParsingError, LayoutWithError}, + data::{ + BorderStyleOverride, Direction, LayoutInfo, LayoutMetadata, LayoutParsingError, + LayoutWithError, + }, home::{default_layout_dir, find_default_config_dir}, input::{ command::RunCommand, @@ -789,6 +792,8 @@ pub struct FloatingPaneLayout { pub y: Option, pub pinned: Option, pub borderless: Option, + #[serde(default)] + pub border_style: Option, pub run: Option, pub focus: Option, pub already_running: bool, @@ -808,6 +813,7 @@ impl FloatingPaneLayout { y: None, pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -838,6 +844,7 @@ impl From<&TiledPaneLayout> for FloatingPaneLayout { name: pane_layout.name.clone(), run: pane_layout.run.clone(), focus: pane_layout.focus, + border_style: pane_layout.border_style, ..Default::default() } } @@ -851,6 +858,8 @@ pub struct TiledPaneLayout { pub split_size: Option, pub run: Option, pub borderless: Option, + #[serde(default)] + pub border_style: Option, pub focus: Option, pub external_children_index: Option, pub children_are_stacked: bool, @@ -1606,6 +1615,21 @@ impl Layout { // TODO: ideally these should not be hard-coded // we should load layouts by name from the config // and load them from a hashmap or some such + if let Some(layout) = path + .to_str() + .and_then(|name| crate::distribution::builtin_layout(name)) + { + return Ok(( + format!("{} layout", layout.name), + layout.layout.to_owned(), + layout.swap_layout.map(|swap_layout| { + ( + format!("{} swap layout", layout.name), + swap_layout.to_owned(), + ) + }), + )); + } match path.to_str() { Some("default") => Ok(( "Default layout".into(), diff --git a/zellij-utils/src/input/plugins.rs b/zellij-utils/src/input/plugins.rs index 47ea21c32..448bb907a 100644 --- a/zellij-utils/src/input/plugins.rs +++ b/zellij-utils/src/input/plugins.rs @@ -8,10 +8,8 @@ use serde::{Deserialize, Serialize}; use url::Url; use super::layout::{PluginUserConfiguration, RunPlugin, RunPluginLocation}; -#[cfg(not(target_family = "wasm"))] -use crate::consts::ASSET_MAP; -use crate::consts::BUILTIN_PLUGIN_NAMES; pub use crate::data::PluginTag; +use crate::distribution; use crate::errors::prelude::*; #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] @@ -58,7 +56,7 @@ impl PluginConfig { }), RunPluginLocation::Zellij(tag) => { let tag = tag.to_string(); - if BUILTIN_PLUGIN_NAMES.contains(&tag.as_str()) { + if distribution::is_builtin_plugin_name(&tag) { Some(PluginConfig { path: PathBuf::from(&tag), _allow_exec_host_cmd: run_plugin._allow_exec_host_cmd, @@ -82,9 +80,10 @@ impl PluginConfig { } /// Resolve wasm plugin bytes for the plugin path and given plugin directory. /// - /// If zellij was built without the 'disable_automatic_asset_installation' feature, builtin - /// plugins (Starting with 'zellij:' in the layout file) are loaded directly from the - /// binary-internal asset map. Otherwise: + /// Builtin plugins (starting with 'zellij:' in the layout file) are bundled into the + /// executable by the running distribution (see [`crate::distribution`]) and are loaded + /// directly from memory. Builds using the 'disable_automatic_asset_installation' feature + /// embed no plugin bytes, in which case: /// /// Attempts to first resolve the plugin path as an absolute path, then adds a ".wasm" /// extension to the path and resolves that, then the plugin directory joined with the path @@ -103,6 +102,24 @@ impl PluginConfig { let err_context = |err: std::io::Error, path: &PathBuf| format!("{}: '{}'", err, path.display()); + if self.is_builtin() { + if let Some(name) = self.path.file_stem().and_then(|stem| stem.to_str()) { + if let Some(bytes) = distribution::builtin_plugin_bytes(name) { + log::debug!("Loaded plugin '{}' from internal assets", name); + + if plugin_dir.join(name).with_extension("wasm").exists() { + log::info!( + "Plugin '{}' exists in the 'PLUGIN DIR' at '{}' but is being ignored", + name, + plugin_dir.display() + ); + } + + return Ok(bytes.to_vec()); + } + } + } + // Locations we check for valid plugins #[allow(unused_mut)] let mut paths: Vec = vec![ @@ -128,26 +145,6 @@ impl PluginConfig { // spell it out right here. let mut last_err: Result> = Err(anyhow!("failed to load plugin from disk")); for path in paths { - // Check if the plugin path matches an entry in the asset map. If so, load it directly - // from memory, don't bother with the disk. - #[cfg(not(target_family = "wasm"))] - if !cfg!(feature = "disable_automatic_asset_installation") && self.is_builtin() { - let asset_path = PathBuf::from("plugins").join(&path); - if let Some(bytes) = ASSET_MAP.get(&asset_path) { - log::debug!("Loaded plugin '{}' from internal assets", path.display()); - - if plugin_dir.join(&path).with_extension("wasm").exists() { - log::info!( - "Plugin '{}' exists in the 'PLUGIN DIR' at '{}' but is being ignored", - path.display(), - plugin_dir.display() - ); - } - - return Ok(bytes.to_vec()); - } - } - // Try to read from disk match fs::read(&path) { Ok(val) => { @@ -193,7 +190,7 @@ impl PluginConfig { self.path .file_stem() .and_then(|stem| stem.to_str()) - .map(|name| BUILTIN_PLUGIN_NAMES.contains(&name)) + .map(|name| distribution::is_builtin_plugin_name(name)) .unwrap_or(false) } } diff --git a/zellij-utils/src/input/theme.rs b/zellij-utils/src/input/theme.rs index b4fbc630e..7f15283f3 100644 --- a/zellij-utils/src/input/theme.rs +++ b/zellij-utils/src/input/theme.rs @@ -7,7 +7,7 @@ use std::{ fmt, }; -use crate::data::Styling; +use crate::data::{BorderStyle, BorderStyleOverride, Styling}; #[derive(Debug, Default, Clone, Copy, PartialEq, Deserialize, Serialize)] pub struct UiConfig { @@ -26,6 +26,10 @@ impl UiConfig { pub struct FrameConfig { pub rounded_corners: bool, pub hide_session_name: bool, + #[serde(default)] + pub border_style: BorderStyleOverride, + #[serde(default)] + pub floating_border_style: BorderStyleOverride, } impl FrameConfig { @@ -33,8 +37,20 @@ impl FrameConfig { let mut merged = self.clone(); merged.rounded_corners = other.rounded_corners; merged.hide_session_name = other.hide_session_name; + merged.border_style = merged.border_style.merge(&other.border_style); + merged.floating_border_style = merged + .floating_border_style + .merge(&other.floating_border_style); merged } + pub fn resolved_border_style(&self) -> BorderStyle { + self.border_style + .apply_to(BorderStyle::with_rounded_corners(self.rounded_corners)) + } + pub fn resolved_floating_border_style(&self) -> BorderStyle { + self.floating_border_style + .apply_to(self.resolved_border_style()) + } } #[derive(Clone, PartialEq, Default, Serialize, Deserialize)] diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs index 52b716133..59aee8340 100644 --- a/zellij-utils/src/input/unit/layout_test.rs +++ b/zellij-utils/src/input/unit/layout_test.rs @@ -46,6 +46,47 @@ fn layout_with_one_pane() { assert_eq!(layout, expected_layout); } +#[test] +fn layout_with_border_styles() { + use crate::data::{BorderStyleOverride, LineStyle}; + let kdl_layout = r#" + layout { + pane border_style="double" border_top="heavy" rounded_corners=true + floating_panes { + pane border_style="dashed" + } + } + "#; + let layout = Layout::from_kdl(kdl_layout, Some("layout_file_name".into()), None, None).unwrap(); + let (tiled, floating) = layout.template.clone().unwrap(); + assert_eq!( + tiled.children[0].border_style, + Some(BorderStyleOverride { + all: Some(LineStyle::Double), + top: Some(LineStyle::Heavy), + rounded_corners: Some(true), + ..Default::default() + }) + ); + assert_eq!( + floating[0].border_style, + Some(BorderStyleOverride { + all: Some(LineStyle::Dashed), + ..Default::default() + }) + ); +} + +#[test] +fn layout_with_invalid_border_style() { + let kdl_layout = r#" + layout { + pane border_style="squiggly" + } + "#; + assert!(Layout::from_kdl(kdl_layout, Some("layout_file_name".into()), None, None).is_err()); +} + #[test] fn layout_with_multiple_panes() { let kdl_layout = r#" diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap index bb4010cb8..6b09abc25 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_added_to_args_in_template.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1410 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [], @@ -31,6 +30,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -64,6 +64,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -79,6 +80,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap index 9830de489..200430f5a 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__args_override_args_in_template.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1375 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [], @@ -34,6 +33,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -67,6 +67,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -82,6 +83,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_a_stack_with_an_expanded_pane.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_a_stack_with_an_expanded_pane.snap index 688f2b80e..0a472d9d5 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_a_stack_with_an_expanded_pane.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_a_stack_with_an_expanded_pane.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1984 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [], @@ -23,6 +22,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -41,6 +41,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -59,6 +60,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -74,6 +76,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: true, @@ -89,6 +92,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_stacked_children_for_pane_node.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_stacked_children_for_pane_node.snap index debfb03fd..3561152bb 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_stacked_children_for_pane_node.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_stacked_children_for_pane_node.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1952 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [], @@ -23,6 +22,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -41,6 +41,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -56,6 +57,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: true, @@ -71,6 +73,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_stacked_children_for_pane_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_stacked_children_for_pane_template.snap index 35c767a67..69cf97884 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_stacked_children_for_pane_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_define_stacked_children_for_pane_template.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1969 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [], @@ -27,6 +26,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -45,6 +45,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -60,6 +61,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: true, @@ -75,6 +77,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: true, @@ -90,6 +93,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_load_swap_layouts_from_a_different_file.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_load_swap_layouts_from_a_different_file.snap index a1fcdacce..cdbbdd8ac 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_load_swap_layouts_from_a_different_file.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_load_swap_layouts_from_a_different_file.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1938 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [], @@ -19,6 +18,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -34,6 +34,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -88,6 +89,7 @@ Layout { borderless: Some( true, ), + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -114,6 +116,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -132,6 +135,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: Some( 0, @@ -149,6 +153,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -164,6 +169,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -205,6 +211,7 @@ Layout { borderless: Some( true, ), + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -220,6 +227,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -267,6 +275,7 @@ Layout { borderless: Some( true, ), + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -293,6 +302,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: Some( 0, @@ -317,6 +327,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -335,6 +346,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -353,6 +365,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -371,6 +384,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -386,6 +400,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -401,6 +416,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -416,6 +432,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -457,6 +474,7 @@ Layout { borderless: Some( true, ), + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -472,6 +490,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -519,6 +538,7 @@ Layout { borderless: Some( true, ), + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -545,6 +565,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: Some( 0, @@ -569,6 +590,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -587,6 +609,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -605,6 +628,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -623,6 +647,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -638,6 +663,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -660,6 +686,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -678,6 +705,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -696,6 +724,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -714,6 +743,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -729,6 +759,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -744,6 +775,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -759,6 +791,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -800,6 +833,7 @@ Layout { borderless: Some( true, ), + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -815,6 +849,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap index 56ba1f222..7d25cc1ca 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_pane_template.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1033 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [], @@ -29,6 +28,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -51,6 +51,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -69,6 +70,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -84,6 +86,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -99,6 +102,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -117,6 +121,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -132,6 +137,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -158,6 +164,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -173,6 +180,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: Some( 1, @@ -193,6 +201,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -208,6 +217,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -223,6 +233,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_tab_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_tab_template.snap index 3ae6e4a9c..997acdfaa 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_tab_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__children_not_as_first_child_of_tab_template.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 985 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -24,6 +23,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -46,6 +46,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -64,6 +65,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -79,6 +81,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -94,6 +97,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -112,6 +116,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -127,6 +132,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -157,6 +163,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -172,6 +179,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: Some( 1, @@ -192,6 +200,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -207,6 +216,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -231,6 +241,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap index 8ba07ad39..59835811a 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_added_to_close_on_exit_in_template.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1427 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [], @@ -31,6 +30,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -61,6 +61,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -76,6 +77,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap index 032bd843b..59835811a 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__close_on_exit_overrides_close_on_exit_in_template.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1393 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [], @@ -31,6 +30,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -61,6 +61,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -76,6 +77,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__combined_tab_and_pane_template_both_with_children.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__combined_tab_and_pane_template_both_with_children.snap index 6e99b11c9..5b70f3bc1 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__combined_tab_and_pane_template_both_with_children.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__combined_tab_and_pane_template_both_with_children.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1085 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -24,6 +23,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -48,6 +48,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -63,6 +64,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -81,6 +83,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -96,6 +99,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -118,6 +122,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -136,6 +141,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -151,6 +157,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -166,6 +173,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -196,6 +204,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -220,6 +229,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -235,6 +245,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -253,6 +264,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -268,6 +280,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -286,6 +299,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -301,6 +315,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -325,6 +340,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap index c2b277c1f..99328a34b 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_added_to_cwd_in_template.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1462 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [], @@ -31,6 +30,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -63,6 +63,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -78,6 +79,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap index 303764157..3ae8644b5 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__cwd_override_cwd_in_template.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1445 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [], @@ -33,6 +32,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -65,6 +65,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -80,6 +81,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__env_var_expansion.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__env_var_expansion.snap index 30b301da4..6d1a76c2a 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__env_var_expansion.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__env_var_expansion.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 2230 -expression: "format!(\"{layout:#?}\")" +expression: "normalize_layout_debug(format!(\"{layout:#?}\"))" --- Layout { tabs: [ @@ -22,6 +21,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -44,6 +44,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -66,6 +67,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -88,6 +90,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -120,6 +123,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -146,6 +150,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -172,6 +177,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -204,6 +210,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -230,6 +237,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -249,6 +257,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -281,6 +290,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -303,6 +313,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -325,6 +336,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -347,6 +359,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -379,6 +392,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -405,6 +419,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -431,6 +446,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -463,6 +479,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -489,6 +506,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -508,6 +526,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd.snap index 4fa28d02a..fa1aadf30 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1847 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -22,6 +21,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -54,6 +54,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -73,6 +74,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -97,6 +99,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_pane_templates.snap index 36b254dad..d9473fe0f 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_pane_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_pane_templates.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1868 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -26,6 +25,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -58,6 +58,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -84,6 +85,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -103,6 +105,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -122,6 +125,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -141,6 +145,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -165,6 +170,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_tab_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_tab_templates.snap index 248d46827..0bacbd09b 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_tab_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_and_tab_cwd_prepended_to_panes_with_and_without_cwd_in_tab_templates.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1887 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -22,6 +21,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -54,6 +54,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -80,6 +81,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -99,6 +101,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -118,6 +121,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -142,6 +146,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap index b6171a495..416906e9d 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_given_to_panes_without_cwd.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1732 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -22,6 +21,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -54,6 +54,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -73,6 +74,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -105,6 +107,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -137,6 +140,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -156,6 +160,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap index aaca18c62..303cf5755 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1765 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [], @@ -23,6 +22,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -55,6 +55,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -74,6 +75,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap index d5067f75a..c7019b2b2 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_passed_from_layout_constructor_overrides_global_cwd_in_layout_file.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1786 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -22,6 +21,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -54,6 +54,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -73,6 +74,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -105,6 +107,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -137,6 +140,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -156,6 +160,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap index 1666c83bf..9c46d0b2f 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_prepended_to_panes_with_cwd.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1745 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -22,6 +21,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -54,6 +54,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -73,6 +74,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -105,6 +107,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -137,6 +140,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -156,6 +160,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_with_tab_cwd_given_to_panes_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_with_tab_cwd_given_to_panes_without_cwd.snap index e745a99b0..be87fd3a5 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_with_tab_cwd_given_to_panes_without_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__global_cwd_with_tab_cwd_given_to_panes_without_cwd.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1802 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -22,6 +21,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -54,6 +54,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -73,6 +74,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -97,6 +99,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap index baf0eca48..a48e1eeb6 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_close_on_exit.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 511 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [], @@ -31,6 +30,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -46,6 +46,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_start_suspended.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_start_suspended.snap index 481a74d89..cc67b70b9 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_start_suspended.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_command_panes_and_start_suspended.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 524 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [], @@ -31,6 +30,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -46,6 +46,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_default_tab_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_default_tab_template.snap index abddb5c9d..60562ae86 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_default_tab_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_default_tab_template.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 819 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -20,6 +19,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -42,6 +42,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -60,6 +61,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -75,6 +77,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -93,6 +96,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -108,6 +112,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -136,6 +141,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -158,6 +164,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -176,6 +183,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -191,6 +199,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -209,6 +218,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -224,6 +234,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -250,6 +261,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -268,6 +280,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -286,6 +299,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -301,6 +315,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -329,6 +344,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -347,6 +363,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -365,6 +382,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -380,6 +398,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_branched_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_branched_pane_templates.snap index 3d267afa0..bc97cb533 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_branched_pane_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_branched_pane_templates.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 939 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [], @@ -23,6 +22,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -41,6 +41,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -63,6 +64,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -81,6 +83,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -96,6 +99,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -118,6 +122,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -136,6 +141,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -154,6 +160,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -169,6 +176,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -184,6 +192,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -199,6 +208,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_pane_templates.snap index 6f24f2eeb..37cf739cc 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_pane_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_nested_pane_templates.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 913 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [], @@ -23,6 +22,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -41,6 +41,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -63,6 +64,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -81,6 +83,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -96,6 +99,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -114,6 +118,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -129,6 +134,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -144,6 +150,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_new_tab_template.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_new_tab_template.snap index 3eb32a534..25c04680c 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_new_tab_template.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_new_tab_template.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 844 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -20,6 +19,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -38,6 +38,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -53,6 +54,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -81,6 +83,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -99,6 +102,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -114,6 +118,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -136,6 +141,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -168,6 +174,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -186,6 +193,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -201,6 +209,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -216,6 +225,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_excluded_from_sync.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_excluded_from_sync.snap index a14262ee9..8bca4df02 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_excluded_from_sync.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_excluded_from_sync.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1096 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [], @@ -19,6 +18,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -36,6 +36,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_templates.snap index 43965e114..ed876b075 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_pane_templates.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 871 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [], @@ -23,6 +22,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -45,6 +45,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -60,6 +61,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -78,6 +80,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -93,6 +96,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -115,6 +119,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -137,6 +142,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -155,6 +161,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -170,6 +177,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -188,6 +196,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -203,6 +212,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -225,6 +235,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -247,6 +258,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -265,6 +277,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -280,6 +293,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -298,6 +312,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -313,6 +328,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -335,6 +351,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -353,6 +370,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -371,6 +389,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -386,6 +405,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -401,6 +421,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap index edc76b2ee..9a8db02ef 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tab_and_pane_templates.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 892 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -22,6 +21,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -56,6 +56,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -71,6 +72,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -89,6 +91,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -104,6 +107,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -119,6 +123,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -143,6 +148,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tabs_and_floating_panes.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tabs_and_floating_panes.snap index 9e7bf8af8..134117b37 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tabs_and_floating_panes.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__layout_with_tabs_and_floating_panes.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 305 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -18,6 +17,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -33,6 +33,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -53,6 +54,7 @@ Layout { y: None, pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -76,6 +78,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -91,6 +94,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -111,6 +115,7 @@ Layout { y: None, pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -127,6 +132,7 @@ Layout { y: None, pinned: None, borderless: None, + border_style: None, run: None, focus: None, already_running: false, @@ -148,6 +154,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap index f6ffefa06..e346e266e 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_is_overriden_by_its_consumers_bare_cwd.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1616 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -32,6 +31,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -51,6 +51,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -93,6 +94,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -112,6 +114,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap index 7e3cb306d..b56e6f07f 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_overriden_by_its_consumers_command_cwd.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1562 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -32,6 +31,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -51,6 +51,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -93,6 +94,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -112,6 +114,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap index 91e2f3582..c560ec9f5 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_with_cwd_remains_when_its_consumer_command_does_not_have_a_cwd.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1579 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -32,6 +31,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -51,6 +51,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -93,6 +94,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -112,6 +114,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap index 2d0ca3604..c560ec9f5 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_is_overriden_by_its_consumers_cwd.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1597 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -32,6 +31,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -51,6 +51,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -93,6 +94,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -112,6 +114,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap index fdbf56a3a..e346e266e 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_command_without_cwd_receives_its_consumers_bare_cwd.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1634 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -32,6 +31,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -51,6 +51,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -93,6 +94,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -112,6 +114,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap index 5bb6cceca..0d32be8f5 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_cwd_overriden_by_its_consumers_bare_cwd.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1652 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -22,6 +21,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -41,6 +41,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -73,6 +74,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -92,6 +94,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap index 4e8812fa5..e346e266e 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_with_cwd.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1686 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -32,6 +31,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -51,6 +51,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -93,6 +94,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -112,6 +114,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap index 5ea6f110c..1542be0d5 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_command_without_cwd.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1668 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -32,6 +31,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -51,6 +51,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -93,6 +94,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -112,6 +114,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_edit.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_edit.snap index 1913b50f5..72391a0b3 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_edit.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_bare_propagated_to_its_consumer_edit.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1702 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -26,6 +25,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -45,6 +45,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -81,6 +82,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -100,6 +102,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_command_propagated_to_its_consumer_edit.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_command_propagated_to_its_consumer_edit.snap index f6bbaeed6..e214e3e95 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_command_propagated_to_its_consumer_edit.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__pane_template_with_command_propagated_to_its_consumer_edit.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1718 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -26,6 +25,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -45,6 +45,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -81,6 +82,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -100,6 +102,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_given_to_panes_without_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_given_to_panes_without_cwd.snap index 8a8e5420d..2152b1d3e 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_given_to_panes_without_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_given_to_panes_without_cwd.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1817 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -22,6 +21,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -54,6 +54,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -73,6 +74,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -97,6 +99,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_prepended_to_panes_with_cwd.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_prepended_to_panes_with_cwd.snap index 1b37491ef..385dcf218 100644 --- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_prepended_to_panes_with_cwd.snap +++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__tab_cwd_prepended_to_panes_with_cwd.snap @@ -1,7 +1,6 @@ --- source: zellij-utils/src/input/./unit/layout_test.rs -assertion_line: 1832 -expression: "format!(\"{:#?}\", layout)" +expression: "normalize_layout_debug(format!(\"{:#?}\", layout))" --- Layout { tabs: [ @@ -22,6 +21,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -54,6 +54,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -73,6 +74,7 @@ Layout { ), ), borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, @@ -97,6 +99,7 @@ Layout { split_size: None, run: None, borderless: None, + border_style: None, focus: None, external_children_index: None, children_are_stacked: false, diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs index ee07fa219..9ffa14ea1 100644 --- a/zellij-utils/src/ipc.rs +++ b/zellij-utils/src/ipc.rs @@ -12,6 +12,7 @@ use std::{ fmt::{Display, Error, Formatter}, io::{self, Read, Write}, marker::PhantomData, + time::Duration, }; // Protobuf imports @@ -31,6 +32,7 @@ type SessionId = u64; /// A bidirectional byte stream that supports cloning for simultaneous read/write. pub trait IpcStream: Read + Write + Send + 'static { fn try_clone_stream(&self) -> io::Result>; + fn set_read_timeout(&self, timeout: Option) -> io::Result<()>; } impl IpcStream for LocalSocketStream { @@ -38,6 +40,10 @@ impl IpcStream for LocalSocketStream { use interprocess::TryClone; Ok(Box::new(self.try_clone()?)) } + fn set_read_timeout(&self, timeout: Option) -> io::Result<()> { + use interprocess::local_socket::traits::Stream; + self.set_recv_timeout(timeout) + } } #[derive(PartialEq, Eq, Serialize, Deserialize, Hash)] @@ -490,6 +496,10 @@ where let socket = self.receiver.get_ref().try_clone_stream().unwrap(); IpcSenderWithContext::from_boxed(socket) } + + pub fn set_read_timeout(&self, timeout: Option) -> io::Result<()> { + self.receiver.get_ref().set_read_timeout(timeout) + } } // Protobuf wire format utilities diff --git a/zellij-utils/src/ipc/protobuf_conversion.rs b/zellij-utils/src/ipc/protobuf_conversion.rs index 1e77172e3..c0410cd19 100644 --- a/zellij-utils/src/ipc/protobuf_conversion.rs +++ b/zellij-utils/src/ipc/protobuf_conversion.rs @@ -1,5 +1,7 @@ use std::str::FromStr; +use crate::data::ClientId; + use crate::{ client_server_contract::client_server_contract::{ client_to_server_msg, server_to_client_msg, ActionMsg, AttachClientMsg, @@ -201,7 +203,11 @@ impl TryFrom for ClientToServerMsg { match msg.message { Some(client_to_server_msg::Message::DetachSession(detach)) => { Ok(ClientToServerMsg::DetachSession { - client_ids: detach.client_ids.into_iter().map(|id| id as u16).collect(), + client_ids: detach + .client_ids + .into_iter() + .map(|id| id as ClientId) + .collect(), }) }, Some(client_to_server_msg::Message::TerminalPixelDimensions(pixel_dims)) => { @@ -274,7 +280,7 @@ impl TryFrom for ClientToServerMsg { .ok_or_else(|| anyhow!("Missing action"))? .try_into()?, terminal_id: action.terminal_id, - client_id: action.client_id.map(|id| id as u16), + client_id: action.client_id.map(|id| id as ClientId), is_cli_client: action.is_cli_client, }), Some(client_to_server_msg::Message::Key(key)) => Ok(ClientToServerMsg::Key { @@ -1164,6 +1170,10 @@ impl From fn from(action: crate::input::actions::Action) -> Self { use crate::client_server_contract::client_server_contract::{ action::ActionType, + ApplyFloatingSwapLayoutAction, + ApplyFloatingSwapLayoutByTabIdAction, + ApplyTiledSwapLayoutAction, + ApplyTiledSwapLayoutByTabIdAction, AreFloatingPanesVisibleAction, BreakPaneAction, BreakPaneLeftAction, @@ -1276,6 +1286,7 @@ impl From SelectCommandAtScrollPositionAction, SetDarkThemeAction, SetLightThemeAction, + SetPaneBorderStyleAction, SetPaneBorderlessAction, SetPaneColorAction, SetPaneFrameStyleAction, @@ -1554,6 +1565,7 @@ impl From near_current_pane, no_focus, borderless, + border_style, tab_id, .. } => ActionType::NewTiledPane(NewTiledPaneAction { @@ -1562,6 +1574,7 @@ impl From pane_name, near_current_pane, borderless, + border_style: border_style.map(|b| b.into()), tab_id: tab_id.map(|t| t as u32), no_focus, }), @@ -1813,6 +1826,12 @@ impl From crate::input::actions::Action::NextSwapLayout => { ActionType::NextSwapLayout(NextSwapLayoutAction {}) }, + crate::input::actions::Action::ApplyTiledSwapLayout { name } => { + ActionType::ApplyTiledSwapLayout(ApplyTiledSwapLayoutAction { name }) + }, + crate::input::actions::Action::ApplyFloatingSwapLayout { name } => { + ActionType::ApplyFloatingSwapLayout(ApplyFloatingSwapLayoutAction { name }) + }, crate::input::actions::Action::OverrideLayout { tabs, retain_existing_terminal_panes, @@ -2050,6 +2069,13 @@ impl From pane_id: Some(pane_id.into()), borderless, }), + crate::input::actions::Action::SetPaneBorderStyle { + pane_id, + border_style, + } => ActionType::SetPaneBorderStyle(SetPaneBorderStyleAction { + pane_id: Some(pane_id.into()), + border_style: Some(border_style.into()), + }), crate::input::actions::Action::TogglePaneInGroup => { ActionType::TogglePaneInGroup(TogglePaneInGroupAction {}) }, @@ -2217,6 +2243,18 @@ impl From crate::input::actions::Action::NextSwapLayoutByTabId { id } => { ActionType::NextSwapLayoutByTabId(NextSwapLayoutByTabIdAction { id }) }, + crate::input::actions::Action::ApplyTiledSwapLayoutByTabId { id, name } => { + ActionType::ApplyTiledSwapLayoutByTabId(ApplyTiledSwapLayoutByTabIdAction { + id, + name, + }) + }, + crate::input::actions::Action::ApplyFloatingSwapLayoutByTabId { id, name } => { + ActionType::ApplyFloatingSwapLayoutByTabId(ApplyFloatingSwapLayoutByTabIdAction { + id, + name, + }) + }, crate::input::actions::Action::MoveTabByTabId { id, direction } => { ActionType::MoveTabByTabId(MoveTabByTabIdAction { id, @@ -2459,6 +2497,7 @@ impl TryFrom near_current_pane: new_tiled_action.near_current_pane, no_focus: new_tiled_action.no_focus, borderless: new_tiled_action.borderless, + border_style: new_tiled_action.border_style.map(|b| b.into()), tab_id: new_tiled_action.tab_id.map(|t| t as usize), }) }, @@ -2718,6 +2757,12 @@ impl TryFrom Ok(crate::input::actions::Action::PreviousSwapLayout) }, ActionType::NextSwapLayout(_) => Ok(crate::input::actions::Action::NextSwapLayout), + ActionType::ApplyTiledSwapLayout(a) => { + Ok(crate::input::actions::Action::ApplyTiledSwapLayout { name: a.name }) + }, + ActionType::ApplyFloatingSwapLayout(a) => { + Ok(crate::input::actions::Action::ApplyFloatingSwapLayout { name: a.name }) + }, ActionType::OverrideLayout(override_layout_action) => { Ok(crate::input::actions::Action::OverrideLayout { tabs: override_layout_action @@ -2980,6 +3025,18 @@ impl TryFrom borderless: set_borderless_action.borderless, }) }, + ActionType::SetPaneBorderStyle(set_border_style_action) => { + Ok(crate::input::actions::Action::SetPaneBorderStyle { + pane_id: set_border_style_action + .pane_id + .ok_or_else(|| anyhow!("SetPaneBorderStyle missing pane_id"))? + .try_into()?, + border_style: set_border_style_action + .border_style + .map(|b| b.into()) + .unwrap_or_default(), + }) + }, ActionType::TogglePaneInGroup(_) => { Ok(crate::input::actions::Action::TogglePaneInGroup) }, @@ -3194,6 +3251,18 @@ impl TryFrom ActionType::NextSwapLayoutByTabId(a) => { Ok(crate::input::actions::Action::NextSwapLayoutByTabId { id: a.id }) }, + ActionType::ApplyTiledSwapLayoutByTabId(a) => { + Ok(crate::input::actions::Action::ApplyTiledSwapLayoutByTabId { + id: a.id, + name: a.name, + }) + }, + ActionType::ApplyFloatingSwapLayoutByTabId(a) => Ok( + crate::input::actions::Action::ApplyFloatingSwapLayoutByTabId { + id: a.id, + name: a.name, + }, + ), ActionType::MoveTabByTabId(a) => { let direction = proto_i32_to_direction(a.direction)?; Ok(crate::input::actions::Action::MoveTabByTabId { @@ -3819,6 +3888,62 @@ impl TryFrom i32 { + use crate::client_server_contract::client_server_contract::LineStyle as ProtoLineStyle; + let proto = match line_style { + crate::data::LineStyle::Single => ProtoLineStyle::Single, + crate::data::LineStyle::Double => ProtoLineStyle::Double, + crate::data::LineStyle::Heavy => ProtoLineStyle::Heavy, + crate::data::LineStyle::Dashed => ProtoLineStyle::Dashed, + crate::data::LineStyle::HeavyDashed => ProtoLineStyle::HeavyDashed, + }; + proto as i32 +} + +fn proto_i32_to_line_style(value: i32) -> crate::data::LineStyle { + use crate::client_server_contract::client_server_contract::LineStyle as ProtoLineStyle; + match ProtoLineStyle::try_from(value) { + Ok(ProtoLineStyle::Single) => crate::data::LineStyle::Single, + Ok(ProtoLineStyle::Double) => crate::data::LineStyle::Double, + Ok(ProtoLineStyle::Heavy) => crate::data::LineStyle::Heavy, + Ok(ProtoLineStyle::Dashed) => crate::data::LineStyle::Dashed, + Ok(ProtoLineStyle::HeavyDashed) => crate::data::LineStyle::HeavyDashed, + Err(_) => crate::data::LineStyle::Single, + } +} + +impl From + for crate::client_server_contract::client_server_contract::BorderStyleOverride +{ + fn from(border_style: crate::data::BorderStyleOverride) -> Self { + Self { + all: border_style.all.map(line_style_to_proto_i32), + top: border_style.top.map(line_style_to_proto_i32), + right: border_style.right.map(line_style_to_proto_i32), + bottom: border_style.bottom.map(line_style_to_proto_i32), + left: border_style.left.map(line_style_to_proto_i32), + rounded_corners: border_style.rounded_corners, + } + } +} + +impl From + for crate::data::BorderStyleOverride +{ + fn from( + border_style: crate::client_server_contract::client_server_contract::BorderStyleOverride, + ) -> Self { + Self { + all: border_style.all.map(proto_i32_to_line_style), + top: border_style.top.map(proto_i32_to_line_style), + right: border_style.right.map(proto_i32_to_line_style), + bottom: border_style.bottom.map(proto_i32_to_line_style), + left: border_style.left.map(proto_i32_to_line_style), + rounded_corners: border_style.rounded_corners, + } + } +} + // FloatingPaneCoordinates conversion impl From for crate::client_server_contract::client_server_contract::FloatingPaneCoordinates @@ -3831,6 +3956,7 @@ impl From height: coords.height.map(|h| h.into()), pinned: coords.pinned, borderless: coords.borderless, + border_style: coords.border_style.map(|b| b.into()), } } } @@ -3850,6 +3976,7 @@ impl TryFrom }; let placement_type = match placement { crate::data::NewPanePlacement::NoPreference { - borderless: Some(b), + borderless: None, + border_style: None, + } => PlacementType::NoPreference(true), + crate::data::NewPanePlacement::NoPreference { + borderless, + border_style, } => PlacementType::NoPreferenceWithOptions(NoPreferencePlacement { - borderless: Some(b), - }), - crate::data::NewPanePlacement::NoPreference { borderless: None } => { - PlacementType::NoPreference(true) - }, - crate::data::NewPanePlacement::Tiled { - direction, - borderless: Some(b), - } => PlacementType::TiledWithOptions(TiledPlacement { - direction: direction.map(direction_to_proto_i32), - borderless: Some(b), + borderless, + border_style: border_style.map(|b| b.into()), }), crate::data::NewPanePlacement::Tiled { direction, borderless: None, + border_style: None, } => PlacementType::Tiled(direction.map(direction_to_proto_i32).unwrap_or(0)), + crate::data::NewPanePlacement::Tiled { + direction, + borderless, + border_style, + } => PlacementType::TiledWithOptions(TiledPlacement { + direction: direction.map(direction_to_proto_i32), + borderless, + border_style: border_style.map(|b| b.into()), + }), crate::data::NewPanePlacement::Floating(coords) => { PlacementType::Floating(coords.map(|c| c.into()).unwrap_or_default()) }, @@ -3890,28 +4023,33 @@ impl From pane_id_to_replace, close_replaced_pane, borderless, + border_style, } => PlacementType::InPlace( crate::client_server_contract::client_server_contract::NewPanePlacementInPlace { pane_id_to_replace: pane_id_to_replace.map(|id| id.into()), close_replaced_pane, borderless, + border_style: border_style.map(|b| b.into()), }, ), - crate::data::NewPanePlacement::Stacked { - pane_id_to_stack_under, - borderless: Some(b), - } => PlacementType::StackedWithOptions(StackedPlacement { - pane_id_to_stack_under: pane_id_to_stack_under.map(|id| id.into()), - borderless: Some(b), - }), crate::data::NewPanePlacement::Stacked { pane_id_to_stack_under, borderless: None, + border_style: None, } => PlacementType::Stacked( pane_id_to_stack_under .map(|id| id.into()) .unwrap_or_default(), ), + crate::data::NewPanePlacement::Stacked { + pane_id_to_stack_under, + borderless, + border_style, + } => PlacementType::StackedWithOptions(StackedPlacement { + pane_id_to_stack_under: pane_id_to_stack_under.map(|id| id.into()), + borderless, + border_style: border_style.map(|b| b.into()), + }), }; Self { placement_type: Some(placement_type), @@ -3936,6 +4074,7 @@ impl TryFrom { Ok(crate::data::NewPanePlacement::NoPreference { borderless: opts.borderless, + border_style: opts.border_style.map(|b| b.into()), }) }, PlacementType::TiledWithOptions(opts) => { @@ -3943,6 +4082,7 @@ impl TryFrom { @@ -3953,12 +4093,14 @@ impl TryFrom { - Ok(crate::data::NewPanePlacement::NoPreference { borderless: None }) - }, + PlacementType::NoPreference(_) => Ok(crate::data::NewPanePlacement::NoPreference { + borderless: None, + border_style: None, + }), PlacementType::Tiled(direction) => { let direction = if direction == 0 { None @@ -3968,6 +4110,7 @@ impl TryFrom { @@ -3985,6 +4128,7 @@ impl TryFrom { let pane_id = if pane_id == Default::default() { @@ -3995,6 +4139,7 @@ impl TryFrom pane_initial_contents: layout.pane_initial_contents, default_fg: layout.default_fg, default_bg: layout.default_bg, + border_style: layout.border_style.map(|b| b.into()), } } } @@ -4296,6 +4442,7 @@ impl From borderless: layout.borderless, default_fg: layout.default_fg, default_bg: layout.default_bg, + border_style: layout.border_style.map(|b| b.into()), } } } @@ -4508,6 +4655,7 @@ impl TryFrom KdlLayoutParser<'a> { || word == "swap_floating_layout" || word == "hide_floating_panes" || word == "contents_file" + || word == "border_style" + || word == "border_top" + || word == "border_right" + || word == "border_bottom" + || word == "border_left" + || word == "rounded_corners" } fn is_a_valid_pane_property(&self, property_name: &str) -> bool { property_name == "borderless" @@ -108,6 +115,44 @@ impl<'a> KdlLayoutParser<'a> { || property_name == "contents_file" || property_name == "default_fg" || property_name == "default_bg" + || self.is_a_border_style_property(property_name) + } + fn is_a_border_style_property(&self, property_name: &str) -> bool { + property_name == "border_style" + || property_name == "border_top" + || property_name == "border_right" + || property_name == "border_bottom" + || property_name == "border_left" + || property_name == "rounded_corners" + } + fn parse_border_style( + &self, + kdl_node: &KdlNode, + ) -> Result, ConfigError> { + let parse = |property_name: &str| -> Result, ConfigError> { + match kdl_get_string_property_or_child_value_with_error!(kdl_node, property_name) { + Some(value) => LineStyle::from_str(value).map(Some).map_err(|e| { + ConfigError::new_layout_kdl_error( + e, + kdl_node.span().offset(), + kdl_node.span().len(), + ) + }), + None => Ok(None), + } + }; + let border_style_override = BorderStyleOverride { + all: parse("border_style")?, + top: parse("border_top")?, + right: parse("border_right")?, + bottom: parse("border_bottom")?, + left: parse("border_left")?, + rounded_corners: kdl_get_bool_property_or_child_value_with_error!( + kdl_node, + "rounded_corners" + ), + }; + Ok(border_style_override.none_if_empty()) } fn is_a_valid_floating_pane_property(&self, property_name: &str) -> bool { property_name == "borderless" @@ -128,6 +173,7 @@ impl<'a> KdlLayoutParser<'a> { || property_name == "contents_file" || property_name == "default_fg" || property_name == "default_bg" + || self.is_a_border_style_property(property_name) } fn is_a_valid_tab_property(&self, property_name: &str) -> bool { property_name == "focus" @@ -580,8 +626,10 @@ impl<'a> KdlLayoutParser<'a> { std::fs::read_to_string(parent_folder.join(contents_file)).ok() }) }); + let border_style = self.parse_border_style(kdl_node)?; Ok(TiledPaneLayout { borderless, + border_style, focus, name, split_size, @@ -628,6 +676,7 @@ impl<'a> KdlLayoutParser<'a> { std::fs::read_to_string(parent_folder.join(contents_file)).ok() }) }); + let border_style = self.parse_border_style(kdl_node)?; Ok(FloatingPaneLayout { name, height, @@ -638,6 +687,7 @@ impl<'a> KdlLayoutParser<'a> { focus, pinned, borderless, + border_style, pane_initial_contents, default_fg, default_bg, @@ -767,6 +817,14 @@ impl<'a> KdlLayoutParser<'a> { if let Some(borderless) = borderless { pane_template.borderless = Some(borderless); } + if let Some(border_style) = self.parse_border_style(kdl_node)? { + pane_template.border_style = Some( + pane_template + .border_style + .unwrap_or_default() + .merge(&border_style), + ); + } if let Some(focus) = focus { pane_template.focus = Some(focus); } @@ -882,6 +940,14 @@ impl<'a> KdlLayoutParser<'a> { if let Some(pinned) = pinned { pane_template.pinned = Some(pinned); } + if let Some(border_style) = self.parse_border_style(kdl_node)? { + pane_template.border_style = Some( + pane_template + .border_style + .unwrap_or_default() + .merge(&border_style), + ); + } Ok(pane_template) }, PaneOrFloatingPane::Either(mut pane_template) => { @@ -937,6 +1003,14 @@ impl<'a> KdlLayoutParser<'a> { if let Some(pinned) = pinned { floating_pane.pinned = Some(pinned); } + if let Some(border_style) = self.parse_border_style(kdl_node)? { + floating_pane.border_style = Some( + floating_pane + .border_style + .unwrap_or_default() + .merge(&border_style), + ); + } Ok(floating_pane) }, } @@ -1100,6 +1174,7 @@ impl<'a> KdlLayoutParser<'a> { PaneOrFloatingPane::Either(TiledPaneLayout { focus, run, + border_style: self.parse_border_style(kdl_node)?, ..Default::default() }), kdl_node.clone(), @@ -1124,6 +1199,7 @@ impl<'a> KdlLayoutParser<'a> { x, y, pinned, + border_style: self.parse_border_style(kdl_node)?, ..Default::default() }), kdl_node.clone(), @@ -1134,6 +1210,7 @@ impl<'a> KdlLayoutParser<'a> { // pane properties let borderless = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "borderless"); + let border_style = self.parse_border_style(kdl_node)?; let children_are_stacked = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "stacked") .unwrap_or(false); @@ -1154,6 +1231,7 @@ impl<'a> KdlLayoutParser<'a> { ( PaneOrFloatingPane::Pane(TiledPaneLayout { borderless, + border_style, focus, split_size, run, diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs index 427747a45..aa85ddfa0 100644 --- a/zellij-utils/src/kdl/mod.rs +++ b/zellij-utils/src/kdl/mod.rs @@ -1,9 +1,10 @@ mod kdl_layout_parser; +use crate::data::ClientId; use crate::data::{ - BareKey, Direction, FloatingPaneCoordinates, InputMode, KeyWithModifier, LayoutInfo, - LayoutMetadata, MultiplayerColors, Palette, PaletteColor, PaneId, PaneInfo, PaneManifest, - PermissionType, Resize, SessionInfo, StyleDeclaration, Styling, TabInfo, ThemeHue, WebSharing, - DEFAULT_STYLES, + BareKey, BorderStyleOverride, Direction, FloatingPaneCoordinates, InputMode, KeyWithModifier, + LayoutInfo, LayoutMetadata, LineStyle, MultiplayerColors, Palette, PaletteColor, PaneId, + PaneInfo, PaneManifest, PermissionType, Resize, SessionInfo, StyleDeclaration, Styling, + TabInfo, ThemeHue, WebSharing, DEFAULT_STYLES, }; use crate::envs::EnvironmentVariables; use crate::home::{find_default_config_dir, get_layout_dir}; @@ -464,6 +465,8 @@ impl Action { ) -> Result { match action_name { "WriteChars" => Ok(Action::WriteChars { chars: string }), + "ApplyTiledSwapLayout" => Ok(Action::ApplyTiledSwapLayout { name: string }), + "ApplyFloatingSwapLayout" => Ok(Action::ApplyFloatingSwapLayout { name: string }), "SetPaneFrameStyle" => { let style = PaneFrameStyle::from_str(string.as_str()).map_err(|e| { ConfigError::new_kdl_error( @@ -884,6 +887,7 @@ impl Action { pane_name: name, near_current_pane: false, borderless: _, + border_style: _, .. } => { let mut node = KdlNode::new("Run"); @@ -1282,6 +1286,16 @@ impl Action { Action::ToggleMouseMode => Some(KdlNode::new("ToggleMouseMode")), Action::PreviousSwapLayout => Some(KdlNode::new("PreviousSwapLayout")), Action::NextSwapLayout => Some(KdlNode::new("NextSwapLayout")), + Action::ApplyTiledSwapLayout { name } => { + let mut node = KdlNode::new("ApplyTiledSwapLayout"); + node.push(name.clone()); + Some(node) + }, + Action::ApplyFloatingSwapLayout { name } => { + let mut node = KdlNode::new("ApplyFloatingSwapLayout"); + node.push(name.clone()); + Some(node) + }, Action::BreakPane => Some(KdlNode::new("BreakPane")), Action::BreakPaneRight => Some(KdlNode::new("BreakPaneRight")), Action::BreakPaneLeft => Some(KdlNode::new("BreakPaneLeft")), @@ -2082,12 +2096,20 @@ impl TryFrom<(&KdlNode, &Options)> for Action { command_metadata.and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "pinned")); let borderless = command_metadata .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "borderless")); + let border_style = match command_metadata { + Some(command_metadata) => { + border_style_override_from_kdl_document(command_metadata, "border")? + .none_if_empty() + }, + None => None, + }; if floating { Ok(Action::NewFloatingPane { command: Some(run_command_action), pane_name: name, - coordinates: FloatingPaneCoordinates::new( - x, y, width, height, pinned, borderless, + coordinates: FloatingPaneCoordinates::merge_border_style( + FloatingPaneCoordinates::new(x, y, width, height, pinned, borderless), + border_style, ), near_current_pane: false, no_focus: false, @@ -2119,6 +2141,7 @@ impl TryFrom<(&KdlNode, &Options)> for Action { near_current_pane: false, no_focus: false, borderless: None, + border_style, tab_id: None, }) } @@ -2233,6 +2256,13 @@ impl TryFrom<(&KdlNode, &Options)> for Action { }, "PreviousSwapLayout" => Ok(Action::PreviousSwapLayout), "NextSwapLayout" => Ok(Action::NextSwapLayout), + "ApplyTiledSwapLayout" | "ApplyFloatingSwapLayout" => { + parse_kdl_action_char_or_string_arguments!( + action_name, + action_arguments, + kdl_action + ) + }, "BreakPane" => Ok(Action::BreakPane), "BreakPaneRight" => Ok(Action::BreakPaneRight), "BreakPaneLeft" => Ok(Action::BreakPaneLeft), @@ -5675,6 +5705,84 @@ fn load_plugins_from_kdl( Ok(load_plugins) } +pub fn line_style_from_kdl( + kdl_document: &KdlDocument, + child_name: &str, +) -> Result, ConfigError> { + let Some(child) = kdl_document.get(child_name) else { + return Ok(None); + }; + let value = child + .get(0) + .and_then(|v| v.value().as_string()) + .ok_or_else(|| { + ConfigError::new_kdl_error( + format!("'{}' must have a string value", child_name), + child.span().offset(), + child.span().len(), + ) + })?; + LineStyle::from_str(value) + .map(Some) + .map_err(|e| ConfigError::new_kdl_error(e, child.span().offset(), child.span().len())) +} + +pub fn border_style_override_from_kdl_document( + kdl_document: &KdlDocument, + prefix: &str, +) -> Result { + let rounded_corners = kdl_document + .get(&format!("{}_rounded_corners", prefix)) + .and_then(|n| n.entries().iter().next()) + .and_then(|e| e.value().as_bool()); + Ok(BorderStyleOverride { + all: line_style_from_kdl(kdl_document, &format!("{}_style", prefix))?, + top: line_style_from_kdl(kdl_document, &format!("{}_top", prefix))?, + right: line_style_from_kdl(kdl_document, &format!("{}_right", prefix))?, + bottom: line_style_from_kdl(kdl_document, &format!("{}_bottom", prefix))?, + left: line_style_from_kdl(kdl_document, &format!("{}_left", prefix))?, + rounded_corners, + }) +} + +pub fn border_style_override_from_kdl_children( + kdl_node: &KdlNode, + prefix: &str, +) -> Result { + match kdl_node.children() { + Some(children) => border_style_override_from_kdl_document(children, prefix), + None => Ok(BorderStyleOverride::default()), + } +} + +pub fn border_style_override_to_kdl_children( + border_style_override: &BorderStyleOverride, + prefix: &str, + children: &mut KdlDocument, +) -> bool { + let mut has_any = false; + let mut push = |name: String, line_style: Option| { + if let Some(line_style) = line_style { + let mut node = KdlNode::new(name); + node.push(KdlValue::String(line_style.to_string())); + children.nodes_mut().push(node); + has_any = true; + } + }; + push(format!("{}_style", prefix), border_style_override.all); + push(format!("{}_top", prefix), border_style_override.top); + push(format!("{}_right", prefix), border_style_override.right); + push(format!("{}_bottom", prefix), border_style_override.bottom); + push(format!("{}_left", prefix), border_style_override.left); + if let Some(rounded_corners) = border_style_override.rounded_corners { + let mut node = KdlNode::new(format!("{}_rounded_corners", prefix)); + node.push(KdlValue::Bool(rounded_corners)); + children.nodes_mut().push(node); + has_any = true; + } + has_any +} + impl UiConfig { pub fn from_kdl(kdl_ui_config: &KdlNode) -> Result { let mut ui_config = UiConfig::default(); @@ -5684,9 +5792,14 @@ impl UiConfig { .unwrap_or(false); let hide_session_name = kdl_get_child_entry_bool_value!(pane_frames, "hide_session_name").unwrap_or(false); + let border_style = border_style_override_from_kdl_children(pane_frames, "border")?; + let floating_border_style = + border_style_override_from_kdl_children(pane_frames, "floating_border")?; let frame_config = FrameConfig { rounded_corners, hide_session_name, + border_style, + floating_border_style, }; ui_config.pane_frames = frame_config; } @@ -5710,6 +5823,20 @@ impl UiConfig { hide_session_name.push(KdlValue::Bool(true)); frame_config_children.nodes_mut().push(hide_session_name); } + if border_style_override_to_kdl_children( + &self.pane_frames.border_style, + "border", + &mut frame_config_children, + ) { + has_ui_config = true; + } + if border_style_override_to_kdl_children( + &self.pane_frames.floating_border_style, + "floating_border", + &mut frame_config_children, + ) { + has_ui_config = true; + } if has_ui_config { frame_config.set_children(frame_config_children); ui_config_children.nodes_mut().push(frame_config); @@ -6172,7 +6299,7 @@ impl SessionInfo { } } } - tab_history.insert(client_id as u16, history); + tab_history.insert(client_id as ClientId, history); } } } @@ -6216,7 +6343,7 @@ impl SessionInfo { } } } - pane_history.insert(client_id as u16, history); + pane_history.insert(client_id as ClientId, history); } } } @@ -6415,7 +6542,7 @@ impl TabInfo { { for entry in tab_other_focused_clients { if let Some(entry_parsed) = entry.value().as_i64() { - other_focused_clients.push(entry_parsed as u16); + other_focused_clients.push(entry_parsed as ClientId); } } } @@ -6439,6 +6566,7 @@ impl TabInfo { is_sync_panes_active, are_floating_panes_visible, other_focused_clients, + other_focused_client_slots: vec![], active_swap_layout_name, is_swap_layout_dirty, viewport_rows, @@ -6878,6 +7006,7 @@ fn serialize_and_deserialize_session_info_with_data() { is_sync_panes_active: false, are_floating_panes_visible: true, other_focused_clients: vec![2, 3], + other_focused_client_slots: vec![], active_swap_layout_name: Some("BASE".to_owned()), is_swap_layout_dirty: true, viewport_rows: 10, @@ -6899,6 +7028,7 @@ fn serialize_and_deserialize_session_info_with_data() { is_sync_panes_active: true, are_floating_panes_visible: true, other_focused_clients: vec![2, 3], + other_focused_client_slots: vec![], active_swap_layout_name: None, is_swap_layout_dirty: false, viewport_rows: 10, @@ -7089,6 +7219,51 @@ fn can_bind_theme_actions() { assert_eq!(deserialized, deserialized_from_serialized); } +#[test] +fn can_bind_named_swap_layout_actions() { + let fake_config = r#" + keybinds { + normal { + bind "Ctrl t" { ApplyTiledSwapLayout "vertical"; } + bind "Ctrl f" { ApplyFloatingSwapLayout "staggered"; } + } + }"#; + let document: KdlDocument = fake_config.parse().unwrap(); + let deserialized = Keybinds::from_kdl( + document.get("keybinds").unwrap(), + Default::default(), + &Default::default(), + ) + .unwrap(); + let ctrl_t = KeyWithModifier::new(BareKey::Char('t')).with_ctrl_modifier(); + assert_eq!( + deserialized.get_actions_for_key_in_mode(&InputMode::Normal, &ctrl_t), + Some(&vec![Action::ApplyTiledSwapLayout { + name: "vertical".to_owned() + }]) + ); + let ctrl_f = KeyWithModifier::new(BareKey::Char('f')).with_ctrl_modifier(); + assert_eq!( + deserialized.get_actions_for_key_in_mode(&InputMode::Normal, &ctrl_f), + Some(&vec![Action::ApplyFloatingSwapLayout { + name: "staggered".to_owned() + }]) + ); + let serialized = Keybinds::to_kdl(&deserialized, true); + let deserialized_from_serialized = Keybinds::from_kdl( + serialized + .to_string() + .parse::() + .unwrap() + .get("keybinds") + .unwrap(), + Default::default(), + &Default::default(), + ) + .unwrap(); + assert_eq!(deserialized, deserialized_from_serialized); +} + #[test] fn keybinds_to_string_with_all_actions() { let fake_config = r#" @@ -7660,6 +7835,71 @@ fn ui_config_to_string() { insta::assert_snapshot!(serialized.to_string()); } +#[test] +fn ui_config_with_border_styles_to_string() { + let fake_config = r##" + ui { + pane_frames { + rounded_corners true + border_style "single" + border_top "double" + floating_border_style "heavy" + floating_border_rounded_corners false + } + }"##; + let document: KdlDocument = fake_config.parse().unwrap(); + let deserialized = UiConfig::from_kdl(document.get("ui").unwrap()).unwrap(); + assert_eq!( + deserialized.pane_frames.border_style, + BorderStyleOverride { + all: Some(LineStyle::Single), + top: Some(LineStyle::Double), + ..Default::default() + } + ); + assert_eq!( + deserialized.pane_frames.floating_border_style, + BorderStyleOverride { + all: Some(LineStyle::Heavy), + rounded_corners: Some(false), + ..Default::default() + } + ); + let resolved = deserialized.pane_frames.resolved_border_style(); + assert_eq!(resolved.top, LineStyle::Double); + assert_eq!(resolved.bottom, LineStyle::Single); + assert!(resolved.rounded_corners); + let resolved_floating = deserialized.pane_frames.resolved_floating_border_style(); + assert_eq!(resolved_floating.top, LineStyle::Heavy); + assert!(!resolved_floating.rounded_corners); + let serialized = UiConfig::to_kdl(&deserialized).unwrap(); + let deserialized_from_serialized = UiConfig::from_kdl( + serialized + .to_string() + .parse::() + .unwrap() + .get("ui") + .unwrap(), + ) + .unwrap(); + assert_eq!( + deserialized, deserialized_from_serialized, + "Deserialized and serialized ui config are the same" + ); +} + +#[test] +fn ui_config_with_invalid_border_style_is_an_error() { + let fake_config = r##" + ui { + pane_frames { + border_style "squiggly" + } + }"##; + let document: KdlDocument = fake_config.parse().unwrap(); + assert!(UiConfig::from_kdl(document.get("ui").unwrap()).is_err()); +} + #[test] fn ui_config_to_string_with_no_ui_config() { let fake_config = r##" diff --git a/zellij-utils/src/lib.rs b/zellij-utils/src/lib.rs index e7a2d797f..c8c5c3975 100644 --- a/zellij-utils/src/lib.rs +++ b/zellij-utils/src/lib.rs @@ -2,6 +2,7 @@ pub mod cli; pub mod client_server_contract; pub mod consts; pub mod data; +pub mod distribution; pub mod envs; pub mod errors; pub mod home; diff --git a/zellij-utils/src/logging.rs b/zellij-utils/src/logging.rs index 74f806ca0..52f989587 100644 --- a/zellij-utils/src/logging.rs +++ b/zellij-utils/src/logging.rs @@ -31,7 +31,7 @@ pub fn configure_logger() { let roller = FixedWindowRoller::builder() .build( ZELLIJ_TMP_LOG_DIR - .join("zellij.log.old.{}") + .join(format!("{}.log.old.{{}}", crate::distribution::name())) .to_str() .unwrap(), 1, @@ -122,7 +122,11 @@ pub fn atomic_create_dir(dir_name: &Path) -> io::Result<()> { pub fn debug_to_file(message: &[u8], terminal_id: i32) -> io::Result<()> { let mut path = PathBuf::new(); path.push(&*ZELLIJ_TMP_LOG_DIR); - path.push(format!("zellij-{}.log", terminal_id)); + path.push(format!( + "{}-{}.log", + crate::distribution::name(), + terminal_id + )); let mut file = fs::OpenOptions::new() .append(true) diff --git a/zellij-utils/src/plugin_api/action.proto b/zellij-utils/src/plugin_api/action.proto index 9f1d4aaa5..fc36761bc 100644 --- a/zellij-utils/src/plugin_api/action.proto +++ b/zellij-utils/src/plugin_api/action.proto @@ -3,6 +3,7 @@ syntax = "proto3"; import "input_mode.proto"; import "resize.proto"; import "pane_frame_style.proto"; +import "style.proto"; package api.action; @@ -108,6 +109,7 @@ message TiledPaneLayout { bool is_expanded_in_stack = 11; bool hide_floating_panes = 12; optional string pane_initial_contents = 13; + optional style.BorderStyleOverride border_style = 14; } message FloatingPaneLayout { @@ -123,6 +125,7 @@ message FloatingPaneLayout { optional string pane_initial_contents = 10; optional uint32 logical_position = 11; optional bool borderless = 12; + optional style.BorderStyleOverride border_style = 13; } message SwapTiledLayout { @@ -247,9 +250,17 @@ message Action { HideFloatingPanesPayload hide_floating_panes_payload = 59; AreFloatingPanesVisiblePayload are_floating_panes_visible_payload = 60; SetPaneFrameStylePayload set_pane_frame_style_payload = 61; + string apply_tiled_swap_layout_payload = 62; + string apply_floating_swap_layout_payload = 63; + SetPaneBorderStylePayload set_pane_border_style_payload = 64; } } +message SetPaneBorderStylePayload { + PaneId pane_id = 1; + style.BorderStyleOverride border_style = 2; +} + message SetPaneFrameStylePayload { pane_frame_style.PaneFrameStyle pane_frame_style = 1; } @@ -335,6 +346,7 @@ message NewTiledPanePayload { optional resize.ResizeDirection direction = 2; bool near_current_pane = 3; optional bool borderless = 4; + optional style.BorderStyleOverride border_style = 5; } message MovePanePayload { @@ -509,6 +521,9 @@ enum ActionName { ScrollToNextPrompt = 111; SelectCommandAtScrollPosition = 112; CopyLastCommandOutput = 113; + ApplyTiledSwapLayout = 114; + ApplyFloatingSwapLayout = 115; + SetPaneBorderStyle = 116; } message Position { @@ -540,6 +555,7 @@ message FloatingPaneCoordinates { optional SplitSize height = 4; optional bool pinned = 5; optional bool borderless = 6; + optional style.BorderStyleOverride border_style = 7; } // SetPaneBorderlessPayload specifies the pane and borderless state @@ -568,11 +584,13 @@ message NewPanePlacement { message NoPreferenceOptions { optional bool borderless = 1; + optional style.BorderStyleOverride border_style = 2; } message TiledPlacement { optional resize.ResizeDirection direction = 1; optional bool borderless = 2; + optional style.BorderStyleOverride border_style = 3; } message FloatingPlacement { @@ -583,11 +601,13 @@ message InPlaceConfig { optional PaneId pane_id_to_replace = 1; bool close_replaced_pane = 2; optional bool borderless = 3; + optional style.BorderStyleOverride border_style = 4; } message StackedPlacement { optional PaneId pane_id = 1; optional bool borderless = 2; + optional style.BorderStyleOverride border_style = 3; } message MouseEventPayload { diff --git a/zellij-utils/src/plugin_api/action.rs b/zellij-utils/src/plugin_api/action.rs index 0e787caa6..818bc60f2 100644 --- a/zellij-utils/src/plugin_api/action.rs +++ b/zellij-utils/src/plugin_api/action.rs @@ -79,8 +79,8 @@ pub use super::generated_api::api::{ resize::{Resize as ProtobufResize, ResizeDirection as ProtobufResizeDirection}, }; use crate::data::{ - CommandOrPlugin, Direction, FloatingPaneCoordinates, InputMode, KeyWithModifier, - NewPanePlacement, PaneId, PluginTag, ResizeStrategy, UnblockCondition, + BorderStyleOverride, CommandOrPlugin, Direction, FloatingPaneCoordinates, InputMode, + KeyWithModifier, NewPanePlacement, PaneId, PluginTag, ResizeStrategy, UnblockCondition, }; use crate::errors::prelude::*; use crate::input::actions::Action; @@ -430,6 +430,7 @@ impl TryFrom for Action { .and_then(|d| d.try_into().ok()); let near_current_pane = payload.near_current_pane; let borderless = payload.borderless; + let border_style = payload.border_style.map(BorderStyleOverride::from); if let Some(payload) = payload.command { let pane_name = payload.pane_name.clone(); let run_command_action: RunCommandAction = payload.try_into()?; @@ -440,6 +441,7 @@ impl TryFrom for Action { near_current_pane, no_focus: false, borderless, + border_style, tab_id: None, }) } else { @@ -450,6 +452,7 @@ impl TryFrom for Action { near_current_pane, no_focus: false, borderless, + border_style, tab_id: None, }) } @@ -848,6 +851,22 @@ impl TryFrom for Action { Some(_) => Err("NextSwapLayout should not have a payload"), None => Ok(Action::NextSwapLayout), }, + Some(ProtobufActionName::ApplyTiledSwapLayout) => { + match protobuf_action.optional_payload { + Some(OptionalPayload::ApplyTiledSwapLayoutPayload(name)) => { + Ok(Action::ApplyTiledSwapLayout { name }) + }, + _ => Err("Wrong payload for Action::ApplyTiledSwapLayout"), + } + }, + Some(ProtobufActionName::ApplyFloatingSwapLayout) => { + match protobuf_action.optional_payload { + Some(OptionalPayload::ApplyFloatingSwapLayoutPayload(name)) => { + Ok(Action::ApplyFloatingSwapLayout { name }) + }, + _ => Err("Wrong payload for Action::ApplyFloatingSwapLayout"), + } + }, Some(ProtobufActionName::OverrideLayout) => match protobuf_action.optional_payload { Some(OptionalPayload::OverrideLayoutPayload(payload)) => { Ok(Action::OverrideLayout { @@ -1211,6 +1230,8 @@ impl TryFrom for ProtobufAction { | Action::ToggleFloatingPanesByTabId { .. } | Action::PreviousSwapLayoutByTabId { .. } | Action::NextSwapLayoutByTabId { .. } + | Action::ApplyTiledSwapLayoutByTabId { .. } + | Action::ApplyFloatingSwapLayoutByTabId { .. } | Action::MoveTabByTabId { .. } => { Err("These are CLI-only actions, not available in keybindings") }, @@ -1487,6 +1508,7 @@ impl TryFrom for ProtobufAction { pane_name, near_current_pane, borderless, + border_style, .. } => { let direction = direction.and_then(|direction| { @@ -1508,6 +1530,7 @@ impl TryFrom for ProtobufAction { command, near_current_pane, borderless, + border_style: border_style.map(|b| b.into()), }, )), }) @@ -1798,6 +1821,14 @@ impl TryFrom for ProtobufAction { name: ProtobufActionName::NextSwapLayout as i32, optional_payload: None, }), + Action::ApplyTiledSwapLayout { name } => Ok(ProtobufAction { + name: ProtobufActionName::ApplyTiledSwapLayout as i32, + optional_payload: Some(OptionalPayload::ApplyTiledSwapLayoutPayload(name)), + }), + Action::ApplyFloatingSwapLayout { name } => Ok(ProtobufAction { + name: ProtobufActionName::ApplyFloatingSwapLayout as i32, + optional_payload: Some(OptionalPayload::ApplyFloatingSwapLayoutPayload(name)), + }), Action::OverrideLayout { tabs, retain_existing_terminal_panes, @@ -2066,6 +2097,7 @@ impl TryFrom for ProtobufAction { } | Action::TogglePaneBorderless { pane_id: _ } | Action::SetPaneBorderless { .. } + | Action::SetPaneBorderStyle { .. } | Action::SkipConfirm { action: _ } | Action::SwitchSession { .. } | Action::SaveSession @@ -2554,6 +2586,7 @@ impl TryFrom for FloatingPaneCoordinates { height: protobuf_coords.height.and_then(|h| h.try_into().ok()), pinned: protobuf_coords.pinned, borderless: protobuf_coords.borderless, + border_style: protobuf_coords.border_style.map(BorderStyleOverride::from), }) } } @@ -2568,6 +2601,7 @@ impl TryFrom for ProtobufFloatingPaneCoordinates { height: coords.height.and_then(|h| h.try_into().ok()), pinned: coords.pinned, borderless: coords.borderless, + border_style: coords.border_style.map(|b| b.into()), }) } } @@ -2581,6 +2615,7 @@ impl TryFrom for NewPanePlacement { match protobuf_placement.placement_variant { Some(PlacementVariant::NoPreference(opts)) => Ok(NewPanePlacement::NoPreference { borderless: opts.borderless, + border_style: opts.border_style.map(BorderStyleOverride::from), }), Some(PlacementVariant::Tiled(tiled)) => { let direction = tiled @@ -2590,6 +2625,7 @@ impl TryFrom for NewPanePlacement { Ok(NewPanePlacement::Tiled { direction, borderless: tiled.borderless, + border_style: tiled.border_style.map(BorderStyleOverride::from), }) }, Some(PlacementVariant::Floating(floating)) => { @@ -2603,6 +2639,7 @@ impl TryFrom for NewPanePlacement { pane_id_to_replace, close_replaced_pane: config.close_replaced_pane, borderless: config.borderless, + border_style: config.border_style.map(BorderStyleOverride::from), }) }, Some(PlacementVariant::Stacked(stacked)) => { @@ -2610,6 +2647,7 @@ impl TryFrom for NewPanePlacement { Ok(NewPanePlacement::Stacked { pane_id_to_stack_under: pane_id, borderless: stacked.borderless, + border_style: stacked.border_style.map(BorderStyleOverride::from), }) }, None => Err("NewPanePlacement must have a placement variant"), @@ -2624,14 +2662,17 @@ impl TryFrom for ProtobufNewPanePlacement { use super::generated_api::api::action::NoPreferenceOptions; let placement_variant = match placement { - NewPanePlacement::NoPreference { borderless } => { - Some(PlacementVariant::NoPreference(NoPreferenceOptions { - borderless, - })) - }, + NewPanePlacement::NoPreference { + borderless, + border_style, + } => Some(PlacementVariant::NoPreference(NoPreferenceOptions { + borderless, + border_style: border_style.map(|b| b.into()), + })), NewPanePlacement::Tiled { direction, borderless, + border_style, } => { let direction = direction.and_then(|d| { let protobuf_direction: ProtobufResizeDirection = d.try_into().ok()?; @@ -2640,6 +2681,7 @@ impl TryFrom for ProtobufNewPanePlacement { Some(PlacementVariant::Tiled(ProtobufTiledPlacement { direction, borderless, + border_style: border_style.map(|b| b.into()), })) }, NewPanePlacement::Floating(coords) => { @@ -2652,22 +2694,26 @@ impl TryFrom for ProtobufNewPanePlacement { pane_id_to_replace, close_replaced_pane, borderless, + border_style, } => { let pane_id_to_replace = pane_id_to_replace.and_then(|id| id.try_into().ok()); Some(PlacementVariant::InPlace(ProtobufInPlaceConfig { pane_id_to_replace, close_replaced_pane, borderless, + border_style: border_style.map(|b| b.into()), })) }, NewPanePlacement::Stacked { pane_id_to_stack_under, borderless, + border_style, } => { let pane_id = pane_id_to_stack_under.and_then(|id| id.try_into().ok()); Some(PlacementVariant::Stacked(ProtobufStackedPlacement { pane_id, borderless, + border_style: border_style.map(|b| b.into()), })) }, }; @@ -3017,6 +3063,7 @@ impl TryFrom for CommandOrPlugin { path: std::path::PathBuf::from(&f.path), line_number: f.line_number.map(|n| n as usize), cwd: f.cwd.map(std::path::PathBuf::from), + border_style: None, })) }, None => Err("CommandOrPlugin must have command_or_plugin_type"), @@ -3159,6 +3206,7 @@ impl TryFrom for TiledPaneLayout { pane_initial_contents: protobuf.pane_initial_contents, default_fg: None, default_bg: None, + border_style: protobuf.border_style.map(BorderStyleOverride::from), }) } } @@ -3190,6 +3238,7 @@ impl TryFrom for ProtobufTiledPaneLayout { exclude_from_sync: internal.exclude_from_sync, hide_floating_panes: internal.hide_floating_panes, pane_initial_contents: internal.pane_initial_contents, + border_style: internal.border_style.map(|b| b.into()), }) } } @@ -3217,6 +3266,7 @@ impl TryFrom for FloatingPaneLayout { borderless: protobuf.borderless, default_fg: None, default_bg: None, + border_style: protobuf.border_style.map(BorderStyleOverride::from), }) } } @@ -3242,6 +3292,7 @@ impl TryFrom for ProtobufFloatingPaneLayout { pane_initial_contents: internal.pane_initial_contents, logical_position: internal.logical_position.map(|p| p as u32), borderless: internal.borderless, + border_style: internal.border_style.map(|b| b.into()), }) } } @@ -3362,6 +3413,110 @@ mod tests { use super::*; use crate::input::options::PaneFrameStyle; + #[test] + fn new_tiled_pane_action_carries_a_border_style_over_protobuf() { + use crate::data::LineStyle; + let original = Action::NewTiledPane { + direction: None, + command: None, + pane_name: None, + near_current_pane: false, + no_focus: false, + borderless: None, + border_style: Some(BorderStyleOverride { + all: Some(LineStyle::Double), + top: Some(LineStyle::Heavy), + rounded_corners: Some(true), + ..Default::default() + }), + tab_id: None, + }; + let protobuf: ProtobufAction = original.clone().try_into().expect("encode"); + let decoded: Action = protobuf.try_into().expect("decode"); + assert_eq!(original, decoded); + } + + #[test] + fn a_new_pane_placement_carries_a_border_style_over_protobuf() { + use super::super::generated_api::api::action::NewPanePlacement as ProtobufNewPanePlacement; + use crate::data::LineStyle; + for original in [ + NewPanePlacement::NoPreference { + borderless: None, + border_style: Some(BorderStyleOverride { + all: Some(LineStyle::Double), + ..Default::default() + }), + }, + NewPanePlacement::Tiled { + direction: None, + borderless: Some(true), + border_style: Some(BorderStyleOverride { + left: Some(LineStyle::Heavy), + ..Default::default() + }), + }, + NewPanePlacement::InPlace { + pane_id_to_replace: None, + close_replaced_pane: false, + borderless: None, + border_style: Some(BorderStyleOverride { + bottom: Some(LineStyle::Dashed), + ..Default::default() + }), + }, + NewPanePlacement::Stacked { + pane_id_to_stack_under: None, + borderless: None, + border_style: Some(BorderStyleOverride { + rounded_corners: Some(false), + ..Default::default() + }), + }, + NewPanePlacement::Floating(Some(FloatingPaneCoordinates::default().with_border_style( + Some(BorderStyleOverride { + all: Some(LineStyle::HeavyDashed), + ..Default::default() + }), + ))), + ] { + let protobuf: ProtobufNewPanePlacement = original.clone().try_into().expect("encode"); + let decoded: NewPanePlacement = protobuf.try_into().expect("decode"); + assert_eq!(original, decoded); + } + } + + #[test] + fn pane_layouts_carry_border_styles_over_protobuf() { + use super::super::generated_api::api::action::{ + FloatingPaneLayout as ProtobufFloatingPaneLayout, + TiledPaneLayout as ProtobufTiledPaneLayout, + }; + use crate::data::LineStyle; + use crate::input::layout::{FloatingPaneLayout, TiledPaneLayout}; + let border_style = Some(BorderStyleOverride { + all: Some(LineStyle::Double), + top: Some(LineStyle::Heavy), + rounded_corners: Some(true), + ..Default::default() + }); + let tiled = TiledPaneLayout { + border_style, + ..Default::default() + }; + let protobuf: ProtobufTiledPaneLayout = tiled.clone().try_into().expect("encode"); + let decoded: TiledPaneLayout = protobuf.try_into().expect("decode"); + assert_eq!(decoded.border_style, border_style); + + let floating = FloatingPaneLayout { + border_style, + ..Default::default() + }; + let protobuf: ProtobufFloatingPaneLayout = floating.clone().try_into().expect("encode"); + let decoded: FloatingPaneLayout = protobuf.try_into().expect("decode"); + assert_eq!(decoded.border_style, border_style); + } + #[test] fn set_pane_frame_style_action_protobuf_round_trip() { for style in [ diff --git a/zellij-utils/src/plugin_api/command.proto b/zellij-utils/src/plugin_api/command.proto index 70402f25a..feb2ebefe 100644 --- a/zellij-utils/src/plugin_api/command.proto +++ b/zellij-utils/src/plugin_api/command.proto @@ -2,8 +2,11 @@ syntax = "proto3"; package api.command; +import "style.proto"; + message Command { string path = 1; repeated string args = 2; optional string cwd = 3; + optional style.BorderStyleOverride border_style = 4; } diff --git a/zellij-utils/src/plugin_api/command.rs b/zellij-utils/src/plugin_api/command.rs index 2f8bf3005..4ff8c22d5 100644 --- a/zellij-utils/src/plugin_api/command.rs +++ b/zellij-utils/src/plugin_api/command.rs @@ -1,5 +1,5 @@ pub use super::generated_api::api::command::Command as ProtobufCommand; -use crate::data::CommandToRun; +use crate::data::{BorderStyleOverride, CommandToRun}; use std::convert::TryFrom; use std::path::PathBuf; @@ -10,7 +10,13 @@ impl TryFrom for CommandToRun { let path = PathBuf::from(protobuf_command.path); let args = protobuf_command.args; let cwd = protobuf_command.cwd.map(|c| PathBuf::from(c)); - Ok(CommandToRun { path, args, cwd }) + let border_style = protobuf_command.border_style.map(BorderStyleOverride::from); + Ok(CommandToRun { + path, + args, + cwd, + border_style, + }) } } @@ -21,6 +27,7 @@ impl TryFrom for ProtobufCommand { path: command_to_run.path.display().to_string(), args: command_to_run.args, cwd: command_to_run.cwd.map(|c| c.display().to_string()), + border_style: command_to_run.border_style.map(|b| b.into()), }) } } diff --git a/zellij-utils/src/plugin_api/event.proto b/zellij-utils/src/plugin_api/event.proto index 3565e85c8..613f2a7cc 100644 --- a/zellij-utils/src/plugin_api/event.proto +++ b/zellij-utils/src/plugin_api/event.proto @@ -492,6 +492,7 @@ message TabInfo { uint32 tab_id = 17; bool has_bell_notification = 18; bool is_flashing_bell = 19; + repeated uint32 other_focused_client_slots = 20; } message ModeUpdatePayload { diff --git a/zellij-utils/src/plugin_api/event.rs b/zellij-utils/src/plugin_api/event.rs index 07ee5a540..e6bb53445 100644 --- a/zellij-utils/src/plugin_api/event.rs +++ b/zellij-utils/src/plugin_api/event.rs @@ -425,9 +425,9 @@ impl TryFrom for Event { .ok_or("Missing action in UserAction payload")? .try_into() .map_err(|_| "Failed to convert Action in UserAction payload")?; - let client_id = protobuf_payload.client_id as u16; + let client_id = protobuf_payload.client_id as ClientId; let terminal_id = protobuf_payload.terminal_id; - let cli_client_id = protobuf_payload.cli_client_id.map(|id| id as u16); + let cli_client_id = protobuf_payload.cli_client_id.map(|id| id as ClientId); Ok(Event::UserAction( action, client_id, @@ -469,7 +469,7 @@ impl TryFrom for Event { let focused_client_ids: Vec = protobuf_payload .focused_client_ids .into_iter() - .map(|id| id as u16) + .map(|id| id as ClientId) .collect(); Ok(Event::CwdChanged(pane_id, new_cwd, focused_client_ids)) }, @@ -485,7 +485,7 @@ impl TryFrom for Event { let focused_client_ids: Vec = p .focused_client_ids .into_iter() - .map(|id| id as u16) + .map(|id| id as ClientId) .collect(); Ok(Event::CommandChanged( pane_id, @@ -628,7 +628,7 @@ impl TryFrom for ClientInfo { type Error = &'static str; fn try_from(protobuf_client_info: ProtobufClientInfo) -> Result { Ok(ClientInfo::new( - protobuf_client_info.client_id as u16, + protobuf_client_info.client_id as ClientId, protobuf_client_info .pane_id .ok_or("No pane id found")? @@ -1279,8 +1279,8 @@ impl TryFrom for ProtobufSessionManifest { } } -impl From<(u16, Vec)> for ProtobufClientTabHistory { - fn from((client_id, tab_history): (u16, Vec)) -> ProtobufClientTabHistory { +impl From<(ClientId, Vec)> for ProtobufClientTabHistory { + fn from((client_id, tab_history): (ClientId, Vec)) -> ProtobufClientTabHistory { ProtobufClientTabHistory { client_id: client_id as u32, tab_history: tab_history.into_iter().map(|t| t as u32).collect(), @@ -1288,8 +1288,8 @@ impl From<(u16, Vec)> for ProtobufClientTabHistory { } } -impl From<(u16, Vec)> for ProtobufClientPaneHistory { - fn from((client_id, pane_history): (u16, Vec)) -> ProtobufClientPaneHistory { +impl From<(ClientId, Vec)> for ProtobufClientPaneHistory { + fn from((client_id, pane_history): (ClientId, Vec)) -> ProtobufClientPaneHistory { ProtobufClientPaneHistory { client_id: client_id as u32, pane_history: pane_history @@ -1353,7 +1353,7 @@ impl TryFrom for SessionInfo { .iter() .map(|t| *t as usize) .collect(); - tab_history.insert(client_id as u16, tab_history_for_client); + tab_history.insert(client_id as ClientId, tab_history_for_client); } let mut pane_history = BTreeMap::new(); for client_pane_history in protobuf_session_manifest.pane_history.into_iter() { @@ -1363,7 +1363,7 @@ impl TryFrom for SessionInfo { .into_iter() .filter_map(|p| p.try_into().ok()) .collect(); - pane_history.insert(client_id as u16, pane_history_for_client); + pane_history.insert(client_id as ClientId, pane_history_for_client); } Ok(SessionInfo { name: protobuf_session_manifest.name, @@ -1818,7 +1818,7 @@ impl TryFrom for PaneInfo { .iter() .map(|index_in_pane_group| { ( - index_in_pane_group.client_id as u16, + index_in_pane_group.client_id as ClientId, index_in_pane_group.index as usize, ) }) @@ -1888,7 +1888,12 @@ impl TryFrom for TabInfo { other_focused_clients: protobuf_tab_info .other_focused_clients .iter() - .map(|c| *c as u16) + .map(|c| *c as ClientId) + .collect(), + other_focused_client_slots: protobuf_tab_info + .other_focused_client_slots + .iter() + .map(|s| *s as usize) .collect(), active_swap_layout_name: protobuf_tab_info.active_swap_layout_name, is_swap_layout_dirty: protobuf_tab_info.is_swap_layout_dirty, @@ -1922,6 +1927,11 @@ impl TryFrom for ProtobufTabInfo { .iter() .map(|c| *c as u32) .collect(), + other_focused_client_slots: tab_info + .other_focused_client_slots + .iter() + .map(|s| *s as u32) + .collect(), active_swap_layout_name: tab_info.active_swap_layout_name, is_swap_layout_dirty: tab_info.is_swap_layout_dirty, viewport_rows: tab_info.viewport_rows as u32, @@ -2482,6 +2492,8 @@ fn serialize_mode_update_event_with_non_default_values() { // TODO: replace default rounded_corners: true, hide_session_name: false, + border_style: crate::data::BorderStyle::with_rounded_corners(true), + floating_border_style: crate::data::BorderStyle::with_rounded_corners(true), }, capabilities: PluginCapabilities { arrow_fonts: false }, session_name: Some("my awesome test session".to_owned()), @@ -2548,6 +2560,7 @@ fn serialize_tab_update_event_with_non_default_values() { is_sync_panes_active: false, are_floating_panes_visible: true, other_focused_clients: vec![2, 3, 4], + other_focused_client_slots: vec![1, 2, 3], active_swap_layout_name: Some("my cool swap layout".to_owned()), is_swap_layout_dirty: false, viewport_rows: 10, @@ -2569,6 +2582,7 @@ fn serialize_tab_update_event_with_non_default_values() { is_sync_panes_active: true, are_floating_panes_visible: true, other_focused_clients: vec![1, 5, 111], + other_focused_client_slots: vec![1, 2, 3], active_swap_layout_name: None, is_swap_layout_dirty: true, viewport_rows: 10, @@ -2894,6 +2908,7 @@ fn serialize_session_update_event_with_non_default_values() { is_sync_panes_active: false, are_floating_panes_visible: true, other_focused_clients: vec![2, 3, 4], + other_focused_client_slots: vec![1, 2, 3], active_swap_layout_name: Some("my cool swap layout".to_owned()), is_swap_layout_dirty: false, viewport_rows: 10, @@ -2915,6 +2930,7 @@ fn serialize_session_update_event_with_non_default_values() { is_sync_panes_active: true, are_floating_panes_visible: true, other_focused_clients: vec![1, 5, 111], + other_focused_client_slots: vec![1, 2, 3], active_swap_layout_name: None, is_swap_layout_dirty: true, viewport_rows: 10, diff --git a/zellij-utils/src/plugin_api/file.proto b/zellij-utils/src/plugin_api/file.proto index b2c5372b8..21243974f 100644 --- a/zellij-utils/src/plugin_api/file.proto +++ b/zellij-utils/src/plugin_api/file.proto @@ -2,8 +2,11 @@ syntax = "proto3"; package api.file; +import "style.proto"; + message File { string path = 1; optional int32 line_number = 2; optional string cwd = 3; + optional style.BorderStyleOverride border_style = 4; } diff --git a/zellij-utils/src/plugin_api/file.rs b/zellij-utils/src/plugin_api/file.rs index 7c06aa8e0..a3f225be5 100644 --- a/zellij-utils/src/plugin_api/file.rs +++ b/zellij-utils/src/plugin_api/file.rs @@ -1,5 +1,5 @@ pub use super::generated_api::api::file::File as ProtobufFile; -use crate::data::FileToOpen; +use crate::data::{BorderStyleOverride, FileToOpen}; use std::convert::TryFrom; use std::path::PathBuf; @@ -14,6 +14,7 @@ impl TryFrom for FileToOpen { path, line_number, cwd, + border_style: protobuf_file.border_style.map(BorderStyleOverride::from), }) } } @@ -25,6 +26,7 @@ impl TryFrom for ProtobufFile { path: file_to_open.path.display().to_string(), line_number: file_to_open.line_number.map(|l| l as i32), cwd: file_to_open.cwd.map(|c| c.display().to_string()), + border_style: file_to_open.border_style.map(|b| b.into()), }) } } diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto index ccff12d94..2be85503f 100644 --- a/zellij-utils/src/plugin_api/plugin_command.proto +++ b/zellij-utils/src/plugin_api/plugin_command.proto @@ -10,6 +10,7 @@ import "plugin_permission.proto"; import "input_mode.proto"; import "key.proto"; import "pane_frame_style.proto"; +import "style.proto"; package api.plugin_command; @@ -218,6 +219,9 @@ enum CommandName { NewPane = 226; ToggleFocusNoUiFullscreen = 227; FocusHostSession = 228; + ApplyTiledSwapLayout = 229; + ApplyFloatingSwapLayout = 230; + SetPaneBorderStyle = 231; } message PluginCommand { @@ -378,6 +382,9 @@ message PluginCommand { NewTiledPaneInTabPayload new_tiled_pane_in_tab_payload = 169; SetPaneFrameStylePayload set_pane_frame_style_payload = 170; ToggleFloatingPanesPayload toggle_floating_panes_payload = 171; + string apply_tiled_swap_layout_payload = 172; + string apply_floating_swap_layout_payload = 173; + SetPaneBorderStylePayload set_pane_border_style_payload = 174; } } @@ -535,6 +542,11 @@ message SetPaneBorderlessPayload { bool borderless = 2; } +message SetPaneBorderStylePayload { + PaneId pane_id = 1; + style.BorderStyleOverride border_style = 2; +} + message ChangeHostFolderPayload { string new_host_folder = 1; } @@ -815,6 +827,7 @@ message FloatingPaneCoordinates { optional FixedOrPercentValue height = 4; optional bool pinned = 5; optional bool borderless = 6; + optional style.BorderStyleOverride border_style = 7; } message FixedOrPercentValue { diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs index c47ee1507..66b589551 100644 --- a/zellij-utils/src/plugin_api/plugin_command.rs +++ b/zellij-utils/src/plugin_api/plugin_command.rs @@ -121,7 +121,7 @@ pub use super::generated_api::api::{ SaveSessionResponse as ProtobufSaveSessionResponse, ScrollDownInPaneIdPayload, ScrollToBottomInPaneIdPayload, ScrollToTopInPaneIdPayload, ScrollUpInPaneIdPayload, SessionListSnapshot as ProtobufSessionListSnapshot, SetFloatingPanePinnedPayload, - SetPaneBorderlessPayload, SetPaneColorPayload, + SetPaneBorderStylePayload, SetPaneBorderlessPayload, SetPaneColorPayload, SetPaneFrameStylePayload as ProtobufSetPaneFrameStylePayload, SetPaneRegexHighlightsPayload, SetSelfMouseSelectionSupportPayload, SetSoftKeyboardPayload as ProtobufSetSoftKeyboardPayload, SetTimeoutPayload, @@ -147,6 +147,7 @@ use crate::data::{ use crate::input::actions::Action; use crate::input::layout::PercentOrFixed; +use crate::data::BorderStyleOverride; use std::collections::BTreeMap; use std::convert::TryFrom; use std::path::PathBuf; @@ -200,6 +201,7 @@ impl Into for ProtobufFloatingPaneCoordinates { }), pinned: self.pinned, borderless: self.borderless, + border_style: self.border_style.map(BorderStyleOverride::from), } } } @@ -253,6 +255,7 @@ impl Into for FloatingPaneCoordinates { }, pinned: self.pinned, borderless: self.borderless, + border_style: self.border_style.map(|b| b.into()), } } } @@ -1178,6 +1181,18 @@ impl TryFrom for PluginCommand { } Ok(PluginCommand::NextSwapLayout) }, + Some(CommandName::ApplyTiledSwapLayout) => match protobuf_plugin_command.payload { + Some(Payload::ApplyTiledSwapLayoutPayload(layout_name)) => { + Ok(PluginCommand::ApplyTiledSwapLayout(layout_name)) + }, + _ => Err("Mismatched payload for ApplyTiledSwapLayout"), + }, + Some(CommandName::ApplyFloatingSwapLayout) => match protobuf_plugin_command.payload { + Some(Payload::ApplyFloatingSwapLayoutPayload(layout_name)) => { + Ok(PluginCommand::ApplyFloatingSwapLayout(layout_name)) + }, + _ => Err("Mismatched payload for ApplyFloatingSwapLayout"), + }, Some(CommandName::GoToTabName) => match protobuf_plugin_command.payload { Some(Payload::GoToTabNamePayload(tab_name)) => { Ok(PluginCommand::GoToTabName(tab_name)) @@ -2094,6 +2109,19 @@ impl TryFrom for PluginCommand { }, _ => Err("Mismatched payload for SetPaneBorderless"), }, + Some(CommandName::SetPaneBorderStyle) => match protobuf_plugin_command.payload { + Some(Payload::SetPaneBorderStylePayload(payload)) => match payload.pane_id { + Some(pane_id) => Ok(PluginCommand::SetPaneBorderStyle( + pane_id.try_into()?, + payload + .border_style + .map(BorderStyleOverride::from) + .unwrap_or_default(), + )), + _ => Err("Malformed SetPaneBorderStyle payload"), + }, + _ => Err("Mismatched payload for SetPaneBorderStyle"), + }, Some(CommandName::OpenCommandPaneNearPlugin) => match protobuf_plugin_command.payload { Some(Payload::OpenCommandPaneNearPluginPayload(command_to_run_payload)) => { match command_to_run_payload.command_to_run { @@ -3155,6 +3183,14 @@ impl TryFrom for ProtobufPluginCommand { name: CommandName::NextSwapLayout as i32, payload: None, }), + PluginCommand::ApplyTiledSwapLayout(layout_name) => Ok(ProtobufPluginCommand { + name: CommandName::ApplyTiledSwapLayout as i32, + payload: Some(Payload::ApplyTiledSwapLayoutPayload(layout_name)), + }), + PluginCommand::ApplyFloatingSwapLayout(layout_name) => Ok(ProtobufPluginCommand { + name: CommandName::ApplyFloatingSwapLayout as i32, + payload: Some(Payload::ApplyFloatingSwapLayoutPayload(layout_name)), + }), PluginCommand::GoToTabName(tab_name) => Ok(ProtobufPluginCommand { name: CommandName::GoToTabName as i32, payload: Some(Payload::GoToTabNamePayload(tab_name)), @@ -3935,6 +3971,15 @@ impl TryFrom for ProtobufPluginCommand { }, )), }), + PluginCommand::SetPaneBorderStyle(pane_id, border_style) => Ok(ProtobufPluginCommand { + name: CommandName::SetPaneBorderStyle as i32, + payload: Some(Payload::SetPaneBorderStylePayload( + SetPaneBorderStylePayload { + pane_id: Some(pane_id.try_into()?), + border_style: Some(border_style.into()), + }, + )), + }), PluginCommand::OpenCommandPaneNearPlugin(command_to_run, context) => { let context: Vec<_> = context .into_iter() @@ -5230,6 +5275,109 @@ mod tests { use super::*; use crate::data::PluginCommand; + #[test] + fn set_pane_border_style_protobuf_round_trip() { + use crate::data::{BorderStyleOverride, LineStyle, PaneId}; + let border_style = BorderStyleOverride { + all: Some(LineStyle::Double), + top: Some(LineStyle::Heavy), + right: Some(LineStyle::Dashed), + bottom: Some(LineStyle::HeavyDashed), + left: Some(LineStyle::Single), + rounded_corners: Some(true), + }; + let original = PluginCommand::SetPaneBorderStyle(PaneId::Plugin(3), border_style); + let protobuf: ProtobufPluginCommand = original.try_into().expect("encode"); + let decoded: PluginCommand = protobuf.try_into().expect("decode"); + match decoded { + PluginCommand::SetPaneBorderStyle(pane_id, decoded_border_style) => { + assert_eq!(pane_id, PaneId::Plugin(3)); + assert_eq!(decoded_border_style, border_style); + }, + other => panic!("expected SetPaneBorderStyle, got {:?}", other), + } + } + + #[test] + fn an_empty_pane_border_style_round_trips() { + use crate::data::{BorderStyleOverride, PaneId}; + let original = + PluginCommand::SetPaneBorderStyle(PaneId::Terminal(1), BorderStyleOverride::default()); + let protobuf: ProtobufPluginCommand = original.try_into().expect("encode"); + let decoded: PluginCommand = protobuf.try_into().expect("decode"); + match decoded { + PluginCommand::SetPaneBorderStyle(pane_id, border_style) => { + assert_eq!(pane_id, PaneId::Terminal(1)); + assert!(border_style.is_empty()); + }, + other => panic!("expected SetPaneBorderStyle, got {:?}", other), + } + } + + #[test] + fn floating_pane_coordinates_carry_a_border_style_over_protobuf() { + use crate::data::{BorderStyleOverride, FileToOpen, FloatingPaneCoordinates, LineStyle}; + let border_style = BorderStyleOverride { + all: Some(LineStyle::Heavy), + ..Default::default() + }; + let coordinates = FloatingPaneCoordinates::default().with_border_style(Some(border_style)); + let original = + PluginCommand::OpenTerminalFloating(FileToOpen::new("/tmp"), Some(coordinates.clone())); + let protobuf: ProtobufPluginCommand = original.try_into().expect("encode"); + let decoded: PluginCommand = protobuf.try_into().expect("decode"); + match decoded { + PluginCommand::OpenTerminalFloating(_, decoded_coordinates) => { + assert_eq!( + decoded_coordinates.unwrap().border_style, + Some(border_style) + ); + }, + other => panic!("expected OpenTerminalFloating, got {:?}", other), + } + } + + #[test] + fn a_file_to_open_carries_a_border_style_over_protobuf() { + use crate::data::{BorderStyleOverride, FileToOpen, LineStyle}; + let border_style = BorderStyleOverride { + top: Some(LineStyle::Double), + ..Default::default() + }; + let original = + PluginCommand::OpenTerminal(FileToOpen::new("/tmp").with_border_style(border_style)); + let protobuf: ProtobufPluginCommand = original.try_into().expect("encode"); + let decoded: PluginCommand = protobuf.try_into().expect("decode"); + match decoded { + PluginCommand::OpenTerminal(file_to_open) => { + assert_eq!(file_to_open.border_style, Some(border_style)); + }, + other => panic!("expected OpenTerminal, got {:?}", other), + } + } + + #[test] + fn a_command_to_run_carries_a_border_style_over_protobuf() { + use crate::data::{BorderStyleOverride, CommandToRun, LineStyle}; + let border_style = BorderStyleOverride { + bottom: Some(LineStyle::Dashed), + rounded_corners: Some(false), + ..Default::default() + }; + let original = PluginCommand::OpenCommandPane( + CommandToRun::new("/bin/sh").with_border_style(border_style), + BTreeMap::new(), + ); + let protobuf: ProtobufPluginCommand = original.try_into().expect("encode"); + let decoded: PluginCommand = protobuf.try_into().expect("decode"); + match decoded { + PluginCommand::OpenCommandPane(command_to_run, _) => { + assert_eq!(command_to_run.border_style, Some(border_style)); + }, + other => panic!("expected OpenCommandPane, got {:?}", other), + } + } + #[test] fn set_pane_frame_style_protobuf_round_trip() { use crate::input::options::PaneFrameStyle; @@ -5261,6 +5409,32 @@ mod tests { } } + #[test] + fn apply_tiled_swap_layout_protobuf_round_trip() { + let original = PluginCommand::ApplyTiledSwapLayout("single_open".to_owned()); + let protobuf: ProtobufPluginCommand = original.try_into().expect("encode"); + let decoded: PluginCommand = protobuf.try_into().expect("decode"); + match decoded { + PluginCommand::ApplyTiledSwapLayout(layout_name) => { + assert_eq!(layout_name, "single_open") + }, + other => panic!("expected ApplyTiledSwapLayout, got {:?}", other), + } + } + + #[test] + fn apply_floating_swap_layout_protobuf_round_trip() { + let original = PluginCommand::ApplyFloatingSwapLayout("staggered".to_owned()); + let protobuf: ProtobufPluginCommand = original.try_into().expect("encode"); + let decoded: PluginCommand = protobuf.try_into().expect("decode"); + match decoded { + PluginCommand::ApplyFloatingSwapLayout(layout_name) => { + assert_eq!(layout_name, "staggered") + }, + other => panic!("expected ApplyFloatingSwapLayout, got {:?}", other), + } + } + #[test] fn toggle_floating_panes_focused_tab_protobuf_round_trip() { let original = PluginCommand::ToggleFloatingPanes { tab_id: None }; diff --git a/zellij-utils/src/plugin_api/plugin_ids.rs b/zellij-utils/src/plugin_api/plugin_ids.rs index 9a7509734..565dbe21d 100644 --- a/zellij-utils/src/plugin_api/plugin_ids.rs +++ b/zellij-utils/src/plugin_api/plugin_ids.rs @@ -1,7 +1,7 @@ pub use super::generated_api::api::plugin_ids::{ PluginIds as ProtobufPluginIds, ZellijVersion as ProtobufZellijVersion, }; -use crate::data::PluginIds; +use crate::data::{ClientId, PluginIds}; use std::convert::TryFrom; use std::path::PathBuf; @@ -13,7 +13,7 @@ impl TryFrom for PluginIds { plugin_id: protobuf_plugin_ids.plugin_id as u32, zellij_pid: protobuf_plugin_ids.zellij_pid as u32, initial_cwd: PathBuf::from(protobuf_plugin_ids.initial_cwd), - client_id: protobuf_plugin_ids.client_id as u16, + client_id: protobuf_plugin_ids.client_id as ClientId, }) } } diff --git a/zellij-utils/src/plugin_api/style.proto b/zellij-utils/src/plugin_api/style.proto index cfea362e5..e424235ad 100644 --- a/zellij-utils/src/plugin_api/style.proto +++ b/zellij-utils/src/plugin_api/style.proto @@ -7,6 +7,33 @@ message Style { bool rounded_corners = 2; bool hide_session_name = 3; Styling styling = 4; + BorderStyle border_style = 5; + BorderStyle floating_border_style = 6; +} + +enum LineStyle { + Single = 0; + Double = 1; + Heavy = 2; + Dashed = 3; + HeavyDashed = 4; +} + +message BorderStyle { + LineStyle top = 1; + LineStyle right = 2; + LineStyle bottom = 3; + LineStyle left = 4; + bool rounded_corners = 5; +} + +message BorderStyleOverride { + optional LineStyle all = 1; + optional LineStyle top = 2; + optional LineStyle right = 3; + optional LineStyle bottom = 4; + optional LineStyle left = 5; + optional bool rounded_corners = 6; } message Palette { diff --git a/zellij-utils/src/plugin_api/style.rs b/zellij-utils/src/plugin_api/style.rs index bb1e10ec6..0d55684c6 100644 --- a/zellij-utils/src/plugin_api/style.rs +++ b/zellij-utils/src/plugin_api/style.rs @@ -1,10 +1,13 @@ use super::generated_api::api::style::{ - color::Payload as ProtobufColorPayload, Color as ProtobufColor, ColorType as ProtobufColorType, - Palette as ProtobufPalette, RgbColorPayload as ProtobufRgbColorPayload, Style as ProtobufStyle, - Styling as ProtobufStyling, ThemeHue as ProtobufThemeHue, + color::Payload as ProtobufColorPayload, BorderStyle as ProtobufBorderStyle, + BorderStyleOverride as ProtobufBorderStyleOverride, Color as ProtobufColor, + ColorType as ProtobufColorType, LineStyle as ProtobufLineStyle, Palette as ProtobufPalette, + RgbColorPayload as ProtobufRgbColorPayload, Style as ProtobufStyle, Styling as ProtobufStyling, + ThemeHue as ProtobufThemeHue, }; use crate::data::{ - MultiplayerColors, Palette, PaletteColor, Style, StyleDeclaration, Styling, ThemeHue, + BorderStyle, BorderStyleOverride, LineStyle, MultiplayerColors, Palette, PaletteColor, Style, + StyleDeclaration, Styling, ThemeHue, }; use crate::errors::prelude::*; @@ -21,6 +24,18 @@ impl TryFrom for Style { colors: s, rounded_corners: protobuf_style.rounded_corners, hide_session_name: protobuf_style.hide_session_name, + border_style: protobuf_style + .border_style + .map(BorderStyle::from) + .unwrap_or_else(|| { + BorderStyle::with_rounded_corners(protobuf_style.rounded_corners) + }), + floating_border_style: protobuf_style + .floating_border_style + .map(BorderStyle::from) + .unwrap_or_else(|| { + BorderStyle::with_rounded_corners(protobuf_style.rounded_corners) + }), }) } } @@ -36,10 +51,94 @@ impl TryFrom