aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2023-08-24 13:36:24 +0200
committerGitHub <noreply@github.com>2023-08-24 13:36:24 +0200
commitbc628abc1266cdc0dbce4f19a89727527a3e39a8 (patch)
tree6a0fcac6ec35c71a237bca0128a57a5b40afb0e3
parentdocs(changelog): permission system (diff)
downloadsl0ppij-bc628abc1266cdc0dbce4f19a89727527a3e39a8.tar.gz
sl0ppij-bc628abc1266cdc0dbce4f19a89727527a3e39a8.tar.xz
sl0ppij-bc628abc1266cdc0dbce4f19a89727527a3e39a8.zip
feat(sessions): add a session manager to switch between sessions, tabs and panes and create new ones (#2721)
* write/read session metadata to disk for all sessions * switch session client side * fix tests * various adjustments * fix full screen focus bug in tiled panes * fix tests * fix permission sorting issue * cleanups * add session manager * fix tests * various cleanups * style(fmt): rustfmt * clear screen before switching sessions * I hate you clippy * truncate controls line to width * version session cache * attempt to fix plugin tests * style(fmt): rustfmt * another attempt to fix the tests in the ci
-rw-r--r--Cargo.lock15
-rw-r--r--Cargo.toml1
-rw-r--r--default-plugins/session-manager/.cargo/config.toml2
-rw-r--r--default-plugins/session-manager/.gitignore1
-rw-r--r--default-plugins/session-manager/Cargo.lock2809
-rw-r--r--default-plugins/session-manager/Cargo.toml12
-rw-r--r--default-plugins/session-manager/src/main.rs208
-rw-r--r--default-plugins/session-manager/src/session_list.rs381
-rw-r--r--default-plugins/session-manager/src/ui/components.rs577
-rw-r--r--default-plugins/session-manager/src/ui/mod.rs357
-rw-r--r--src/commands.rs264
-rw-r--r--xtask/src/main.rs1
-rw-r--r--zellij-client/src/input_handler.rs3
-rw-r--r--zellij-client/src/lib.rs110
-rw-r--r--zellij-client/src/os_input_output.rs59
-rw-r--r--zellij-client/src/stdin_handler.rs117
-rw-r--r--zellij-client/src/unit/stdin_tests.rs5
-rw-r--r--zellij-server/src/background_jobs.rs106
-rw-r--r--zellij-server/src/lib.rs73
-rw-r--r--zellij-server/src/panes/grid.rs2
-rw-r--r--zellij-server/src/panes/tiled_panes/mod.rs6
-rw-r--r--zellij-server/src/plugins/unit/plugin_tests.rs67
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__go_to_tab.snap4
-rw-r--r--zellij-server/src/plugins/wasm_bridge.rs1
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs39
-rw-r--r--zellij-server/src/route.rs16
-rw-r--r--zellij-server/src/screen.rs279
-rw-r--r--zellij-server/src/tab/mod.rs3
-rw-r--r--zellij-tile/src/shim.rs28
-rw-r--r--zellij-utils/assets/config/default.kdl7
-rwxr-xr-xzellij-utils/assets/plugins/compact-bar.wasmbin806599 -> 818565 bytes
-rwxr-xr-xzellij-utils/assets/plugins/fixture-plugin-for-tests.wasmbin768790 -> 787551 bytes
-rwxr-xr-xzellij-utils/assets/plugins/session-manager.wasmbin0 -> 771931 bytes
-rwxr-xr-xzellij-utils/assets/plugins/status-bar.wasmbin961879 -> 975452 bytes
-rwxr-xr-xzellij-utils/assets/plugins/strider.wasmbin2025837 -> 2033674 bytes
-rwxr-xr-xzellij-utils/assets/plugins/tab-bar.wasmbin781434 -> 792795 bytes
-rw-r--r--zellij-utils/src/consts.rs14
-rw-r--r--zellij-utils/src/data.rs45
-rw-r--r--zellij-utils/src/errors.rs5
-rw-r--r--zellij-utils/src/input/permission.rs15
-rw-r--r--zellij-utils/src/ipc.rs24
-rw-r--r--zellij-utils/src/kdl/mod.rs520
-rw-r--r--zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info.snap12
-rw-r--r--zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info_with_data.snap81
-rw-r--r--zellij-utils/src/plugin_api/event.proto14
-rw-r--r--zellij-utils/src/plugin_api/event.rs218
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.proto9
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.rs30
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap40
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap40
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap17
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap40
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap40
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap40
54 files changed, 6392 insertions, 365 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 918d8a87..9fda1060 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2869,6 +2869,17 @@ dependencies = [
]
[[package]]
+name = "session-manager"
+version = "0.1.0"
+dependencies = [
+ "ansi_term",
+ "chrono",
+ "fuzzy-matcher",
+ "unicode-width",
+ "zellij-tile",
+]
+
+[[package]]
name = "sha-1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3596,9 +3607,9 @@ checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
[[package]]
name = "unicode-width"
-version = "0.1.9"
+version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "unsafe-any-ors"
diff --git a/Cargo.toml b/Cargo.toml
index 65b4f04a..4d74bfad 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -35,6 +35,7 @@ members = [
"default-plugins/strider",
"default-plugins/tab-bar",
"default-plugins/fixture-plugin-for-tests",
+ "default-plugins/session-manager",
"zellij-client",
"zellij-server",
"zellij-utils",
diff --git a/default-plugins/session-manager/.cargo/config.toml b/default-plugins/session-manager/.cargo/config.toml
new file mode 100644
index 00000000..bc255e30
--- /dev/null
+++ b/default-plugins/session-manager/.cargo/config.toml
@@ -0,0 +1,2 @@
+[build]
+target = "wasm32-wasi" \ No newline at end of file
diff --git a/default-plugins/session-manager/.gitignore b/default-plugins/session-manager/.gitignore
new file mode 100644
index 00000000..ea8c4bf7
--- /dev/null
+++ b/default-plugins/session-manager/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/default-plugins/session-manager/Cargo.lock b/default-plugins/session-manager/Cargo.lock
new file mode 100644
index 00000000..edd9db57
--- /dev/null
+++ b/default-plugins/session-manager/Cargo.lock
@@ -0,0 +1,2809 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "ahash"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.71"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
+dependencies = [
+ "backtrace",
+]
+
+[[package]]
+name = "arc-swap"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
+
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+
+[[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-executor"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb"
+dependencies = [
+ "async-lock",
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "slab",
+]
+
+[[package]]
+name = "async-global-executor"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776"
+dependencies = [
+ "async-channel",
+ "async-executor",
+ "async-io",
+ "async-lock",
+ "blocking",
+ "futures-lite",
+ "once_cell",
+]
+
+[[package]]
+name = "async-io"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
+dependencies = [
+ "async-lock",
+ "autocfg",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-lite",
+ "log",
+ "parking",
+ "polling",
+ "rustix",
+ "slab",
+ "socket2",
+ "waker-fn",
+]
+
+[[package]]
+name = "async-lock"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7"
+dependencies = [
+ "event-listener",
+]
+
+[[package]]
+name = "async-process"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9"
+dependencies = [
+ "async-io",
+ "async-lock",
+ "autocfg",
+ "blocking",
+ "cfg-if",
+ "event-listener",
+ "futures-lite",
+ "rustix",
+ "signal-hook 0.3.15",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "async-std"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
+dependencies = [
+ "async-channel",
+ "async-global-executor",
+ "async-io",
+ "async-lock",
+ "async-process",
+ "crossbeam-utils",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-lite",
+ "gloo-timers",
+ "kv-log-macro",
+ "log",
+ "memchr",
+ "once_cell",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+ "wasm-bindgen-futures",
+]
+
+[[package]]
+name = "async-task"
+version = "4.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae"
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi 0.1.19",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "backtrace"
+version = "0.3.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[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.21.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array",
+]
+
+[[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 = "blocking"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65"
+dependencies = [
+ "async-channel",
+ "async-lock",
+ "async-task",
+ "atomic-waker",
+ "fastrand",
+ "futures-lite",
+ "log",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bytes"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time",
+ "winapi",
+]
+
+[[package]]
+name = "clap"
+version = "3.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
+dependencies = [
+ "atty",
+ "bitflags",
+ "clap_derive",
+ "clap_lex",
+ "indexmap",
+ "once_cell",
+ "strsim",
+ "termcolor",
+ "textwrap 0.16.0",
+]
+
+[[package]]
+name = "clap_complete"
+version = "3.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f7a2e0a962c45ce25afce14220bc24f9dade0a1787f185cecf96bfba7847cd8"
+dependencies = [
+ "clap",
+]
+
+[[package]]
+name = "clap_derive"
+version = "3.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
+dependencies = [
+ "os_str_bytes",
+]
+
+[[package]]
+name = "colored"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
+dependencies = [
+ "atty",
+ "lazy_static",
+ "winapi",
+]
+
+[[package]]
+name = "colorsys"
+version = "0.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54261aba646433cb567ec89844be4c4825ca92a4f8afba52fc4dd88436e31bbd"
+
+[[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 = "cpufeatures"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crossbeam"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c"
+dependencies = [
+ "cfg-if",
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-epoch",
+ "crossbeam-queue",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
+dependencies = [
+ "cfg-if",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "crossbeam-utils",
+ "memoffset 0.9.0",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
+dependencies = [
+ "cfg-if",
+]
+
+[[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 = "csscolorparser"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf"
+dependencies = [
+ "lab",
+ "phf 0.11.1",
+]
+
+[[package]]
+name = "deltae"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ef311e2c0a388b9ba56c839ac68d33b92d18ce7104d0acc4375cb479e2b9a53"
+
+[[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.109",
+]
+
+[[package]]
+name = "destructure_traitobject"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7"
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
+[[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",
+]
+
+[[package]]
+name = "directories-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc"
+dependencies = [
+ "cfg-if",
+ "dirs-sys-next",
+]
+
+[[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"
+version = "5.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
+dependencies = [
+ "dirs-sys 0.4.1",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "either"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+
+[[package]]
+name = "errno"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+
+[[package]]
+name = "fastrand"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "file-id"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13be71e6ca82e91bc0cb862bebaac0b2d1924a5a1d970c822b2f98b63fda8c3"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "filedescriptor"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
+dependencies = [
+ "libc",
+ "thiserror",
+ "winapi",
+]
+
+[[package]]
+name = "filetime"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.2.16",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "finl_unicode"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6"
+
+[[package]]
+name = "fixedbitset"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
+dependencies = [
+ "percent-encoding",
+]
+
+[[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-channel"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
+
+[[package]]
+name = "futures-io"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
+
+[[package]]
+name = "futures-lite"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "memchr",
+ "parking",
+ "pin-project-lite",
+ "waker-fn",
+]
+
+[[package]]
+name = "fuzzy-matcher"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
+dependencies = [
+ "thread_local",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+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 = "gimli"
+version = "0.27.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4"
+
+[[package]]
+name = "gloo-timers"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+dependencies = [
+ "ahash",
+]
+
+[[package]]
+name = "heck"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "idna"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[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 = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "inotify"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
+dependencies = [
+ "bitflags",
+ "inotify-sys",
+ "libc",
+]
+
+[[package]]
+name = "inotify-sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
+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 = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81f2533f3be42fffe3b5e63b71aeca416c1c3bc33e4e27be018521e76b1f38fb"
+dependencies = [
+ "blocking",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "intmap",
+ "libc",
+ "once_cell",
+ "rustc_version",
+ "spinning",
+ "thiserror",
+ "to_method",
+ "winapi",
+]
+
+[[package]]
+name = "intmap"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae52f28f45ac2bc96edb7714de995cffc174a395fb0abf5bff453587c980d7b9"
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
+dependencies = [
+ "hermit-abi 0.3.1",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
+dependencies = [
+ "hermit-abi 0.3.1",
+ "io-lifetimes",
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "is_ci"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
+
+[[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 = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
+
+[[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 = "kdl"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "062c875482ccb676fd40c804a40e3824d4464c18c364547456d1c8e8e951ae47"
+dependencies = [
+ "miette",
+ "nom 7.1.3",
+ "thiserror",
+]
+
+[[package]]
+name = "kqueue"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98"
+dependencies = [
+ "kqueue-sys",
+ "libc",
+]
+
+[[package]]
+name = "kqueue-sys"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587"
+dependencies = [
+ "bitflags",
+ "libc",
+]
+
+[[package]]
+name = "kv-log-macro"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "lab"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.146"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b"
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
+
+[[package]]
+name = "lock_api"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
+dependencies = [
+ "serde",
+ "value-bag",
+]
+
+[[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",
+ "humantime",
+ "libc",
+ "log",
+ "log-mdc",
+ "parking_lot",
+ "serde",
+ "serde-value",
+ "serde_json",
+ "serde_yaml",
+ "thiserror",
+ "thread-id",
+ "typemap-ors",
+ "winapi",
+]
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "memmem"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15"
+
+[[package]]
+name = "memoffset"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "miette"
+version = "5.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a236ff270093b0b67451bc50a509bd1bad302cb1d3c7d37d5efe931238581fa9"
+dependencies = [
+ "backtrace",
+ "backtrace-ext",
+ "is-terminal",
+ "miette-derive",
+ "once_cell",
+ "owo-colors",
+ "supports-color",
+ "supports-hyperlinks",
+ "supports-unicode",
+ "terminal_size",
+ "textwrap 0.15.2",
+ "thiserror",
+ "unicode-width",
+]
+
+[[package]]
+name = "miette-derive"
+version = "5.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4901771e1d44ddb37964565c654a3223ba41a594d02b8da471cc4464912b5cfa"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[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.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
+dependencies = [
+ "libc",
+ "log",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "multimap"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
+
+[[package]]
+name = "nix"
+version = "0.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c"
+dependencies = [
+ "bitflags",
+ "cc",
+ "cfg-if",
+ "libc",
+ "memoffset 0.6.5",
+]
+
+[[package]]
+name = "nix"
+version = "0.24.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "libc",
+ "memoffset 0.6.5",
+]
+
+[[package]]
+name = "nom"
+version = "5.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b"
+dependencies = [
+ "memchr",
+ "version_check",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "notify"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d9ba6c734de18ca27c8cef5cd7058aa4ac9f63596131e4c7e41e579319032a2"
+dependencies = [
+ "bitflags",
+ "crossbeam-channel",
+ "filetime",
+ "fsevent-sys",
+ "inotify",
+ "kqueue",
+ "libc",
+ "mio",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "notify-debouncer-full"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4812c1eb49be776fb8df4961623bdc01ec9dfdc1abe8211ceb09150a2e64219"
+dependencies = [
+ "crossbeam-channel",
+ "file-id",
+ "notify",
+ "parking_lot",
+ "walkdir",
+]
+
+[[package]]
+name = "num-derive"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "object"
+version = "0.30.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
+name = "ordered-float"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "ordered-float"
+version = "3.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fc2dbde8f8a79f2102cc474ceb0ad68e3b80b85289ea62389b60e66777e4213"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "os_str_bytes"
+version = "6.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac"
+
+[[package]]
+name = "owo-colors"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
+
+[[package]]
+name = "parking"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e"
+
+[[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.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.3.5",
+ "smallvec",
+ "windows-targets 0.48.0",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
+
+[[package]]
+name = "pest"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70"
+dependencies = [
+ "thiserror",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411"
+dependencies = [
+ "once_cell",
+ "pest",
+ "sha2 0.10.6",
+]
+
+[[package]]
+name = "petgraph"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4"
+dependencies = [
+ "fixedbitset",
+ "indexmap",
+]
+
+[[package]]
+name = "phf"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
+dependencies = [
+ "phf_shared 0.10.0",
+]
+
+[[package]]
+name = "phf"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c"
+dependencies = [
+ "phf_macros",
+ "phf_shared 0.11.1",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770"
+dependencies = [
+ "phf_generator",
+ "phf_shared 0.11.1",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf"
+dependencies = [
+ "phf_shared 0.11.1",
+ "rand",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92aacdc5f16768709a569e913f7451034034178b05bdc8acda226659a3dccc66"
+dependencies = [
+ "phf_generator",
+ "phf_shared 0.11.1",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "polling"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
+dependencies = [
+ "autocfg",
+ "bitflags",
+ "cfg-if",
+ "concurrent-queue",
+ "libc",
+ "log",
+ "pin-project-lite",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "prettyplease"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86"
+dependencies = [
+ "proc-macro2",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "prost"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd"
+dependencies = [
+ "bytes",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270"
+dependencies = [
+ "bytes",
+ "heck 0.4.1",
+ "itertools",
+ "lazy_static",
+ "log",
+ "multimap",
+ "petgraph",
+ "prettyplease",
+ "prost",
+ "prost-types",
+ "regex",
+ "syn 1.0.109",
+ "tempfile",
+ "which",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4"
+dependencies = [
+ "anyhow",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13"
+dependencies = [
+ "prost",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
+dependencies = [
+ "getrandom",
+ "redox_syscall 0.2.16",
+ "thiserror",
+]
+
+[[package]]
+name = "regex"
+version = "1.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
+
+[[package]]
+name = "rmp"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f"
+dependencies = [
+ "byteorder",
+ "num-traits",
+ "paste",
+]
+
+[[package]]
+name = "rmp-serde"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5b13be192e0220b8afb7222aa5813cb62cc269ebb5cac346ca6487681d2913e"
+dependencies = [
+ "byteorder",
+ "rmp",
+ "serde",
+]
+
+[[package]]
+name = "rust-plugin-example"
+version = "0.1.0"
+dependencies = [
+ "ansi_term",
+ "chrono",
+ "fuzzy-matcher",
+ "unicode-width",
+ "zellij-tile",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver 1.0.17",
+]
+
+[[package]]
+name = "rustix"
+version = "0.37.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0"
+dependencies = [
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+
+[[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 = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "semver"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
+dependencies = [
+ "semver-parser",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
+
+[[package]]
+name = "semver-parser"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
+dependencies = [
+ "pest",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.164"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde-value"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
+dependencies = [
+ "ordered-float 2.10.0",
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.164"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.8.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b"
+dependencies = [
+ "indexmap",
+ "ryu",
+ "serde",
+ "yaml-rust",
+]
+
+[[package]]
+name = "sha2"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
+dependencies = [
+ "block-buffer 0.9.0",
+ "cfg-if",
+ "cpufeatures",
+ "digest 0.9.0",
+ "opaque-debug",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "shellexpand"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b"
+dependencies = [
+ "dirs 5.0.1",
+]
+
+[[package]]
+name = "signal-hook"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "siphasher"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
+
+[[package]]
+name = "slab"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "smawk"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
+
+[[package]]
+name = "socket2"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "spinning"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d4f0e86297cad2658d92a707320d87bf4e6ae1050287f51d19b67ef3f153a7b"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "strip-ansi-escapes"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "011cbb39cf7c1f62871aea3cc46e5817b0937b49e9447370c93cacbe93a766d8"
+dependencies = [
+ "vte 0.10.1",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "strum"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c"
+
+[[package]]
+name = "strum_macros"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149"
+dependencies = [
+ "heck 0.3.2",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "supports-color"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354"
+dependencies = [
+ "is-terminal",
+ "is_ci",
+]
+
+[[package]]
+name = "supports-hyperlinks"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f84231692eb0d4d41e4cdd0cabfdd2e6cd9e255e65f80c9aa7c98dd502b4233d"
+dependencies = [
+ "is-terminal",
+]
+
+[[package]]
+name = "supports-unicode"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b6c2cb240ab5dd21ed4906895ee23fe5a48acdbd15a3ce388e7b62a9b66baf7"
+dependencies = [
+ "is-terminal",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "fastrand",
+ "redox_syscall 0.3.5",
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
+dependencies = [
+ "winapi-util",
+]
+
+[[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 = "terminfo"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da31aef70da0f6352dbcb462683eb4dd2bfad01cf3fc96cf204547b9a839a585"
+dependencies = [
+ "dirs 4.0.0",
+ "fnv",
+ "nom 5.1.3",
+ "phf 0.11.1",
+ "phf_codegen",
+]
+
+[[package]]
+name = "termios"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "termwiz"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9509a978a10fcbace4991deae486ae10885e0f4c2c465123e08c9714a90648fa"
+dependencies = [
+ "anyhow",
+ "base64",
+ "bitflags",
+ "filedescriptor",
+ "finl_unicode",
+ "fixedbitset",
+ "hex",
+ "lazy_static",
+ "libc",
+ "log",
+ "memmem",
+ "nix 0.24.3",
+ "num-derive",
+ "num-traits",
+ "ordered-float 3.7.0",
+ "pest",
+ "pest_derive",
+ "phf 0.10.1",
+ "regex",
+ "semver 0.11.0",
+ "sha2 0.9.9",
+ "signal-hook 0.1.17",
+ "siphasher",
+ "terminfo",
+ "termios",
+ "thiserror",
+ "ucd-trie",
+ "unicode-segmentation",
+ "vtparse",
+ "wezterm-bidi",
+ "wezterm-color-types",
+ "wezterm-dynamic",
+ "winapi",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d"
+dependencies = [
+ "smawk",
+ "unicode-linebreak",
+ "unicode-width",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
+
+[[package]]
+name = "thiserror"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "thread-id"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ee93aa2b8331c0fec9091548843f2c90019571814057da3b783f9de09349d73"
+dependencies = [
+ "libc",
+ "redox_syscall 0.2.16",
+ "winapi",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "time"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
+dependencies = [
+ "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
+ "winapi",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "to_method"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8"
+
+[[package]]
+name = "typemap-ors"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a68c24b707f02dd18f1e4ccceb9d49f2058c2fb86384ef9972592904d7a28867"
+dependencies = [
+ "unsafe-any-ors",
+]
+
+[[package]]
+name = "typenum"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
+
+[[package]]
+name = "unicode-linebreak"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
+dependencies = [
+ "hashbrown",
+ "regex",
+]
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+
+[[package]]
+name = "unsafe-any-ors"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a303d30665362d9680d7d91d78b23f5f899504d4f08b3c4cf08d055d87c0ad"
+dependencies = [
+ "destructure_traitobject",
+]
+
+[[package]]
+name = "url"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
+name = "uuid"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
+dependencies = [
+ "getrandom",
+ "serde",
+]
+
+[[package]]
+name = "value-bag"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "vte"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983"
+dependencies = [
+ "arrayvec",
+ "utf8parse",
+ "vte_generate_state_changes",
+]
+
+[[package]]
+name = "vte"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197"
+dependencies = [
+ "utf8parse",
+ "vte_generate_state_changes",
+]
+
+[[package]]
+name = "vte_generate_state_changes"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "vtparse"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0"
+dependencies = [
+ "utf8parse",
+]
+
+[[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.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[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.18",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[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.18",
+ "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 = "web-sys"
+version = "0.3.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "wezterm-bidi"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1560382cf39b0fa92473eae4d5b3772f88c63202cbf5a72c35db72ba99e66c36"
+dependencies = [
+ "log",
+ "wezterm-dynamic",
+]
+
+[[package]]
+name = "wezterm-color-types"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c6e7a483dd2785ba72705c51e8b1be18300302db2a78368dac9bc8773857777"
+dependencies = [
+ "csscolorparser",
+ "deltae",
+ "lazy_static",
+ "wezterm-dynamic",
+]
+
+[[package]]
+name = "wezterm-dynamic"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75e78c0cc60a76de5d93f9dad05651105351e151b6446ab305514945d7588aa"
+dependencies = [
+ "log",
+ "ordered-float 3.7.0",
+ "strsim",
+ "thiserror",
+ "wezterm-dynamic-derive",
+]
+
+[[package]]
+name = "wezterm-dynamic-derive"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c9f5ef318442d07b3d071f9f43ea40b80992f87faee14bb4d017b6991c307f0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "which"
+version = "4.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
+dependencies = [
+ "either",
+ "libc",
+ "once_cell",
+]
+
+[[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-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[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-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[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_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[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_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[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_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[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_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[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_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
+dependencies = [
+ "linked-hash-map",
+]
+
+[[package]]
+name = "zellij-tile"
+version = "0.38.0"
+dependencies = [
+ "clap",
+ "serde",
+ "serde_json",
+ "strum",
+ "strum_macros",
+ "zellij-utils",
+]
+
+[[package]]
+name = "zellij-utils"
+version = "0.38.0"
+dependencies = [
+ "anyhow",
+ "async-channel",
+ "async-std",
+ "backtrace",
+ "clap",
+ "clap_complete",
+ "colored",
+ "colorsys",
+ "crossbeam",
+ "directories-next",
+ "include_dir",
+ "interprocess",
+ "kdl",
+ "lazy_static",
+ "libc",
+ "log",
+ "log4rs",
+ "miette",
+ "nix 0.23.2",
+ "notify-debouncer-full",
+ "once_cell",
+ "percent-encoding",
+ "prost",
+ "prost-build",
+ "regex",
+ "rmp-serde",
+ "serde",
+ "serde_json",
+ "shellexpand",
+ "signal-hook 0.3.15",
+ "strip-ansi-escapes",
+ "strum",
+ "strum_macros",
+ "tempfile",
+ "termwiz",
+ "thiserror",
+ "unicode-width",
+ "url",
+ "uuid",
+ "vte 0.11.1",
+]
diff --git a/default-plugins/session-manager/Cargo.toml b/default-plugins/session-manager/Cargo.toml
new file mode 100644
index 00000000..c2030f96
--- /dev/null
+++ b/default-plugins/session-manager/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "session-manager"
+version = "0.1.0"
+authors = ["Aram Drevekenin <aram@poor.dev>"]
+edition = "2018"
+
+[dependencies]
+ansi_term = "0.12.1"
+zellij-tile = { path = "../../zellij-tile" }
+chrono = "0.4.0"
+fuzzy-matcher = "0.3.7"
+unicode-width = "0.1.10"
diff --git a/default-plugins/session-manager/src/main.rs b/default-plugins/session-manager/src/main.rs
new file mode 100644
index 00000000..66ff54c4
--- /dev/null
+++ b/default-plugins/session-manager/src/main.rs
@@ -0,0 +1,208 @@
+mod session_list;
+mod ui;
+use zellij_tile::prelude::*;
+
+use std::collections::BTreeMap;
+
+use ui::{
+ components::{render_controls_line, render_new_session_line, render_prompt, Colors},
+ SessionUiInfo,
+};
+
+use session_list::SessionList;
+
+#[derive(Default)]
+struct State {
+ session_name: Option<String>,
+ sessions: SessionList,
+ selected_index: Option<usize>,
+ search_term: String,
+ new_session_name: Option<String>,
+ colors: Colors,
+}
+
+register_plugin!(State);
+
+impl ZellijPlugin for State {
+ fn load(&mut self, _configuration: BTreeMap<String, String>) {
+ subscribe(&[
+ EventType::ModeUpdate,
+ EventType::SessionUpdate,
+ EventType::Key,
+ ]);
+ }
+
+ fn update(&mut self, event: Event) -> bool {
+ let mut should_render = false;
+ match event {
+ Event::ModeUpdate(mode_info) => {
+ self.colors = Colors::new(mode_info.style.colors);
+ should_render = true;
+ },
+ Event::Key(key) => {
+ should_render = self.handle_key(key);
+ },
+ Event::PermissionRequestResult(_result) => {
+ should_render = true;
+ },
+ Event::SessionUpdate(session_infos) => {
+ self.update_session_infos(session_infos);
+ should_render = true;
+ },
+ _ => (),
+ };
+ should_render
+ }
+
+ fn render(&mut self, rows: usize, cols: usize) {
+ render_prompt(
+ self.new_session_name.is_some(),
+ &self.search_term,
+ self.colors,
+ );
+ let room_for_list = rows.saturating_sub(5); // search line and controls
+ self.sessions.update_rows(room_for_list);
+ let list = self
+ .sessions
+ .render(room_for_list, cols.saturating_sub(7), self.colors); // 7 for various ui
+ for line in list {
+ println!("{}", line.render());
+ }
+ render_new_session_line(
+ &self.new_session_name,
+ self.sessions.is_searching,
+ self.colors,
+ );
+ render_controls_line(self.sessions.is_searching, rows, cols, self.colors);
+ }
+}
+
+impl State {
+ fn reset_selected_index(&mut self) {
+ self.selected_index = None;
+ }
+ fn handle_key(&mut self, key: Key) -> bool {
+ let mut should_render = false;
+ if let Key::Right = key {
+ if self.new_session_name.is_none() {
+ self.sessions.result_expand();
+ }
+ should_render = true;
+ } else if let Key::Left = key {
+ if self.new_session_name.is_none() {
+ self.sessions.result_shrink();
+ }
+ should_render = true;
+ } else if let Key::Down = key {
+ if self.new_session_name.is_none() {
+ self.sessions.move_selection_down();
+ }
+ should_render = true;
+ } else if let Key::Up = key {
+ if self.new_session_name.is_none() {
+ self.sessions.move_selection_up();
+ }
+ should_render = true;
+ } else if let Key::Char(character) = key {
+ if character == '\n' {
+ self.handle_selection();
+ } else if let Some(new_session_name) = self.new_session_name.as_mut() {
+ new_session_name.push(character);
+ } else {
+ self.search_term.push(character);
+ self.sessions
+ .update_search_term(&self.search_term, &self.colors);
+ }
+ should_render = true;
+ } else if let Key::Backspace = key {
+ if let Some(new_session_name) = self.new_session_name.as_mut() {
+ if new_session_name.is_empty() {
+ self.new_session_name = None;
+ } else {
+ new_session_name.pop();
+ }
+ } else {
+ self.search_term.pop();
+ self.sessions
+ .update_search_term(&self.search_term, &self.colors);
+ }
+ should_render = true;
+ } else if let Key::Ctrl('w') = key {
+ if self.sessions.is_searching {
+ // no-op
+ } else if self.new_session_name.is_some() {
+ self.new_session_name = None;
+ } else {
+ self.new_session_name = Some(String::new());
+ }
+ should_render = true;
+ } else if let Key::Ctrl('c') = key {
+ if let Some(new_session_name) = self.new_session_name.as_mut() {
+ if new_session_name.is_empty() {
+ self.new_session_name = None;
+ } else {
+ new_session_name.clear()
+ }
+ } else if !self.search_term.is_empty() {
+ self.search_term.clear();
+ self.sessions
+ .update_search_term(&self.search_term, &self.colors);
+ self.reset_selected_index();
+ } else {
+ self.reset_selected_index();
+ hide_self();
+ }
+ should_render = true;
+ } else if let Key::Esc = key {
+ hide_self();
+ }
+ should_render
+ }
+ fn handle_selection(&mut self) {
+ if let Some(new_session_name) = &self.new_session_name {
+ if new_session_name.is_empty() {
+ switch_session(None);
+ } else if self.session_name.as_ref() == Some(new_session_name) {
+ // noop - we're already here!
+ self.new_session_name = None;
+ } else {
+ switch_session(Some(new_session_name));
+ }
+ } else if let Some(selected_session_name) = self.sessions.get_selected_session_name() {
+ let selected_tab = self.sessions.get_selected_tab_position();
+ let selected_pane = self.sessions.get_selected_pane_id();
+ let is_current_session = self.sessions.selected_is_current_session();
+ if is_current_session {
+ if let Some((pane_id, is_plugin)) = selected_pane {
+ if is_plugin {
+ focus_plugin_pane(pane_id, true);
+ } else {
+ focus_terminal_pane(pane_id, true);
+ }
+ } else if let Some(tab_position) = selected_tab {
+ go_to_tab(tab_position as u32);
+ }
+ } else {
+ switch_session_with_focus(&selected_session_name, selected_tab, selected_pane);
+ }
+ }
+ hide_self();
+ }
+ fn update_session_infos(&mut self, session_infos: Vec<SessionInfo>) {
+ let session_infos: Vec<SessionUiInfo> = session_infos
+ .iter()
+ .map(|s| SessionUiInfo::from_session_info(s))
+ .collect();
+ let current_session_name = session_infos.iter().find_map(|s| {
+ if s.is_current_session {
+ Some(s.name.clone())
+ } else {
+ None
+ }
+ });
+ if let Some(current_session_name) = current_session_name {
+ self.session_name = Some(current_session_name);
+ }
+ self.sessions.set_sessions(session_infos);
+ }
+}
diff --git a/default-plugins/session-manager/src/session_list.rs b/default-plugins/session-manager/src/session_list.rs
new file mode 100644
index 00000000..6cf6f716
--- /dev/null
+++ b/default-plugins/session-manager/src/session_list.rs
@@ -0,0 +1,381 @@
+use fuzzy_matcher::skim::SkimMatcherV2;
+use fuzzy_matcher::FuzzyMatcher;
+
+use crate::ui::{
+ components::{Colors, LineToRender, ListItem},
+ SessionUiInfo,
+};
+
+#[derive(Debug, Default)]
+pub struct SessionList {
+ pub session_ui_infos: Vec<SessionUiInfo>,
+ pub selected_index: SelectedIndex,
+ pub selected_search_index: Option<usize>,
+ pub search_results: Vec<SearchResult>,
+ pub is_searching: bool,
+}
+
+impl SessionList {
+ pub fn set_sessions(&mut self, mut session_ui_infos: Vec<SessionUiInfo>) {
+ session_ui_infos.sort_unstable_by(|a, b| {
+ if a.is_current_session {
+ std::cmp::Ordering::Less
+ } else if b.is_current_session {
+ std::cmp::Ordering::Greater
+ } else {
+ a.name.cmp(&b.name)
+ }
+ });
+ self.session_ui_infos = session_ui_infos;
+ }
+ pub fn update_search_term(&mut self, search_term: &str, colors: &Colors) {
+ let mut flattened_assets = self.flatten_assets(colors);
+ let mut matches = vec![];
+ let matcher = SkimMatcherV2::default().use_cache(true);
+ for (list_item, session_name, tab_position, pane_id, is_current_session) in
+ flattened_assets.drain(..)
+ {
+ if let Some((score, indices)) = matcher.fuzzy_indices(&list_item.name, &search_term) {
+ matches.push(SearchResult::new(
+ score,
+ indices,
+ list_item,
+ session_name,
+ tab_position,
+ pane_id,
+ is_current_session,
+ ));
+ }
+ }
+ matches.sort_by(|a, b| b.score.cmp(&a.score));
+ self.search_results = matches;
+ self.is_searching = !search_term.is_empty();
+ self.selected_search_index = Some(0);
+ }
+ fn flatten_assets(
+ &self,
+ colors: &Colors,
+ ) -> Vec<(ListItem, String, Option<usize>, Option<(u32, bool)>, bool)> {
+ // list_item, session_name, tab_position, (pane_id, is_plugin), is_current_session
+ let mut list_items = vec![];
+ for session in &self.session_ui_infos {
+ let session_name = session.name.clone();
+ let is_current_session = session.is_current_session;
+ list_items.push((
+ ListItem::from_session_info(session, *colors),
+ session_name.clone(),
+ None,
+ None,
+ is_current_session,
+ ));
+ for tab in &session.tabs {
+ let tab_position = tab.position;
+ list_items.push((
+ ListItem::from_tab_info(session, tab, *colors),
+ session_name.clone(),
+ Some(tab_position),
+ None,
+ is_current_session,
+ ));
+ for pane in &tab.panes {
+ let pane_id = (pane.pane_id, pane.is_plugin);
+ list_items.push((
+ ListItem::from_pane_info(session, tab, pane, *colors),
+ session_name.clone(),
+ Some(tab_position),
+ Some(pane_id),
+ is_current_session,
+ ));
+ }
+ }
+ }
+ list_items
+ }
+ pub fn get_selected_session_name(&self) -> Option<String> {
+ if self.is_searching {
+ self.selected_search_index
+ .and_then(|i| self.search_results.get(i))
+ .map(|s| s.session_name.clone())
+ } else {
+ self.selected_index
+ .0
+ .and_then(|i| self.session_ui_infos.get(i))
+ .map(|s_i| s_i.name.clone())
+ }
+ }
+ pub fn selected_is_current_session(&self) -> bool {
+ if self.is_searching {
+ self.selected_search_index
+ .and_then(|i| self.search_results.get(i))
+ .map(|s| s.is_current_session)
+ .unwrap_or(false)
+ } else {
+ self.selected_index
+ .0
+ .and_then(|i| self.session_ui_infos.get(i))
+ .map(|s_i| s_i.is_current_session)
+ .unwrap_or(false)
+ }
+ }
+ pub fn get_selected_tab_position(&self) -> Option<usize> {
+ if self.is_searching {
+ self.selected_search_index
+ .and_then(|i| self.search_results.get(i))
+ .and_then(|s| s.tab_position)
+ } else {
+ self.selected_index
+ .0
+ .and_then(|i| self.session_ui_infos.get(i))
+ .and_then(|s_i| {
+ self.selected_index
+ .1
+ .and_then(|i| s_i.tabs.get(i))
+ .map(|t| t.position)
+ })
+ }
+ }
+ pub fn get_selected_pane_id(&self) -> Option<(u32, bool)> {
+ // (pane_id, is_plugin)
+ if self.is_searching {
+ self.selected_search_index
+ .and_then(|i| self.search_results.get(i))
+ .and_then(|s| s.pane_id)
+ } else {
+ self.selected_index
+ .0
+ .and_then(|i| self.session_ui_infos.get(i))
+ .and_then(|s_i| {
+ self.selected_index
+ .1
+ .and_then(|i| s_i.tabs.get(i))
+ .and_then(|t| {
+ self.selected_index
+ .2
+ .and_then(|i| t.panes.get(i))
+ .map(|p| (p.pane_id, p.is_plugin))
+ })
+ })
+ }
+ }
+ pub fn move_selection_down(&mut self) {
+ if self.is_searching {
+ match self.selected_search_index.as_mut() {
+ Some(search_index) => {
+ *search_index = search_index.saturating_add(1);
+ },
+ None => {
+ if !self.search_results.is_empty() {
+ self.selected_search_index = Some(0);
+ }
+ },
+ }
+ } else {
+ match self.selected_index {
+ SelectedIndex(None, None, None) => {
+ if !self.session_ui_infos.is_empty() {
+ self.selected_index.0 = Some(0);
+ }
+ },
+ SelectedIndex(Some(selected_session), None, None) => {
+ if self.session_ui_infos.len() > selected_session + 1 {
+ self.selected_index.0 = Some(selected_session + 1);
+ } else {
+ self.selected_index.0 = None;
+ self.selected_index.1 = None;
+ self.selected_index.2 = None;
+ }
+ },
+ SelectedIndex(Some(selected_session), Some(selected_tab), None) => {
+ if self
+ .get_session(selected_session)
+ .map(|s| s.tabs.len() > selected_tab + 1)
+ .unwrap_or(false)
+ {
+ self.selected_index.1 = Some(selected_tab + 1);
+ } else {
+ self.selected_index.1 = Some(0);
+ }
+ },
+ SelectedIndex(Some(selected_session), Some(selected_tab), Some(selected_pane)) => {
+ if self
+ .get_session(selected_session)
+ .and_then(|s| s.tabs.get(selected_tab))
+ .map(|t| t.panes.len() > selected_pane + 1)
+ .unwrap_or(false)
+ {
+ self.selected_index.2 = Some(selected_pane + 1);
+ } else {
+ self.selected_index.2 = Some(0);
+ }
+ },
+ _ => {},
+ }
+ }
+ }
+ pub fn move_selection_up(&mut self) {
+ if self.is_searching {
+ match self.selected_search_index.as_mut() {
+ Some(search_index) => {
+ *search_index = search_index.saturating_sub(1);
+ },
+ None => {
+ if !self.search_results.is_empty() {
+ self.selected_search_index = Some(0);
+ }
+ },
+ }
+ } else {
+ match self.selected_index {
+ SelectedIndex(None, None, None) => {
+ if !self.session_ui_infos.is_empty() {
+ self.selected_index.0 = Some(self.session_ui_infos.len().saturating_sub(1))
+ }
+ },
+ SelectedIndex(Some(selected_session), None, None) => {
+ if selected_session > 0 {
+ self.selected_index.0 = Some(selected_session - 1);
+ } else {
+ self.selected_index.0 = None;
+ }
+ },
+ SelectedIndex(Some(selected_session), Some(selected_tab), None) => {
+ if selected_tab > 0 {
+ self.selected_index.1 = Some(selected_tab - 1);
+ } else {
+ let tab_count = self
+ .get_session(selected_session)
+ .map(|s| s.tabs.len())
+ .unwrap_or(0);
+ self.selected_index.1 = Some(tab_count.saturating_sub(1))
+ }
+ },
+ SelectedIndex(Some(selected_session), Some(selected_tab), Some(selected_pane)) => {
+ if selected_pane > 0 {
+ self.selected_index.2 = Some(selected_pane - 1);
+ } else {
+ let pane_count = self
+ .get_session(selected_session)
+ .and_then(|s| s.tabs.get(selected_tab))
+ .map(|t| t.panes.len())
+ .unwrap_or(0);
+ self.selected_index.2 = Some(pane_count.saturating_sub(1))
+ }
+ },
+ _ => {},
+ }
+ }
+ }
+ fn get_session(&self, index: usize) -> Option<&SessionUiInfo> {
+ self.session_ui_infos.get(index)
+ }
+ pub fn result_expand(&mut self) {
+ // we can't move this to SelectedIndex because the borrow checker is mean
+ match self.selected_index {
+ SelectedIndex(Some(selected_session), None, None) => {
+ let selected_session_has_tabs = self
+ .get_session(selected_session)
+ .map(|s| !s.tabs.is_empty())
+ .unwrap_or(false);
+ if selected_session_has_tabs {
+ self.selected_index.1 = Some(0);
+ }
+ },
+ SelectedIndex(Some(selected_session), Some(selected_tab), None) => {
+ let selected_tab_has_panes = self
+ .get_session(selected_session)
+ .and_then(|s| s.tabs.get(selected_tab))
+ .map(|t| !t.panes.is_empty())
+ .unwrap_or(false);
+ if selected_tab_has_panes {
+ self.selected_index.2 = Some(0);
+ }
+ },
+ _ => {},
+ }
+ }
+ pub fn result_shrink(&mut self) {
+ self.selected_index.result_shrink();
+ }
+ pub fn update_rows(&mut self, rows: usize) {
+ if let Some(search_result_rows_until_selected) = self.selected_search_index.map(|i| {
+ self.search_results
+ .iter()
+ .enumerate()
+ .take(i + 1)
+ .fold(0, |acc, s| acc + s.1.lines_to_render())
+ }) {
+ if search_result_rows_until_selected > rows
+ || self.selected_search_index >= Some(self.search_results.len())
+ {
+ self.selected_search_index = None;
+ }
+ }
+ }
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct SelectedIndex(pub Option<usize>, pub Option<usize>, pub Option<usize>);
+
+impl SelectedIndex {
+ pub fn tabs_are_visible(&self) -> bool {
+ self.1.is_some()
+ }
+ pub fn panes_are_visible(&self) -> bool {
+ self.2.is_some()
+ }
+ pub fn selected_tab_index(&self) -> Option<usize> {
+ self.1
+ }
+ pub fn session_index_is_selected(&self, index: usize) -> bool {
+ self.0 == Some(index)
+ }
+ pub fn result_shrink(&mut self) {
+ match self {
+ SelectedIndex(Some(_selected_session), None, None) => self.0 = None,
+ SelectedIndex(Some(_selected_session), Some(_selected_tab), None) => self.1 = None,
+ SelectedIndex(Some(_selected_session), Some(_selected_tab), Some(_selected_pane)) => {
+ self.2 = None
+ },
+ _ => {},
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct SearchResult {
+ score: i64,
+ indices: Vec<usize>,
+ list_item: ListItem,
+ session_name: String,
+ tab_position: Option<usize>,
+ pane_id: Option<(u32, bool)>,
+ is_current_session: bool,
+}
+
+impl SearchResult {
+ pub fn new(
+ score: i64,
+ indices: Vec<usize>,
+ list_item: ListItem,
+ session_name: String,
+ tab_position: Option<usize>,
+ pane_id: Option<(u32, bool)>,
+ is_current_session: bool,
+ ) -> Self {
+ SearchResult {
+ score,
+ indices,
+ list_item,
+ session_name,
+ tab_position,
+ pane_id,
+ is_current_session,
+ }
+ }
+ pub fn lines_to_render(&self) -> usize {
+ self.list_item.line_count()
+ }
+ pub fn render(&self, max_width: usize) -> Vec<LineToRender> {
+ self.list_item.render(Some(self.indices.clone()), max_width)
+ }
+}
diff --git a/default-plugins/session-manager/src/ui/components.rs b/default-plugins/session-manager/src/ui/components.rs
new file mode 100644
index 00000000..6b606589
--- /dev/null
+++ b/default-plugins/session-manager/src/ui/components.rs
@@ -0,0 +1,577 @@
+use unicode_width::UnicodeWidthChar;
+use unicode_width::UnicodeWidthStr;
+use zellij_tile::prelude::*;
+
+use crate::ui::{PaneUiInfo, SessionUiInfo, TabUiInfo};
+
+#[derive(Debug)]
+pub struct ListItem {
+ pub name: String,
+ pub session_name: Option<Vec<UiSpan>>,
+ pub tab_name: Option<Vec<UiSpan>>,
+ pub pane_name: Option<Vec<UiSpan>>,
+ colors: Colors,
+}
+
+impl ListItem {
+ pub fn from_session_info(session_ui_info: &SessionUiInfo, colors: Colors) -> Self {
+ let session_ui_line = build_session_ui_line(session_ui_info, colors);
+ ListItem {
+ name: session_ui_info.name.clone(),
+ session_name: Some(session_ui_line),
+ tab_name: None,
+ pane_name: None,
+ colors,
+ }
+ }
+ pub fn from_tab_info(
+ session_ui_info: &SessionUiInfo,
+ tab_ui_info: &TabUiInfo,
+ colors: Colors,
+ ) -> Self {
+ let session_ui_line = build_session_ui_line(session_ui_info, colors);
+ let tab_ui_line = build_tab_ui_line(tab_ui_info, colors);
+ ListItem {
+ name: tab_ui_info.name.clone(),
+ session_name: Some(session_ui_line),
+ tab_name: Some(tab_ui_line),
+ pane_name: None,
+ colors,
+ }
+ }
+ pub fn from_pane_info(
+ session_ui_info: &SessionUiInfo,
+ tab_ui_info: &TabUiInfo,
+ pane_ui_info: &PaneUiInfo,
+ colors: Colors,
+ ) -> Self {
+ let session_ui_line = build_session_ui_line(session_ui_info, colors);
+ let tab_ui_line = build_tab_ui_line(tab_ui_info, colors);
+ let pane_ui_line = build_pane_ui_line(pane_ui_info, colors);
+ ListItem {
+ name: pane_ui_info.name.clone(),
+ session_name: Some(session_ui_line),
+ tab_name: Some(tab_ui_line),
+ pane_name: Some(pane_ui_line),
+ colors,
+ }
+ }
+ pub fn line_count(&self) -> usize {
+ let mut line_count = 0;
+ if self.session_name.is_some() {
+ line_count += 1
+ };
+ if self.tab_name.is_some() {
+ line_count += 1
+ };
+ if self.pane_name.is_some() {
+ line_count += 1
+ };
+ line_count
+ }
+ pub fn render(&self, indices: Option<Vec<usize>>, max_cols: usize) -> Vec<LineToRender> {
+ let mut lines_to_render = vec![];
+ if let Some(session_name) = &self.session_name {
+ let indices = if self.tab_name.is_none() && self.pane_name.is_none() {
+ indices.clone()
+ } else {
+ None
+ };
+ let mut line_to_render = LineToRender::new(self.colors);
+ let mut remaining_cols = max_cols;
+ for span in session_name {
+ span.render(
+ indices
+ .clone()
+ .map(|i| (SpanStyle::ForegroundBold(self.colors.palette.magenta), i)),
+ &mut line_to_render,
+ &mut remaining_cols,
+ );
+ }
+ lines_to_render.push(line_to_render);
+ }
+ if let Some(tab_name) = &self.tab_name {
+ let indices = if self.pane_name.is_none() {
+ indices.clone()
+ } else {
+ None
+ };
+ let mut line_to_render = LineToRender::new(self.colors);
+ let mut remaining_cols = max_cols;
+ for span in tab_name {
+ span.render(
+ indices
+ .clone()
+ .map(|i| (SpanStyle::ForegroundBold(self.colors.palette.magenta), i)),
+ &mut line_to_render,
+ &mut remaining_cols,
+ );
+ }
+ lines_to_render.push(line_to_render);
+ }
+ if let Some(pane_name) = &self.pane_name {
+ let mut line_to_render = LineToRender::new(self.colors);
+ let mut remaining_cols = max_cols;
+ for span in pane_name {
+ span.render(
+ indices
+ .clone()
+ .map(|i| (SpanStyle::ForegroundBold(self.colors.palette.magenta), i)),
+ &mut line_to_render,
+ &mut remaining_cols,
+ );
+ }
+ lines_to_render.push(line_to_render);
+ }
+ lines_to_render
+ }
+}
+
+#[derive(Debug)]
+pub enum UiSpan {
+ UiSpanTelescope(UiSpanTelescope),
+ TruncatableUiSpan(TruncatableUiSpan),
+}
+
+impl UiSpan {
+ pub fn render(
+ &self,
+ indices: Option<(SpanStyle, Vec<usize>)>,
+ line_to_render: &mut LineToRender,
+ remaining_cols: &mut usize,
+ ) {
+ match self {
+ UiSpan::UiSpanTelescope(ui_span_telescope) => {
+ ui_span_telescope.render(line_to_render, remaining_cols)
+ },
+ UiSpan::TruncatableUiSpan(truncatable_ui_span) => {
+ truncatable_ui_span.render(indices, line_to_render, remaining_cols)
+ },
+ }
+ }
+}
+
+#[allow(dead_code)] // in the future this will be moved to be its own component
+#[derive(Debug)]
+pub enum SpanStyle {
+ None,
+ Bold,
+ Foreground(PaletteColor),
+ ForegroundBold(PaletteColor),
+}
+
+impl SpanStyle {
+ pub fn style_string(&self, to_style: &str) -> String {
+ match self {
+ SpanStyle::None => to_style.to_owned(),
+ SpanStyle::Bold => format!("\u{1b}[1m{}\u{1b}[22m", to_style),
+ SpanStyle::Foreground(color) => match color {
+ PaletteColor::EightBit(byte) => {
+ format!("\u{1b}[38;5;{byte}m{}\u{1b}[39m", to_style)
+ },
+ PaletteColor::Rgb((r, g, b)) => {
+ format!("\u{1b}[38;2;{};{};{}m{}\u{1b}[39m", r, g, b, to_style)
+ },
+ },
+ SpanStyle::ForegroundBold(color) => match color {
+ PaletteColor::EightBit(byte) => {
+ format!("\u{1b}[38;5;{byte};1m{}\u{1b}[39;22m", to_style)
+ },
+ PaletteColor::Rgb((r, g, b)) => {
+ format!("\u{1b}[38;2;{};{};{};1m{}\u{1b}[39;22m", r, g, b, to_style)
+ },
+ },
+ }
+ }
+}
+
+impl Default for SpanStyle {
+ fn default() -> Self {
+ SpanStyle::None
+ }
+}
+
+#[derive(Debug, Default)]
+pub struct TruncatableUiSpan {
+ text: String,
+ style: SpanStyle,
+}
+
+impl TruncatableUiSpan {
+ pub fn new(text: String, style: SpanStyle) -> Self {
+ TruncatableUiSpan { text, style }
+ }
+ pub fn render(
+ &self,
+ indices: Option<(SpanStyle, Vec<usize>)>,
+ line_to_render: &mut LineToRender,
+ remaining_cols: &mut usize,
+ ) {
+ let mut rendered = String::new();
+ let truncated = if *remaining_cols >= self.text.width() {
+ self.text.clone()
+ } else {
+ let mut truncated = String::new();
+ for character in self.text.chars() {
+ if truncated.width() + character.width().unwrap_or(0) <= *remaining_cols {
+ truncated.push(character);
+ } else {
+ break;
+ }
+ }
+ truncated
+ };
+ match indices {
+ Some((index_style, indices)) => {
+ for (i, character) in truncated.chars().enumerate() {
+ // TODO: optimize this by splitting the string up by its indices and only pushing those
+ // chu8nks
+ if indices.contains(&i) {
+ rendered.push_str(&index_style.style_string(&character.to_string()));
+ } else {
+ rendered.push_str(&self.style.style_string(&character.to_string()));
+ }
+ }
+ },
+ None => {
+ rendered.push_str(&self.style.style_string(&truncated));
+ },
+ }
+ *remaining_cols = remaining_cols.saturating_sub(truncated.width());
+ line_to_render.append(&rendered);
+ }
+}
+
+#[derive(Debug, Default)]
+pub struct UiSpanTelescope(Vec<StringAndLength>);
+
+impl UiSpanTelescope {
+ pub fn new(string_and_lengths: Vec<StringAndLength>) -> Self {
+ UiSpanTelescope(string_and_lengths)
+ }
+ pub fn render(&self, line_to_render: &mut LineToRender, remaining_cols: &mut usize) {
+ for string_and_length in &self.0 {
+ if string_and_length.length < *remaining_cols {
+ line_to_render.append(&string_and_length.string);
+ *remaining_cols -= string_and_length.length;
+ break;
+ }
+ }
+ }
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct StringAndLength {
+ pub string: String,
+ pub length: usize,
+}
+
+impl StringAndLength {
+ pub fn new(string: String, length: usize) -> Self {
+ StringAndLength { string, length }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct LineToRender {
+ line: String,
+ is_selected: bool,
+ truncated_result_count: usize,
+ colors: Colors,
+}
+
+impl LineToRender {
+ pub fn new(colors: Colors) -> Self {
+ LineToRender {
+ line: String::default(),
+ is_selected: false,
+ truncated_result_count: 0,
+ colors,
+ }
+ }
+ pub fn append(&mut self, to_append: &str) {
+ self.line.push_str(to_append)
+ }
+ pub fn make_selected(&mut self) {
+ self.is_selected = true;
+ match self.colors.palette.gray {
+ PaletteColor::EightBit(byte) => {
+ self.line = format!(
+ "\u{1b}[48;5;{byte}m\u{1b}[K\r\u{1b}[48;5;{byte}m{}",
+ self.line
+ );
+ },
+ PaletteColor::Rgb((r, g, b)) => {
+ self.line = format!(
+ "\u{1b}[48;2;{};{};{}m\u{1b}[K\r\u{1b}[48;5;{};{};{}m{}",
+ r, g, b, r, g, b, self.line
+ );
+ },
+ }
+ }
+ pub fn render(&self) -> String {
+ let mut line = self.line.clone();
+
+ let more = if self.truncated_result_count > 0 {
+ self.colors
+ .red(&format!(" [+{}]", self.truncated_result_count))
+ } else {
+ String::new()
+ };
+
+ line.push_str(&more);
+ if self.is_selected {
+ self.line.clone()
+ } else {
+ format!("\u{1b}[49m{}", line)
+ }
+ }
+ pub fn add_truncated_results(&mut self, result_count: usize) {
+ self.truncated_result_count += result_count;
+ }
+}
+
+pub fn build_session_ui_line(session_ui_info: &SessionUiInfo, colors: Colors) -> Vec<UiSpan> {
+ let mut ui_spans = vec![];
+ let tab_count_text = session_ui_info.tabs.len();
+ let total_pane_count_text = session_ui_info
+ .tabs
+ .iter()
+ .fold(0, |acc, tab| acc + tab.panes.len());
+ let tab_count = format!("{}", tab_count_text);
+ let tab_count_styled = colors.cyan(&tab_count);
+ let total_pane_count = format!("{}", total_pane_count_text);
+ let total_pane_count_styled = colors.green(&total_pane_count);
+ let session_name = &session_ui_info.name;
+ let connected_users = format!("{}", session_ui_info.connected_users);
+ let connected_users_styled = colors.orange(&connected_users);
+ let session_bullet_span =
+ UiSpan::UiSpanTelescope(UiSpanTelescope::new(vec![StringAndLength::new(
+ format!(" > "),
+ 3,
+ )]));
+ let session_name_span = UiSpan::TruncatableUiSpan(TruncatableUiSpan::new(
+ session_name.clone(),
+ SpanStyle::ForegroundBold(colors.palette.orange),
+ ));
+ let tab_and_pane_count = UiSpan::UiSpanTelescope(UiSpanTelescope::new(vec![
+ StringAndLength::new(
+ format!(" ({tab_count_styled} tabs, {total_pane_count_styled} panes)"),
+ 2 + tab_count.width() + 7 + total_pane_count.width() + 7,
+ ),
+ StringAndLength::new(
+ format!(" ({tab_count_styled}, {total_pane_count_styled})"),
+ 2 + tab_count.width() + 2 + total_pane_count.width() + 3,
+ ),
+ ]));
+ let connected_users_count = UiSpan::UiSpanTelescope(UiSpanTelescope::new(vec![
+ StringAndLength::new(
+ format!(" [{connected_users_styled} connected users]"),
+ 2 + connected_users.width() + 17,
+ ),
+ StringAndLength::new(
+ format!(" [{connected_users_styled}]"),
+ 2 + connected_users.width() + 1,
+ ),
+ ]));
+ ui_spans.push(session_bullet_span);
+ ui_spans.push(session_name_span);
+ ui_spans.push(tab_and_pane_count);
+ ui_spans.push(connected_users_count);
+ if session_ui_info.is_current_session {
+ let current_session_indication = UiSpan::UiSpanTelescope(UiSpanTelescope::new(vec![
+ StringAndLength::new(colors.orange(&format!(" <CURRENT SESSION>")), 18),
+ StringAndLength::new(colors.orange(&format!(" <CURRENT>")), 10),
+ StringAndLength::new(colors.orange(&format!(" <C>")), 4),
+ ]));
+ ui_spans.push(current_session_indication);
+ }
+ ui_spans
+}
+
+pub fn build_tab_ui_line(tab_ui_info: &TabUiInfo, colors: Colors) -> Vec<UiSpan> {
+ let mut ui_spans = vec![];
+ let tab_name = &tab_ui_info.name;
+ let pane_count_text = tab_ui_info.panes.len();
+ let pane_count = format!("{}", pane_count_text);
+ let pane_count_styled = colors.green(&pane_count);
+ let tab_bullet_span =
+ UiSpan::UiSpanTelescope(UiSpanTelescope::new(vec![StringAndLength::new(
+ format!(" - "),
+ 4,
+ )]));
+ let tab_name_span = UiSpan::TruncatableUiSpan(TruncatableUiSpan::new(
+ tab_name.clone(),
+ SpanStyle::ForegroundBold(colors.palette.cyan),
+ ));
+ let connected_users_count_span = UiSpan::UiSpanTelescope(UiSpanTelescope::new(vec![
+ StringAndLength::new(
+ format!(" ({pane_count_styled} panes)"),
+ 2 + pane_count.width() + 7,
+ ),
+ StringAndLength::new(
+ format!(" ({pane_count_styled})"),
+ 2 + pane_count.width() + 1,
+ ),
+ ]));
+ ui_spans.push(tab_bullet_span);
+ ui_spans.push(tab_name_span);
+ ui_spans.push(connected_users_count_span);
+ ui_spans
+}
+
+pub fn build_pane_ui_line(pane_ui_info: &PaneUiInfo, colors: Colors) -> Vec<UiSpan> {
+ let mut ui_spans = vec![];
+ let pane_name = pane_ui_info.name.clone();
+ let exit_code = pane_ui_info.exit_code.map(|exit_code_number| {
+ let exit_code = format!("{}", exit_code_number);
+ let exit_code = if exit_code_number == 0 {
+ colors.green(&exit_code)
+ } else {
+ colors.red(&exit_code)
+ };
+ exit_code
+ });
+ let pane_bullet_span =
+ UiSpan::UiSpanTelescope(UiSpanTelescope::new(vec![StringAndLength::new(
+ format!(" > "),
+ 6,
+ )]));
+ ui_spans.push(pane_bullet_span);
+ let pane_name_span =
+ UiSpan::TruncatableUiSpan(TruncatableUiSpan::new(pane_name, SpanStyle::Bold));
+ ui_spans.push(pane_name_span);
+ if let Some(exit_code) = exit_code {
+ let pane_name_span = UiSpan::UiSpanTelescope(UiSpanTelescope::new(vec![
+ StringAndLength::new(
+ format!(" (EXIT CODE: {exit_code})"),
+ 13 + exit_code.width() + 1,
+ ),
+ StringAndLength::new(format!(" ({exit_code})"), 2 + exit_code.width() + 1),
+ ]));
+ ui_spans.push(pane_name_span);
+ }
+ ui_spans
+}
+
+pub fn minimize_lines(
+ total_count: usize,
+ line_count_to_remove: usize,
+ selected_index: Option<usize>,
+) -> (usize, usize, usize, usize) {
+ // returns: (start_index, anchor_index, end_index, lines_left_to_remove)
+ let (count_to_render, line_count_to_remove) = if line_count_to_remove > total_count {
+ (1, line_count_to_remove.saturating_sub(total_count) + 1)
+ } else {
+ (total_count.saturating_sub(line_count_to_remove), 0)
+ };
+ let anchor_index = selected_index.unwrap_or(0); // 5
+ let mut start_index = anchor_index.saturating_sub(count_to_render / 2);
+ let mut end_index = start_index + count_to_render;
+ if end_index > total_count {
+ start_index = start_index.saturating_sub(end_index - total_count);
+ end_index = total_count;
+ }
+ (start_index, anchor_index, end_index, line_count_to_remove)
+}
+
+pub fn render_prompt(typing_session_name: bool, search_term: &str, colors: Colors) {
+ if !typing_session_name {
+ let prompt = colors.bold(&format!("> {}_", search_term));
+ println!("{}\n", prompt);
+ } else {
+ println!("\n");
+ }
+}
+
+pub fn render_new_session_line(session_name: &Option<String>, is_searching: bool, colors: Colors) {
+ if is_searching {
+ return;
+ }
+ let new_session_shortcut_text = "<Ctrl w>";
+ let new_session_shortcut = colors.magenta(new_session_shortcut_text);
+ let new_session = colors.bold("New session");
+ let enter = colors.magenta("<ENTER>");
+ match session_name {
+ Some(session_name) => {
+ println!(
+ "\u{1b}[m > {}_ ({}, {} when done)",
+ colors.orange(session_name),
+ colors.bold("Type optional name"),
+ enter
+ );
+ },
+ None => {
+ println!("\u{1b}[m > {new_session_shortcut} - {new_session}");
+ },
+ }
+}
+
+pub fn render_controls_line(is_searching: bool, row: usize, max_cols: usize, colors: Colors) {
+ let (arrows, navigate) = if is_searching {
+ (colors.magenta("<↓↑>"), colors.bold("Navigate"))
+ } else {
+ (colors.magenta("<←↓↑→>"), colors.bold("Navigate and Expand"))
+ };
+ let enter = colors.magenta("<ENTER>");
+ let select = colors.bold("Switch to selected");
+ let esc = colors.magenta("<ESC>");
+ let to_hide = colors.bold("Hide");
+
+ if max_cols >= 80 {
+ print!(
+ "\u{1b}[m\u{1b}[{row}HHelp: {arrows} - {navigate}, {enter} - {select}, {esc} - {to_hide}"
+ );
+ } else if max_cols >= 57 {
+ let navigate = colors.bold("Navigate");
+ let select = colors.bold("Switch");
+ print!(
+ "\u{1b}[m\u{1b}[{row}HHelp: {arrows} - {navigate}, {enter} - {select}, {esc} - {to_hide}"
+ );
+ } else if max_cols >= 20 {
+ print!("\u{1b}[m\u{1b}[{row}H{arrows}/{enter}/{esc}");
+ }
+}
+
+#[derive(Debug, Default, Clone, Copy)]
+pub struct Colors {
+ pub palette: Palette,
+}
+impl Colors {
+ pub fn new(palette: Palette) -> Self {
+ Colors { palette }
+ }
+ pub fn bold(&self, text: &str) -> String {
+ format!("\u{1b}[1m{}\u{1b}[22m", text)
+ }
+
+ fn color(&self, color: &PaletteColor, text: &str) -> String {
+ match color {
+ PaletteColor::EightBit(byte) => {
+ format!("\u{1b}[38;5;{};1m{}\u{1b}[39;22m", byte, text)
+ },
+ PaletteColor::Rgb((r, g, b)) => {
+ format!("\u{1b}[38;2;{};{};{};1m{}\u{1b}[39;22m", r, g, b, text)
+ },
+ }
+ }
+ pub fn orange(&self, text: &str) -> String {
+ self.color(&self.palette.orange, text)
+ }
+
+ pub fn green(&self, text: &str) -> String {
+ self.color(&self.palette.green, text)
+ }
+
+ pub fn red(&self, text: &str) -> String {
+ self.color(&self.palette.red, text)
+ }
+
+ pub fn cyan(&self, text: &str) -> String {
+ self.color(&self.palette.cyan, text)
+ }
+
+ pub fn magenta(&self, text: &str) -> String {
+ self.color(&self.palette.magenta, text)
+ }
+}
diff --git a/default-plugins/session-manager/src/ui/mod.rs b/default-plugins/session-manager/src/ui/mod.rs
new file mode 100644
index 00000000..94a1a8a7
--- /dev/null
+++ b/default-plugins/session-manager/src/ui/mod.rs
@@ -0,0 +1,357 @@
+pub mod components;
+use zellij_tile::prelude::*;
+
+use crate::session_list::{SelectedIndex, SessionList};
+use components::{
+ build_pane_ui_line, build_session_ui_line, build_tab_ui_line, minimize_lines, Colors,
+ LineToRender,
+};
+
+macro_rules! render_assets {
+ ($assets:expr, $line_count_to_remove:expr, $selected_index:expr, $to_render_until_selected: expr, $to_render_after_selected:expr, $has_deeper_selected_assets:expr, $max_cols:expr, $colors:expr) => {{
+ let (start_index, anchor_asset_index, end_index, line_count_to_remove) =
+ minimize_lines($assets.len(), $line_count_to_remove, $selected_index);
+ let mut truncated_result_count_above = start_index;
+ let mut truncated_result_count_below = $assets.len().saturating_sub(end_index);
+ let mut current_index = 1;
+ if let Some(assets_to_render_before_selected) = $assets.get(start_index..anchor_asset_index)
+ {
+ for asset in assets_to_render_before_selected {
+ let mut asset: LineToRender =
+ asset.as_line_to_render(current_index, $max_cols, $colors);
+ asset.add_truncated_results(truncated_result_count_above);
+ truncated_result_count_above = 0;
+ current_index += 1;
+ $to_render_until_selected.push(asset);
+ }
+ }
+ if let Some(selected_asset) = $assets.get(anchor_asset_index) {
+ if $selected_index.is_some() && !$has_deeper_selected_assets {
+ let mut selected_asset: LineToRender =
+ selected_asset.as_line_to_render(current_index, $max_cols, $colors);
+ selected_asset.make_selected();
+ selected_asset.add_truncated_results(truncated_result_count_above);
+ if anchor_asset_index + 1 >= end_index {
+ // no more results below, let's add the more indication if we need to
+ selected_asset.add_truncated_results(truncated_result_count_below);
+ }
+ current_index += 1;
+ $to_render_until_selected.push(selected_asset);
+ } else {
+ $to_render_until_selected.push(selected_asset.as_line_to_render(
+ current_index,
+ $max_cols,
+ $colors,
+ ));
+ current_index += 1;
+ }
+ }
+ if let Some(assets_to_render_after_selected) =
+ $assets.get(anchor_asset_index + 1..end_index)
+ {
+ for asset in assets_to_render_after_selected.iter().rev() {
+ let mut asset: LineToRender =
+ asset.as_line_to_render(current_index, $max_cols, $colors);
+ asset.add_truncated_results(truncated_result_count_below);
+ truncated_result_count_below = 0;
+ current_index += 1;
+ $to_render_after_selected.insert(0, asset.into());
+ }
+ }
+ line_count_to_remove
+ }};
+}
+
+impl SessionList {
+ pub fn render(&self, max_rows: usize, max_cols: usize, colors: Colors) -> Vec<LineToRender> {
+ if self.is_searching {
+ self.render_search_results(max_rows, max_cols)
+ } else {
+ self.render_list(max_rows, max_cols, colors)
+ }
+ }
+ fn render_search_results(&self, max_rows: usize, max_cols: usize) -> Vec<LineToRender> {
+ let mut lines_to_render = vec![];
+ for (i, result) in self.search_results.iter().enumerate() {
+ if lines_to_render.len() + result.lines_to_render() <= max_rows {
+ let mut result_lines = result.render(max_cols);
+ if Some(i) == self.selected_search_index {
+ for line_to_render in result_lines.iter_mut() {
+ line_to_render.make_selected();
+ }
+ }
+ lines_to_render.append(&mut result_lines);
+ } else {
+ break;
+ }
+ }
+ lines_to_render
+ }
+ fn render_list(&self, max_rows: usize, max_cols: usize, colors: Colors) -> Vec<LineToRender> {
+ let mut lines_to_render_until_selected = vec![];
+ let mut lines_to_render_after_selected = vec![];
+ let total_lines_to_render = self.total_lines_to_render();
+ let line_count_to_remove = total_lines_to_render.saturating_sub(max_rows);
+ let line_count_to_remove = self.render_sessions(
+ &mut lines_to_render_until_selected,
+ &mut lines_to_render_after_selected,
+ line_count_to_remove,
+ max_cols,
+ colors,
+ );
+ let line_count_to_remove = self.render_tabs(
+ &mut lines_to_render_until_selected,
+ &mut lines_to_render_after_selected,
+ line_count_to_remove,
+ max_cols,
+ colors,
+ );
+ self.render_panes(
+ &mut lines_to_render_until_selected,
+ &mut lines_to_render_after_selected,
+ line_count_to_remove,
+ max_cols,
+ colors,
+ );
+ let mut lines_to_render = lines_to_render_until_selected;
+ lines_to_render.append(&mut lines_to_render_after_selected);
+ lines_to_render
+ }
+ fn render_sessions(
+ &self,
+ to_render_until_selected: &mut Vec<LineToRender>,
+ to_render_after_selected: &mut Vec<LineToRender>,
+ line_count_to_remove: usize,
+ max_cols: usize,
+ colors: Colors,
+ ) -> usize {
+ render_assets!(
+ self.session_ui_infos,
+ line_count_to_remove,
+ self.selected_index.0,
+ to_render_until_selected,
+ to_render_after_selected,
+ self.selected_index.1.is_some(),
+ max_cols,
+ colors
+ )
+ }
+ fn render_tabs(
+ &self,
+ to_render_until_selected: &mut Vec<LineToRender>,
+ to_render_after_selected: &mut Vec<LineToRender>,
+ line_count_to_remove: usize,
+ max_cols: usize,
+ colors: Colors,
+ ) -> usize {
+ if self.selected_index.1.is_none() {
+ return line_count_to_remove;
+ }
+ if let Some(tabs_in_session) = self
+ .selected_index
+ .0
+ .and_then(|i| self.session_ui_infos.get(i))
+ .map(|s| &s.tabs)
+ {
+ render_assets!(
+ tabs_in_session,
+ line_count_to_remove,
+ self.selected_index.1,
+ to_render_until_selected,
+ to_render_after_selected,
+ self.selected_index.2.is_some(),
+ max_cols,
+ colors
+ )
+ } else {
+ line_count_to_remove
+ }
+ }
+ fn render_panes(
+ &self,
+ to_render_until_selected: &mut Vec<LineToRender>,
+ to_render_after_selected: &mut Vec<LineToRender>,
+ line_count_to_remove: usize,
+ max_cols: usize,
+ colors: Colors,
+ ) -> usize {
+ if self.selected_index.2.is_none() {
+ return line_count_to_remove;
+ }
+ if let Some(panes_in_session) = self
+ .selected_index
+ .0
+ .and_then(|i| self.session_ui_infos.get(i))
+ .map(|s| &s.tabs)
+ .and_then(|tabs| {
+ self.selected_index
+ .1
+ .and_then(|i| tabs.get(i))
+ .map(|t| &t.panes)
+ })
+ {
+ render_assets!(
+ panes_in_session,
+ line_count_to_remove,
+ self.selected_index.2,
+ to_render_until_selected,
+ to_render_after_selected,
+ false,
+ max_cols,
+ colors
+ )
+ } else {
+ line_count_to_remove
+ }
+ }
+ fn total_lines_to_render(&self) -> usize {
+ self.session_ui_infos
+ .iter()
+ .enumerate()
+ .fold(0, |acc, (index, s)| {
+ if self.selected_index.session_index_is_selected(index) {
+ acc + s.line_count(&self.selected_index)
+ } else {
+ acc + 1
+ }
+ })
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct SessionUiInfo {
+ pub name: String,
+ pub tabs: Vec<TabUiInfo>,
+ pub connected_users: usize,
+ pub is_current_session: bool,
+}
+
+impl SessionUiInfo {
+ pub fn from_session_info(session_info: &SessionInfo) -> Self {
+ SessionUiInfo {
+ name: session_info.name.clone(),
+ tabs: session_info
+ .tabs
+ .iter()
+ .map(|t| TabUiInfo::new(t, &session_info.panes))
+ .collect(),
+ connected_users: session_info.connected_clients,
+ is_current_session: session_info.is_current_session,
+ }
+ }
+ pub fn line_count(&self, selected_index: &SelectedIndex) -> usize {
+ let mut line_count = 1; // self
+ if selected_index.tabs_are_visible() {
+ match selected_index
+ .selected_tab_index()
+ .and_then(|i| self.tabs.get(i))
+ .map(|t| t.line_count(&selected_index))
+ {
+ Some(line_count_of_selected_tab) => {
+ // we add the line count in the selected tab minus 1 because we will account
+ // for the selected tab line itself in self.tabs.len() below
+ line_count += line_count_of_selected_tab.saturating_sub(1);
+ line_count += self.tabs.len();
+ },
+ None => {
+ line_count += self.tabs.len();
+ },
+ }
+ }
+ line_count
+ }
+ fn as_line_to_render(
+ &self,
+ _session_index: u8,
+ mut max_cols: usize,
+ colors: Colors,
+ ) -> LineToRender {
+ let mut line_to_render = LineToRender::new(colors);
+ let ui_spans = build_session_ui_line(&self, colors);
+ for span in ui_spans {
+ span.render(None, &mut line_to_render, &mut max_cols);
+ }
+ line_to_render
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct TabUiInfo {
+ pub name: String,
+ pub panes: Vec<PaneUiInfo>,
+ pub position: usize,
+}
+
+impl TabUiInfo {
+ pub fn new(tab_info: &TabInfo, pane_manifest: &PaneManifest) -> Self {
+ let panes = pane_manifest
+ .panes
+ .get(&tab_info.position)
+ .map(|p| {
+ p.iter()
+ .filter_map(|pane_info| {
+ if pane_info.is_selectable {
+ Some(PaneUiInfo {
+ name: pane_info.title.clone(),
+ exit_code: pane_info.exit_status.clone(),
+ pane_id: pane_info.id,
+ is_plugin: pane_info.is_plugin,
+ })
+ } else {
+ None
+ }
+ })
+ .collect()
+ })
+ .unwrap_or_default();
+ TabUiInfo {
+ name: tab_info.name.clone(),
+ panes,
+ position: tab_info.position,
+ }
+ }
+ pub fn line_count(&self, selected_index: &SelectedIndex) -> usize {
+ let mut line_count = 1; // self
+ if selected_index.panes_are_visible() {
+ line_count += self.panes.len()
+ }
+ line_count
+ }
+ fn as_line_to_render(
+ &self,
+ _session_index: u8,
+ mut max_cols: usize,
+ colors: Colors,
+ ) -> LineToRender {
+ let mut line_to_render = LineToRender::new(colors);
+ let ui_spans = build_tab_ui_line(&self, colors);
+ for span in ui_spans {
+ span.render(None, &mut line_to_render, &mut max_cols);
+ }
+ line_to_render
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct PaneUiInfo {
+ pub name: String,
+ pub exit_code: Option<i32>,
+ pub pane_id: u32,
+ pub is_plugin: bool,
+}
+
+impl PaneUiInfo {
+ fn as_line_to_render(
+ &self,
+ _session_index: u8,
+ mut max_cols: usize,
+ colors: Colors,
+ ) -> LineToRender {
+ let mut line_to_render = LineToRender::new(colors);
+ let ui_spans = build_pane_ui_line(&self, colors);
+ for span in ui_spans {
+ span.render(None, &mut line_to_render, &mut max_cols);
+ }
+ line_to_render
+ }
+}
diff --git a/src/commands.rs b/src/commands.rs
index 6e5608e1..5ab188d6 100644
--- a/src/commands.rs
+++ b/src/commands.rs
@@ -16,6 +16,7 @@ use zellij_client::{
use zellij_server::{os_input_output::get_server_os_input, start_server as start_server_impl};
use zellij_utils::{
cli::{CliArgs, Command, SessionCommand, Sessions},
+ data::ConnectToSession,
envs,
input::{
actions::Action,
@@ -331,131 +332,180 @@ pub(crate) fn start_client(opts: CliArgs) {
process::exit(1);
},
};
+ let mut reconnect_to_session: Option<ConnectToSession> = None;
let os_input = get_os_input(get_client_os_input);
+ loop {
+ let os_input = os_input.clone();
+ let config = config.clone();
+ let layout = layout.clone();
+ let mut config_options = config_options.clone();
+ let mut opts = opts.clone();
- let start_client_plan = |session_name: std::string::String| {
- assert_session_ne(&session_name);
- };
+ if let Some(reconnect_to_session) = &reconnect_to_session {
+ // this is integration code to make session reconnects work with this existing,
+ // untested and pretty involved function
+ //
+ // ideally, we should write tests for this whole function and refctor it
+ if reconnect_to_session.name.is_some() {
+ opts.command = Some(Command::Sessions(Sessions::Attach {
+ session_name: reconnect_to_session.name.clone(),
+ create: true,
+ index: None,
+ options: None,
+ }));
+ } else {
+ opts.command = None;
+ opts.session = None;
+ config_options.attach_to_session = None;
+ }
+ }
- if let Some(Command::Sessions(Sessions::Attach {
- session_name,
- create,
- index,
- options,
- })) = opts.command.clone()
- {
- let config_options = match options.as_deref() {
- Some(SessionCommand::Options(o)) => config_options.merge_from_cli(o.to_owned().into()),
- None => config_options,
+ let start_client_plan = |session_name: std::string::String| {
+ assert_session_ne(&session_name);
};
- let client = if let Some(idx) = index {
- attach_with_session_index(config_options.clone(), idx, create)
- } else {
- let session_exists = session_name
- .as_ref()
- .and_then(|s| session_exists(&s).ok())
- .unwrap_or(false);
- if create && !session_exists {
- session_name.clone().map(start_client_plan);
- }
- attach_with_session_name(session_name, config_options.clone(), create)
- };
+ if let Some(Command::Sessions(Sessions::Attach {
+ session_name,
+ create,
+ index,
+ options,
+ })) = opts.command.clone()
+ {
+ let config_options = match options.as_deref() {
+ Some(SessionCommand::Options(o)) => {
+ config_options.merge_from_cli(o.to_owned().into())
+ },
+ None => config_options,
+ };
- if let Ok(val) = std::env::var(envs::SESSION_NAME_ENV_KEY) {
- if val == *client.get_session_name() {
- eprintln!("You are trying to attach to the current session (\"{}\"). Zellij does not support nesting a session in itself.", val);
- process::exit(1);
+ let client = if let Some(idx) = index {
+ attach_with_session_index(config_options.clone(), idx, create)
+ } else {
+ let session_exists = session_name
+ .as_ref()
+ .and_then(|s| session_exists(&s).ok())
+ .unwrap_or(false);
+ if create && !session_exists {
+ session_name.clone().map(start_client_plan);
+ }
+ attach_with_session_name(session_name, config_options.clone(), create)
+ };
+
+ if let Ok(val) = std::env::var(envs::SESSION_NAME_ENV_KEY) {
+ if val == *client.get_session_name() {
+ panic!("You are trying to attach to the current session (\"{}\"). This is not supported.", val);
+ }
}
- }
- let attach_layout = match client {
- ClientInfo::Attach(_, _) => None,
- ClientInfo::New(_) => Some(layout),
- };
+ let attach_layout = match client {
+ ClientInfo::Attach(_, _) => None,
+ ClientInfo::New(_) => Some(layout),
+ };
- start_client_impl(
- Box::new(os_input),
- opts,
- config,
- config_options,
- client,
- attach_layout,
- );
- } else {
- if let Some(session_name) = opts.session.clone() {
- start_client_plan(session_name.clone());
- start_client_impl(
+ let tab_position_to_focus = reconnect_to_session
+ .as_ref()
+ .and_then(|r| r.tab_position.clone());
+ let pane_id_to_focus = reconnect_to_session
+ .as_ref()
+ .and_then(|r| r.pane_id.clone());
+ reconnect_to_session = start_client_impl(
Box::new(os_input),
opts,
config,
config_options,
- ClientInfo::New(session_name),
- Some(layout),
+ client,
+ attach_layout,
+ tab_position_to_focus,
+ pane_id_to_focus,
);
} else {
- if let Some(session_name) = config_options.session_name.as_ref() {
- if let Ok(val) = envs::get_session_name() {
- // This prevents the same type of recursion as above, only that here we
- // don't get the command to "attach", but to start a new session instead.
- // This occurs for example when declaring the session name inside a layout
- // file and then, from within this session, trying to open a new zellij
- // session with the same layout. This causes an infinite recursion in the
- // `zellij_server::terminal_bytes::listen` task, flooding the server and
- // clients with infinite `Render` requests.
- if *session_name == val {
- eprintln!("You are trying to attach to the current session (\"{}\"). Zellij does not support nesting a session in itself.", session_name);
- process::exit(1);
+ if let Some(session_name) = opts.session.clone() {
+ start_client_plan(session_name.clone());
+ reconnect_to_session = start_client_impl(
+ Box::new(os_input),
+ opts,
+ config,
+ config_options,
+ ClientInfo::New(session_name),
+ Some(layout),
+ None,
+ None,
+ );
+ } else {
+ if let Some(session_name) = config_options.session_name.as_ref() {
+ if let Ok(val) = envs::get_session_name() {
+ // This prevents the same type of recursion as above, only that here we
+ // don't get the command to "attach", but to start a new session instead.
+ // This occurs for example when declaring the session name inside a layout
+ // file and then, from within this session, trying to open a new zellij
+ // session with the same layout. This causes an infinite recursion in the
+ // `zellij_server::terminal_bytes::listen` task, flooding the server and
+ // clients with infinite `Render` requests.
+ if *session_name == val {
+ eprintln!("You are trying to attach to the current session (\"{}\"). Zellij does not support nesting a session in itself.", session_name);
+ process::exit(1);
+ }
}
+ match config_options.attach_to_session {
+ Some(true) => {
+ let client = attach_with_session_name(
+ Some(session_name.clone()),
+ config_options.clone(),
+ true,
+ );
+ let attach_layout = match client {
+ ClientInfo::Attach(_, _) => None,
+ ClientInfo::New(_) => Some(layout),
+ };
+ reconnect_to_session = start_client_impl(
+ Box::new(os_input),
+ opts,
+ config,
+ config_options,
+ client,
+ attach_layout,
+ None,
+ None,
+ );
+ },
+ _ => {
+ start_client_plan(session_name.clone());
+ reconnect_to_session = start_client_impl(
+ Box::new(os_input),
+ opts,
+ config,
+ config_options.clone(),
+ ClientInfo::New(session_name.clone()),
+ Some(layout),
+ None,
+ None,
+ );
+ },
+ }
+ if reconnect_to_session.is_some() {
+ continue;
+ }
+ // after we detach, this happens and so we need to exit before the rest of the
+ // function happens
+ process::exit(0);
}
- match config_options.attach_to_session {
- Some(true) => {
- let client = attach_with_session_name(
- Some(session_name.clone()),
- config_options.clone(),
- true,
- );
- let attach_layout = match client {
- ClientInfo::Attach(_, _) => None,
- ClientInfo::New(_) => Some(layout),
- };
- start_client_impl(
- Box::new(os_input),
- opts,
- config,
- config_options,
- client,
- attach_layout,
- );
- },
- _ => {
- start_client_plan(session_name.clone());
- start_client_impl(
- Box::new(os_input),
- opts,
- config,
- config_options.clone(),
- ClientInfo::New(session_name.clone()),
- Some(layout),
- );
- },
- }
- // after we detach, this happens and so we need to exit before the rest of the
- // function happens
- // TODO: offload this to a different function
- process::exit(0);
- }
- let session_name = generate_unique_session_name();
- start_client_plan(session_name.clone());
- start_client_impl(
- Box::new(os_input),
- opts,
- config,
- config_options,
- ClientInfo::New(session_name),
- Some(layout),
- );
+ let session_name = generate_unique_session_name();
+ start_client_plan(session_name.clone());
+ reconnect_to_session = start_client_impl(
+ Box::new(os_input),
+ opts,
+ config,
+ config_options,
+ ClientInfo::New(session_name),
+ Some(layout),
+ None,
+ None,
+ );
+ }
+ }
+ if reconnect_to_session.is_none() {
+ break;
}
}
}
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index c6e75118..1f76fb4e 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -35,6 +35,7 @@ lazy_static::lazy_static! {
WorkspaceMember{crate_name: "default-plugins/strider", build: true},
WorkspaceMember{crate_name: "default-plugins/tab-bar", build: true},
WorkspaceMember{crate_name: "default-plugins/fixture-plugin-for-tests", build: true},
+ WorkspaceMember{crate_name: "default-plugins/session-manager", build: true},
WorkspaceMember{crate_name: "zellij-utils", build: false},
WorkspaceMember{crate_name: "zellij-tile-utils", build: false},
WorkspaceMember{crate_name: "zellij-tile", build: false},
diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs
index 3efe6a72..af977c79 100644
--- a/zellij-client/src/input_handler.rs
+++ b/zellij-client/src/input_handler.rs
@@ -157,6 +157,9 @@ impl InputHandler {
.send(ClientInstruction::DoneParsingStdinQuery)
.unwrap();
},
+ Ok((InputInstruction::Exit, _error_context)) => {
+ self.should_exit = true;
+ },
Err(err) => panic!("Encountered read error: {:?}", err),
}
}
diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs
index fb58db27..8dbae4f0 100644
--- a/zellij-client/src/lib.rs
+++ b/zellij-client/src/lib.rs
@@ -23,8 +23,8 @@ use crate::{
};
use zellij_utils::{
channels::{self, ChannelWithContext, SenderWithContext},
- consts::ZELLIJ_IPC_PIPE,
- data::{ClientId, InputMode, Style},
+ consts::{set_permissions, ZELLIJ_SOCK_DIR},
+ data::{ClientId, ConnectToSession, InputMode, Style},
envs,
errors::{ClientContext, ContextType, ErrorInstruction},
input::{config::Config, options::Options},
@@ -46,6 +46,7 @@ pub(crate) enum ClientInstruction {
StartedParsingStdinQuery,
DoneParsingStdinQuery,
Log(Vec<String>),
+ SwitchSession(ConnectToSession),
}
impl From<ServerToClientMsg> for ClientInstruction {
@@ -60,6 +61,9 @@ impl From<ServerToClientMsg> for ClientInstruction {
ServerToClientMsg::Connected => ClientInstruction::Connected,
ServerToClientMsg::ActiveClients(clients) => ClientInstruction::ActiveClients(clients),
ServerToClientMsg::Log(log_lines) => ClientInstruction::Log(log_lines),
+ ServerToClientMsg::SwitchSession(connect_to_session) => {
+ ClientInstruction::SwitchSession(connect_to_session)
+ },
}
}
}
@@ -77,6 +81,7 @@ impl From<&ClientInstruction> for ClientContext {
ClientInstruction::Log(_) => ClientContext::Log,
ClientInstruction::StartedParsingStdinQuery => ClientContext::StartedParsingStdinQuery,
ClientInstruction::DoneParsingStdinQuery => ClientContext::DoneParsingStdinQuery,
+ ClientInstruction::SwitchSession(..) => ClientContext::SwitchSession,
}
}
}
@@ -130,6 +135,7 @@ pub(crate) enum InputInstruction {
AnsiStdinInstructions(Vec<AnsiStdinInstruction>),
StartedParsing,
DoneParsing,
+ Exit,
}
pub fn start_client(
@@ -139,8 +145,12 @@ pub fn start_client(
config_options: Options,
info: ClientInfo,
layout: Option<Layout>,
-) {
+ tab_position_to_focus: Option<usize>,
+ pane_id_to_focus: Option<(u32, bool)>, // (pane_id, is_plugin)
+) -> Option<ConnectToSession> {
info!("Starting Zellij client!");
+
+ let mut reconnect_to_session = None;
let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l";
let take_snapshot = "\u{1b}[?1049h";
let bracketed_paste = "\u{1b}[?2004h";
@@ -172,28 +182,51 @@ pub fn start_client(
keybinds: config.keybinds.clone(),
};
- let first_msg = match info {
+ let create_ipc_pipe = || -> std::path::PathBuf {
+ let mut sock_dir = ZELLIJ_SOCK_DIR.clone();
+ std::fs::create_dir_all(&sock_dir).unwrap();
+ set_permissions(&sock_dir, 0o700).unwrap();
+ sock_dir.push(envs::get_session_name().unwrap());
+ sock_dir
+ };
+
+ let (first_msg, ipc_pipe) = match info {
ClientInfo::Attach(name, config_options) => {
- envs::set_session_name(name);
+ envs::set_session_name(name.clone());
+ os_input.update_session_name(name);
+ let ipc_pipe = create_ipc_pipe();
- ClientToServerMsg::AttachClient(client_attributes, config_options)
+ (
+ ClientToServerMsg::AttachClient(
+ client_attributes,
+ config_options,
+ tab_position_to_focus,
+ pane_id_to_focus,
+ ),
+ ipc_pipe,
+ )
},
ClientInfo::New(name) => {
- envs::set_session_name(name);
+ envs::set_session_name(name.clone());
+ os_input.update_session_name(name);
+ let ipc_pipe = create_ipc_pipe();
- spawn_server(&*ZELLIJ_IPC_PIPE, opts.debug).unwrap();
+ spawn_server(&*ipc_pipe, opts.debug).unwrap();
- ClientToServerMsg::NewClient(
- client_attributes,
- Box::new(opts),
- Box::new(config_options.clone()),
- Box::new(layout.unwrap()),
- Some(config.plugins.clone()),
+ (
+ ClientToServerMsg::NewClient(
+ client_attributes,
+ Box::new(opts),
+ Box::new(config_options.clone()),
+ Box::new(layout.unwrap()),
+ Some(config.plugins.clone()),
+ ),
+ ipc_pipe,
)
},
};
- os_input.connect_to_server(&*ZELLIJ_IPC_PIPE);
+ os_input.connect_to_server(&*ipc_pipe);
os_input.send_to_server(first_msg);
let mut command_is_executing = CommandIsExecuting::new();
@@ -336,7 +369,7 @@ pub fn start_client(
std::process::exit(1);
};
- let exit_msg: String;
+ let mut exit_msg = String::new();
let mut loading = true;
let mut pending_instructions = vec![];
@@ -415,28 +448,43 @@ pub fn start_client(
log::info!("{line}");
}
},
+ ClientInstruction::SwitchSession(connect_to_session) => {
+ reconnect_to_session = Some(connect_to_session);
+ os_input.send_to_server(ClientToServerMsg::ClientExited);
+ break;
+ },
_ => {},
}
}
router_thread.join().unwrap();
- // cleanup();
- let reset_style = "\u{1b}[m";
- let show_cursor = "\u{1b}[?25h";
- let restore_snapshot = "\u{1b}[?1049l";
- let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
- let goodbye_message = format!(
- "{}\n{}{}{}{}\n",
- goto_start_of_last_line, restore_snapshot, reset_style, show_cursor, exit_msg
- );
+ if reconnect_to_session.is_none() {
+ let reset_style = "\u{1b}[m";
+ let show_cursor = "\u{1b}[?25h";
+ let restore_snapshot = "\u{1b}[?1049l";
+ let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
+ let goodbye_message = format!(
+ "{}\n{}{}{}{}\n",
+ goto_start_of_last_line, restore_snapshot, reset_style, show_cursor, exit_msg
+ );
+
+ os_input.disable_mouse().non_fatal();
+ info!("{}", exit_msg);
+ os_input.unset_raw_mode(0).unwrap();
+ let mut stdout = os_input.get_stdout_writer();
+ let _ = stdout.write(goodbye_message.as_bytes()).unwrap();
+ stdout.flush().unwrap();
+ } else {
+ let clear_screen = "\u{1b}[2J";
+ let mut stdout = os_input.get_stdout_writer();
+ let _ = stdout.write(clear_screen.as_bytes()).unwrap();
+ stdout.flush().unwrap();
+ }
+
+ let _ = send_input_instructions.send(InputInstruction::Exit);
- os_input.disable_mouse().non_fatal();
- info!("{}", exit_msg);
- os_input.unset_raw_mode(0).unwrap();
- let mut stdout = os_input.get_stdout_writer();
- let _ = stdout.write(goodbye_message.as_bytes()).unwrap();
- stdout.flush().unwrap();
+ reconnect_to_session
}
#[cfg(test)]
diff --git a/zellij-client/src/os_input_output.rs b/zellij-client/src/os_input_output.rs
index 192c2159..212ed2f7 100644
--- a/zellij-client/src/os_input_output.rs
+++ b/zellij-client/src/os_input_output.rs
@@ -78,6 +78,8 @@ pub struct ClientOsInputOutput {
orig_termios: Option<Arc<Mutex<termios::Termios>>>,
send_instructions_to_server: Arc<Mutex<Option<IpcSenderWithContext<ClientToServerMsg>>>>,
receive_instructions_from_server: Arc<Mutex<Option<IpcReceiverWithContext<ServerToClientMsg>>>>,
+ reading_from_stdin: Arc<Mutex<Option<Vec<u8>>>>,
+ session_name: Arc<Mutex<Option<String>>>,
}
/// The `ClientOsApi` trait represents an abstract interface to the features of an operating system that
@@ -94,8 +96,9 @@ pub trait ClientOsApi: Send + Sync {
/// Returns the writer that allows writing to standard output.
fn get_stdout_writer(&self) -> Box<dyn io::Write>;
fn get_stdin_reader(&self) -> Box<dyn io::Read>;
+ fn update_session_name(&mut self, new_session_name: String);
/// Returns the raw contents of standard input.
- fn read_from_stdin(&mut self) -> Vec<u8>;
+ fn read_from_stdin(&mut self) -> Result<Vec<u8>, &'static str>;
/// Returns a [`Box`] pointer to this [`ClientOsApi`] struct.
fn box_clone(&self) -> Box<dyn ClientOsApi>;
/// Sends a message to the server.
@@ -135,14 +138,46 @@ impl ClientOsApi for ClientOsInputOutput {
fn box_clone(&self) -> Box<dyn ClientOsApi> {
Box::new((*self).clone())
}
- fn read_from_stdin(&mut self) -> Vec<u8> {
- let stdin = std::io::stdin();
- let mut stdin = stdin.lock();
- let buffer = stdin.fill_buf().unwrap();
- let length = buffer.len();
- let read_bytes = Vec::from(buffer);
- stdin.consume(length);
- read_bytes
+ fn update_session_name(&mut self, new_session_name: String) {
+ *self.session_name.lock().unwrap() = Some(new_session_name);
+ }
+ fn read_from_stdin(&mut self) -> Result<Vec<u8>, &'static str> {
+ let session_name_at_calltime = { self.session_name.lock().unwrap().clone() };
+ // here we wait for a lock in case another thread is holding stdin
+ // this can happen for example when switching sessions, the old thread will only be
+ // released once it sees input over STDIN
+ //
+ // when this happens, we detect in the other thread that our session is ended (by comparing
+ // the session name at the beginning of the call and the one after we read from STDIN), and
+ // so place what we read from STDIN inside a buffer (the "reading_from_stdin" on our state)
+ // and release the lock
+ //
+ // then, another thread will see there's something in the buffer immediately as it acquires
+ // the lock (without having to wait for STDIN itself) forward this buffer and proceed to
+ // wait for the "real" STDIN net time it is called
+ let mut buffered_bytes = self.reading_from_stdin.lock().unwrap();
+ match buffered_bytes.take() {
+ Some(buffered_bytes) => Ok(buffered_bytes),
+ None => {
+ let stdin = std::io::stdin();
+ let mut stdin = stdin.lock();
+ let buffer = stdin.fill_buf().unwrap();
+ let length = buffer.len();
+ let read_bytes = Vec::from(buffer);
+ stdin.consume(length);
+
+ let session_name_after_reading_from_stdin =
+ { self.session_name.lock().unwrap().clone() };
+ if session_name_at_calltime.is_some()
+ && session_name_at_calltime != session_name_after_reading_from_stdin
+ {
+ *buffered_bytes = Some(read_bytes);
+ Err("Session ended")
+ } else {
+ Ok(read_bytes)
+ }
+ },
+ }
}
fn get_stdout_writer(&self) -> Box<dyn io::Write> {
let stdout = ::std::io::stdout();
@@ -258,19 +293,25 @@ impl Clone for Box<dyn ClientOsApi> {
pub fn get_client_os_input() -> Result<ClientOsInputOutput, nix::Error> {
let current_termios = termios::tcgetattr(0)?;
let orig_termios = Some(Arc::new(Mutex::new(current_termios)));
+ let reading_from_stdin = Arc::new(Mutex::new(None));
Ok(ClientOsInputOutput {
orig_termios,
send_instructions_to_server: Arc::new(Mutex::new(None)),
receive_instructions_from_server: Arc::new(Mutex::new(None)),
+ reading_from_stdin,
+ session_name: Arc::new(Mutex::new(None)),
})
}
pub fn get_cli_client_os_input() -> Result<ClientOsInputOutput, nix::Error> {
let orig_termios = None; // not a terminal
+ let reading_from_stdin = Arc::new(Mutex::new(None));
Ok(ClientOsInputOutput {
orig_termios,
send_instructions_to_server: Arc::new(Mutex::new(None)),
receive_instructions_from_server: Arc::new(Mutex::new(None)),
+ reading_from_stdin,
+ session_name: Arc::new(Mutex::new(None)),
})
}
diff --git a/zellij-client/src/stdin_handler.rs b/zellij-client/src/stdin_handler.rs
index 01f1ab23..eae0958b 100644
--- a/zellij-client/src/stdin_handler.rs
+++ b/zellij-client/src/stdin_handler.rs
@@ -59,66 +59,79 @@ pub(crate) fn stdin_loop(
}
let mut ansi_stdin_events = vec![];
loop {
- let buf = os_input.read_from_stdin();
- {
- // here we check if we need to parse specialized ANSI instructions sent over STDIN
- // this happens either on startup (see above) or on SIGWINCH
- //
- // if we need to parse them, we do so with an internal timeout - anything else we
- // receive on STDIN during that timeout is unceremoniously dropped
- let mut stdin_ansi_parser = stdin_ansi_parser.lock().unwrap();
- if stdin_ansi_parser.should_parse() {
- let events = stdin_ansi_parser.parse(buf);
- if !events.is_empty() {
- ansi_stdin_events.append(&mut events.clone());
- let _ = send_input_instructions
- .send(InputInstruction::AnsiStdinInstructions(events));
+ match os_input.read_from_stdin() {
+ Ok(buf) => {
+ {
+ // here we check if we need to parse specialized ANSI instructions sent over STDIN
+ // this happens either on startup (see above) or on SIGWINCH
+ //
+ // if we need to parse them, we do so with an internal timeout - anything else we
+ // receive on STDIN during that timeout is unceremoniously dropped
+ let mut stdin_ansi_parser = stdin_ansi_parser.lock().unwrap();
+ if stdin_ansi_parser.should_parse() {
+ let events = stdin_ansi_parser.parse(buf);
+ if !events.is_empty() {
+ ansi_stdin_events.append(&mut events.clone());
+ let _ = send_input_instructions
+ .send(InputInstruction::AnsiStdinInstructions(events));
+ }
+ continue;
+ }
}
- continue;
- }
- }
- if !ansi_stdin_events.is_empty() {
- stdin_ansi_parser
- .lock()
- .unwrap()
- .write_cache(ansi_stdin_events.drain(..).collect());
- }
- current_buffer.append(&mut buf.to_vec());
- let maybe_more = false; // read_from_stdin should (hopefully) always empty the STDIN buffer completely
- let mut events = vec![];
- input_parser.parse(
- &buf,
- |input_event: InputEvent| {
- events.push(input_event);
- },
- maybe_more,
- );
+ if !ansi_stdin_events.is_empty() {
+ stdin_ansi_parser
+ .lock()
+ .unwrap()
+ .write_cache(ansi_stdin_events.drain(..).collect());
+ }
+ current_buffer.append(&mut buf.to_vec());
+ let maybe_more = false; // read_from_stdin should (hopefully) always empty the STDIN buffer completely
+ let mut events = vec![];
+ input_parser.parse(
+ &buf,
+ |input_event: InputEvent| {
+ events.push(input_event);
+ },
+ maybe_more,
+ );
- let event_count = events.len();
- for (i, input_event) in events.into_iter().enumerate() {
- if holding_mouse && is_mouse_press_or_hold(&input_event) && i == event_count - 1 {
- let mut poller = os_input.stdin_poller();
- loop {
- if poller.ready() {
- break;
+ let event_count = events.len();
+ for (i, input_event) in events.into_iter().enumerate() {
+ if holding_mouse && is_mouse_press_or_hold(&input_event) && i == event_count - 1
+ {
+ let mut poller = os_input.stdin_poller();
+ loop {
+ if poller.ready() {
+ break;
+ }
+ send_input_instructions
+ .send(InputInstruction::KeyEvent(
+ input_event.clone(),
+ current_buffer.clone(),
+ ))
+ .unwrap();
+ }
}
+
+ holding_mouse = is_mouse_press_or_hold(&input_event);
+
send_input_instructions
.send(InputInstruction::KeyEvent(
- input_event.clone(),
- current_buffer.clone(),
+ input_event,
+ current_buffer.drain(..).collect(),
))
.unwrap();
}
- }
-
- holding_mouse = is_mouse_press_or_hold(&input_event);
-
- send_input_instructions
- .send(InputInstruction::KeyEvent(
- input_event,
- current_buffer.drain(..).collect(),
- ))
- .unwrap();
+ },
+ Err(e) => {
+ if e == "Session ended" {
+ log::debug!("Switched sessions, signing this thread off...");
+ } else {
+ log::error!("Failed to read from STDIN: {}", e);
+ }
+ let _ = send_input_instructions.send(InputInstruction::Exit);
+ break;
+ },
}
}
}
diff --git a/zellij-client/src/unit/stdin_tests.rs b/zellij-client/src/unit/stdin_tests.rs
index 6bdd489b..31c7fe07 100644
--- a/zellij-client/src/unit/stdin_tests.rs
+++ b/zellij-client/src/unit/stdin_tests.rs
@@ -154,8 +154,9 @@ impl ClientOsApi for FakeClientOsApi {
fn get_stdin_reader(&self) -> Box<dyn io::Read> {
unimplemented!()
}
- fn read_from_stdin(&mut self) -> Vec<u8> {
- self.stdin_buffer.drain(..).collect()
+ fn update_session_name(&mut self, new_session_name: String) {}
+ fn read_from_stdin(&mut self) -> Result<Vec<u8>, &'static str> {
+ Ok(self.stdin_buffer.drain(..).collect())
}
fn box_clone(&self) -> Box<dyn ClientOsApi> {
unimplemented!()
diff --git a/zellij-server/src/background_jobs.rs b/zellij-server/src/background_jobs.rs
index 8061b4a4..67bd62c1 100644
--- a/zellij-server/src/background_jobs.rs
+++ b/zellij-server/src/background_jobs.rs
@@ -1,10 +1,16 @@
use zellij_utils::async_std::task;
+use zellij_utils::consts::{ZELLIJ_SESSION_INFO_CACHE_DIR, ZELLIJ_SOCK_DIR};
+use zellij_utils::data::SessionInfo;
use zellij_utils::errors::{prelude::*, BackgroundJobContext, ContextType};
-use std::collections::HashMap;
+use std::collections::{BTreeMap, HashMap};
+use std::fs;
+use std::io::Write;
+use std::os::unix::fs::FileTypeExt;
+use std::path::PathBuf;
use std::sync::{
atomic::{AtomicBool, Ordering},
- Arc,
+ Arc, Mutex,
};
use std::time::{Duration, Instant};
@@ -15,8 +21,10 @@ use crate::thread_bus::Bus;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum BackgroundJob {
DisplayPaneError(Vec<PaneId>, String),
- AnimatePluginLoading(u32), // u32 - plugin_id
- StopPluginLoadingAnimation(u32), // u32 - plugin_id
+ AnimatePluginLoading(u32), // u32 - plugin_id
+ StopPluginLoadingAnimation(u32), // u32 - plugin_id
+ ReadAllSessionInfosOnMachine, // u32 - plugin_id
+ ReportSessionInfo(String, SessionInfo), // String - session name
Exit,
}
@@ -28,6 +36,10 @@ impl From<&BackgroundJob> for BackgroundJobContext {
BackgroundJob::StopPluginLoadingAnimation(..) => {
BackgroundJobContext::StopPluginLoadingAnimation
},
+ BackgroundJob::ReadAllSessionInfosOnMachine => {
+ BackgroundJobContext::ReadAllSessionInfosOnMachine
+ },
+ BackgroundJob::ReportSessionInfo(..) => BackgroundJobContext::ReportSessionInfo,
BackgroundJob::Exit => BackgroundJobContext::Exit,
}
}
@@ -35,11 +47,14 @@ impl From<&BackgroundJob> for BackgroundJobContext {
static FLASH_DURATION_MS: u64 = 1000;
static PLUGIN_ANIMATION_OFFSET_DURATION_MD: u64 = 500;
+static SESSION_READ_DURATION: u64 = 1000;
pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
let err_context = || "failed to write to pty".to_string();
let mut running_jobs: HashMap<BackgroundJob, Instant> = HashMap::new();
let mut loading_plugins: HashMap<u32, Arc<AtomicBool>> = HashMap::new(); // u32 - plugin_id
+ let current_session_name = Arc::new(Mutex::new(String::default()));
+ let current_session_info = Arc::new(Mutex::new(SessionInfo::default()));
loop {
let (event, mut err_ctx) = bus.recv().with_context(err_context)?;
@@ -93,10 +108,89 @@ pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
loading_plugin.store(false, Ordering::SeqCst);
}
},
+ BackgroundJob::ReportSessionInfo(session_name, session_info) => {
+ *current_session_name.lock().unwrap() = session_name;
+ *current_session_info.lock().unwrap() = session_info;
+ },
+ BackgroundJob::ReadAllSessionInfosOnMachine => {
+ // this job should only be run once and it keeps track of other sessions (as well
+ // as this one's) infos (metadata mostly) and sends it to the screen which in turn
+ // forwards it to plugins and other places it needs to be
+ if running_jobs.get(&job).is_some() {
+ continue;
+ }
+ running_jobs.insert(job, Instant::now());
+ task::spawn({
+ let senders = bus.senders.clone();
+ let current_session_info = current_session_info.clone();
+ let current_session_name = current_session_name.clone();
+ async move {
+ loop {
+ // write state of current session
+
+ // write it to disk
+ let current_session_name =
+ current_session_name.lock().unwrap().to_string();
+ let cache_file_name =
+ session_info_cache_file_name(&current_session_name);
+ let current_session_info = current_session_info.lock().unwrap().clone();
+ let _wrote_file =
+ std::fs::create_dir_all(ZELLIJ_SESSION_INFO_CACHE_DIR.as_path())
+ .and_then(|_| std::fs::File::create(cache_file_name))
+ .and_then(|mut f| {
+ write!(f, "{}", current_session_info.to_string())
+ });
+ // start a background job (if not already running) that'll periodically read this and other
+ // sesion infos and report back
+
+ // read state of all sessions
+ let mut other_session_names = vec![];
+ let mut session_infos_on_machine = BTreeMap::new();
+ // we do this so that the session infos will be actual and we're
+ // reasonably sure their session is running
+ if let Ok(files) = fs::read_dir(&*ZELLIJ_SOCK_DIR) {
+ files.for_each(|file| {
+ if let Ok(file) = file {
+ if let Ok(file_name) = file.file_name().into_string() {
+ if file.file_type().unwrap().is_socket() {
+ other_session_names.push(file_name);
+ }
+ }
+ }
+ });
+ }
+
+ for session_name in other_session_names {
+ let session_cache_file_name = ZELLIJ_SESSION_INFO_CACHE_DIR
+ .join(format!("{}.kdl", session_name));
+ if let Ok(raw_session_info) =
+ fs::read_to_string(&session_cache_file_name)
+ {
+ if let Ok(session_info) = SessionInfo::from_string(
+ &raw_session_info,
+ &current_session_name,
+ ) {
+ session_infos_on_machine.insert(session_name, session_info);
+ }
+ }
+ }
+ let _ = senders.send_to_screen(ScreenInstruction::UpdateSessionInfos(
+ session_infos_on_machine,
+ ));
+ task::sleep(std::time::Duration::from_millis(SESSION_READ_DURATION))
+ .await;
+ }
+ }
+ });
+ },
BackgroundJob::Exit => {
for loading_plugin in loading_plugins.values() {
loading_plugin.store(false, Ordering::SeqCst);
}
+
+ let cache_file_name =
+ session_info_cache_file_name(&current_session_name.lock().unwrap().to_owned());
+ let _ = std::fs::remove_file(cache_file_name);
return Ok(());
},
}
@@ -122,3 +216,7 @@ fn job_already_running(
},
}
}
+
+fn session_info_cache_file_name(session_name: &str) -> PathBuf {
+ ZELLIJ_SESSION_INFO_CACHE_DIR.join(format!("{}.kdl", &session_name))
+}
diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs
index 53a08f65..0b5f2ea8 100644
--- a/zellij-server/src/lib.rs
+++ b/zellij-server/src/lib.rs
@@ -41,7 +41,7 @@ use zellij_utils::{
channels::{self, ChannelWithContext, SenderWithContext},
cli::CliArgs,
consts::{DEFAULT_SCROLL_BUFFER_SIZE, SCROLL_BUFFER_SIZE},
- data::{Event, PluginCapabilities},
+ data::{ConnectToSession, Event, PluginCapabilities},
errors::{prelude::*, ContextType, ErrorInstruction, FatalError, ServerContext},
input::{
command::{RunCommand, TerminalAction},
@@ -74,10 +74,17 @@ pub enum ServerInstruction {
Error(String),
KillSession,
DetachSession(Vec<ClientId>),
- AttachClient(ClientAttributes, Options, ClientId),
+ AttachClient(
+ ClientAttributes,
+ Options,
+ Option<usize>, // tab position to focus
+ Option<(u32, bool)>, // (pane_id, is_plugin) => pane_id to focus
+ ClientId,
+ ),
ConnStatus(ClientId),
ActiveClients(ClientId),
Log(Vec<String>, ClientId),
+ SwitchSession(ConnectToSession, ClientId),
}
impl From<&ServerInstruction> for ServerContext {
@@ -95,6 +102,7 @@ impl From<&ServerInstruction> for ServerContext {
ServerInstruction::ConnStatus(..) => ServerContext::ConnStatus,
ServerInstruction::ActiveClients(_) => ServerContext::ActiveClients,
ServerInstruction::Log(..) => ServerContext::Log,
+ ServerInstruction::SwitchSession(..) => ServerContext::SwitchSession,
}
}
}
@@ -415,7 +423,13 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.send_to_plugin(PluginInstruction::AddClient(client_id))
.unwrap();
},
- ServerInstruction::AttachClient(attrs, options, client_id) => {
+ ServerInstruction::AttachClient(
+ attrs,
+ options,
+ tab_position_to_focus,
+ pane_id_to_focus,
+ client_id,
+ ) => {
let rlock = session_data.read().unwrap();
let session_data = rlock.as_ref().unwrap();
session_state
@@ -433,7 +447,11 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.unwrap();
session_data
.senders
- .send_to_screen(ScreenInstruction::AddClient(client_id))
+ .send_to_screen(ScreenInstruction::AddClient(
+ client_id,
+ tab_position_to_focus,
+ pane_id_to_focus,
+ ))
.unwrap();
session_data
.senders
@@ -635,6 +653,41 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
session_state
);
},
+ ServerInstruction::SwitchSession(connect_to_session, client_id) => {
+ if let Some(min_size) = session_state.read().unwrap().min_client_terminal_size() {
+ session_data
+ .write()
+ .unwrap()
+ .as_ref()
+ .unwrap()
+ .senders
+ .send_to_screen(ScreenInstruction::TerminalResize(min_size))
+ .unwrap();
+ }
+ 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();
+ send_to_client!(
+ client_id,
+ os_input,
+ ServerToClientMsg::SwitchSession(connect_to_session),
+ session_state
+ );
+ remove_client!(client_id, os_input, session_state);
+ },
}
}
@@ -664,13 +717,11 @@ fn init_session(
plugins,
} = options;
- SCROLL_BUFFER_SIZE
- .set(
- config_options
- .scroll_buffer_size
- .unwrap_or(DEFAULT_SCROLL_BUFFER_SIZE),
- )
- .unwrap();
+ let _ = SCROLL_BUFFER_SIZE.set(
+ config_options
+ .scroll_buffer_size
+ .unwrap_or(DEFAULT_SCROLL_BUFFER_SIZE),
+ );
let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = channels::unbounded();
let to_screen = SenderWithContext::new(to_screen);
diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs
index d63fac56..6cafad7d 100644
--- a/zellij-server/src/panes/grid.rs
+++ b/zellij-server/src/panes/grid.rs
@@ -1351,7 +1351,7 @@ impl Grid {
self.viewport.get(y).unwrap().absolute_character_index(x)
}
pub fn move_cursor_forward_until_edge(&mut self, count: usize) {
- let count_to_move = std::cmp::min(count, self.width - self.cursor.x);
+ let count_to_move = std::cmp::min(count, self.width.saturating_sub(self.cursor.x));
self.cursor.x += count_to_move;
}
pub fn replace_characters_in_line_after_cursor(&mut self, replace_with: TerminalCharacter) {
diff --git a/zellij-server/src/panes/tiled_panes/mod.rs b/zellij-server/src/panes/tiled_panes/mod.rs
index 736c16bc..e85911b4 100644
--- a/zellij-server/src/panes/tiled_panes/mod.rs
+++ b/zellij-server/src/panes/tiled_panes/mod.rs
@@ -523,6 +523,11 @@ impl TiledPanes {
}
}
pub fn focus_pane(&mut self, pane_id: PaneId, client_id: ClientId) {
+ if self.panes_to_hide.contains(&pane_id) {
+ // this means there is a fullscreen pane that is not the current pane, let's unset it
+ // before changing focus
+ self.unset_fullscreen();
+ }
if self
.panes
.get(&pane_id)
@@ -533,7 +538,6 @@ impl TiledPanes {
.expand_pane(&pane_id);
self.reapply_pane_frames();
}
-
self.active_panes
.insert(client_id, pane_id, &mut self.panes);
if self.session_is_mirrored {
diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs
index e995a07b..222b14c6 100644
--- a/zellij-server/src/plugins/unit/plugin_tests.rs
+++ b/zellij-server/src/plugins/unit/plugin_tests.rs
@@ -539,6 +539,7 @@ pub fn load_new_plugin_from_hd() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -609,6 +610,7 @@ pub fn plugin_workers() {
// we send a SystemClipboardFailure to trigger the custom handler in the fixture plugin that
// will send a message to the worker and in turn back to the plugin to be rendered, so we know
// that this cycle is working
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -682,6 +684,7 @@ pub fn plugin_workers_persist_state() {
// we do this a second time so that the worker will log the first message on its own state and
// then send us the "received 2 messages" indication we check for below, letting us know it
// managed to persist its own state and act upon it
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -820,6 +823,7 @@ pub fn switch_to_mode_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -887,6 +891,7 @@ pub fn switch_to_mode_plugin_command_permission_denied() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -954,6 +959,7 @@ pub fn new_tabs_with_layout_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1035,6 +1041,7 @@ pub fn new_tab_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1102,6 +1109,7 @@ pub fn go_to_next_tab_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1168,6 +1176,7 @@ pub fn go_to_previous_tab_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1234,6 +1243,7 @@ pub fn resize_focused_pane_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1300,6 +1310,7 @@ pub fn resize_focused_pane_with_direction_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1366,6 +1377,7 @@ pub fn focus_next_pane_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1432,6 +1444,7 @@ pub fn focus_previous_pane_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1498,6 +1511,7 @@ pub fn move_focus_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1564,6 +1578,7 @@ pub fn move_focus_or_tab_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1630,6 +1645,7 @@ pub fn edit_scrollback_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1696,6 +1712,7 @@ pub fn write_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1762,6 +1779,7 @@ pub fn write_chars_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1828,6 +1846,7 @@ pub fn toggle_tab_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1894,6 +1913,7 @@ pub fn move_pane_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -1960,6 +1980,7 @@ pub fn move_pane_with_direction_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2027,7 +2048,7 @@ pub fn clear_screen_plugin_command() {
client_id,
size,
));
- std::thread::sleep(std::time::Duration::from_millis(100));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2095,6 +2116,7 @@ pub fn scroll_up_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2161,6 +2183,7 @@ pub fn scroll_down_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2227,6 +2250,7 @@ pub fn scroll_to_top_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2293,6 +2317,7 @@ pub fn scroll_to_bottom_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2359,6 +2384,7 @@ pub fn page_scroll_up_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2425,6 +2451,7 @@ pub fn page_scroll_down_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2491,6 +2518,7 @@ pub fn toggle_focus_fullscreen_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2557,6 +2585,7 @@ pub fn toggle_pane_frames_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2623,6 +2652,7 @@ pub fn toggle_pane_embed_or_eject_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2689,6 +2719,7 @@ pub fn undo_rename_pane_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2755,6 +2786,7 @@ pub fn close_focus_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2821,6 +2853,7 @@ pub fn toggle_active_tab_sync_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2887,6 +2920,7 @@ pub fn close_focused_tab_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -2953,6 +2987,7 @@ pub fn undo_rename_tab_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -3019,6 +3054,7 @@ pub fn previous_swap_layout_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -3085,6 +3121,7 @@ pub fn next_swap_layout_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -3151,6 +3188,7 @@ pub fn go_to_tab_name_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -3217,6 +3255,7 @@ pub fn focus_or_create_tab_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -3283,6 +3322,7 @@ pub fn go_to_tab() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -3349,6 +3389,7 @@ pub fn start_or_reload_plugin() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -3422,6 +3463,7 @@ pub fn quit_zellij_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -3495,6 +3537,7 @@ pub fn detach_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -3568,6 +3611,7 @@ pub fn open_file_floating_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -3641,6 +3685,7 @@ pub fn open_file_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -3715,6 +3760,7 @@ pub fn open_file_with_line_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -3788,6 +3834,7 @@ pub fn open_file_with_line_floating_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -3861,6 +3908,7 @@ pub fn open_terminal_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -3934,6 +3982,7 @@ pub fn open_terminal_floating_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -4007,6 +4056,7 @@ pub fn open_command_pane_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -4080,6 +4130,7 @@ pub fn open_command_pane_floating_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -4146,6 +4197,7 @@ pub fn switch_to_tab_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -4207,6 +4259,7 @@ pub fn hide_self_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -4268,6 +4321,7 @@ pub fn show_self_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -4334,6 +4388,7 @@ pub fn close_terminal_pane_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -4400,6 +4455,7 @@ pub fn close_plugin_pane_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -4466,6 +4522,7 @@ pub fn focus_terminal_pane_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -4532,6 +4589,7 @@ pub fn focus_plugin_pane_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -4598,6 +4656,7 @@ pub fn rename_terminal_pane_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -4664,6 +4723,7 @@ pub fn rename_plugin_pane_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -4730,6 +4790,7 @@ pub fn rename_tab_plugin_command() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -4805,6 +4866,7 @@ pub fn send_configuration_to_plugins() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -4868,6 +4930,7 @@ pub fn request_plugin_permissions() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -4955,6 +5018,7 @@ pub fn granted_permission_request_result() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
@@ -5040,6 +5104,7 @@ pub fn denied_permission_request_result() {
client_id,
size,
));
+ std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__go_to_tab.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__go_to_tab.snap
index c3b12bd1..8e2a7570 100644
--- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__go_to_tab.snap
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__go_to_tab.snap
@@ -1,11 +1,11 @@
---
source: zellij-server/src/plugins/./unit/plugin_tests.rs
-assertion_line: 2442
+assertion_line: 3306
expression: "format!(\"{:#?}\", new_tab_event)"
---
Some(
GoToTab(
- 2,
+ 3,
Some(
1,
),
diff --git a/zellij-server/src/plugins/wasm_bridge.rs b/zellij-server/src/plugins/wasm_bridge.rs
index 5c3c7c0d..fdc8f389 100644
--- a/zellij-server/src/plugins/wasm_bridge.rs
+++ b/zellij-server/src/plugins/wasm_bridge.rs
@@ -782,6 +782,7 @@ fn check_event_permission(
Event::ModeUpdate(..)
| Event::TabUpdate(..)
| Event::PaneUpdate(..)
+ | Event::SessionUpdate(..)
| Event::CopyToClipboard(..)
| Event::SystemClipboardFailure
| Event::InputReceived => PermissionType::ReadApplicationState,
diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs
index f2effa19..16943dbb 100644
--- a/zellij-server/src/plugins/zellij_exports.rs
+++ b/zellij-server/src/plugins/zellij_exports.rs
@@ -2,6 +2,7 @@ use super::PluginInstruction;
use crate::plugins::plugin_map::{PluginEnv, Subscriptions};
use crate::plugins::wasm_bridge::handle_plugin_crash;
use crate::route::route_action;
+use crate::ServerInstruction;
use log::{debug, warn};
use serde::Serialize;
use std::{
@@ -15,7 +16,9 @@ use std::{
};
use wasmer::{imports, Function, ImportObject, Store, WasmerEnv};
use wasmer_wasi::WasiEnv;
-use zellij_utils::data::{CommandType, PermissionStatus, PermissionType, PluginPermission};
+use zellij_utils::data::{
+ CommandType, ConnectToSession, PermissionStatus, PermissionType, PluginPermission,
+};
use zellij_utils::input::permission::PermissionCache;
use url::Url;
@@ -205,6 +208,12 @@ fn host_run_plugin_command(env: &ForeignFunctionEnv) {
PluginCommand::RequestPluginPermissions(permissions) => {
request_permission(env, permissions)?
},
+ PluginCommand::SwitchSession(connect_to_session) => switch_session(
+ env,
+ connect_to_session.name,
+ connect_to_session.tab_position,
+ connect_to_session.pane_id,
+ )?,
},
(PermissionStatus::Denied, permission) => {
log::error!(
@@ -679,6 +688,31 @@ fn detach(env: &ForeignFunctionEnv) {
apply_action!(action, error_msg, env);
}
+fn switch_session(
+ env: &ForeignFunctionEnv,
+ session_name: Option<String>,
+ tab_position: Option<usize>,
+ pane_id: Option<(u32, bool)>,
+) -> Result<()> {
+ // pane_id is (id, is_plugin)
+ let err_context = || format!("Failed to switch session");
+ let client_id = env.plugin_env.client_id;
+ let tab_position = tab_position.map(|p| p + 1); // ¯\_()_/¯
+ let connect_to_session = ConnectToSession {
+ name: session_name,
+ tab_position,
+ pane_id,
+ };
+ env.plugin_env
+ .senders
+ .send_to_server(ServerInstruction::SwitchSession(
+ connect_to_session,
+ client_id,
+ ))
+ .with_context(err_context)?;
+ Ok(())
+}
+
fn edit_scrollback(env: &ForeignFunctionEnv) {
let action = Action::EditScrollback;
let error_msg = || format!("Failed to edit scrollback");
@@ -898,7 +932,7 @@ fn go_to_tab(env: &ForeignFunctionEnv, tab_index: u32) {
env.plugin_env.name()
)
};
- let action = Action::GoToTab(tab_index);
+ let action = Action::GoToTab(tab_index + 1);
apply_action!(action, error_msg, env);
}
@@ -1113,7 +1147,6 @@ fn check_command_permission(
_ => return (PermissionStatus::Granted, None),
};
- log::info!("plugin permissions: {:?}", plugin_env.permissions);
if let Some(permissions) = plugin_env.permissions.lock().unwrap().as_ref() {
if permissions.contains(&permission) {
return (PermissionStatus::Granted, None);
diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs
index 603233ff..26b55186 100644
--- a/zellij-server/src/route.rs
+++ b/zellij-server/src/route.rs
@@ -860,9 +860,19 @@ pub(crate) fn route_thread_main(
.send(new_client_instruction)
.with_context(err_context)?;
},
- ClientToServerMsg::AttachClient(client_attributes, opts) => {
- let attach_client_instruction =
- ServerInstruction::AttachClient(client_attributes, opts, client_id);
+ ClientToServerMsg::AttachClient(
+ client_attributes,
+ opts,
+ tab_position_to_focus,
+ pane_id_to_focus,
+ ) => {
+ let attach_client_instruction = ServerInstruction::AttachClient(
+ client_attributes,
+ opts,
+ tab_position_to_focus,
+ pane_id_to_focus,
+ client_id,
+ );
to_server
.send(attach_client_instruction)
.with_context(err_context)?;
diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs
index 456bea09..2c018dcf 100644
--- a/zellij-server/src/screen.rs
+++ b/zellij-server/src/screen.rs
@@ -6,7 +6,9 @@ use std::path::PathBuf;
use std::rc::Rc;
use std::str;
-use zellij_utils::data::{Direction, PaneManifest, PluginPermission, Resize, ResizeStrategy};
+use zellij_utils::data::{
+ Direction, PaneManifest, PluginPermission, Resize, ResizeStrategy, SessionInfo,
+};
use zellij_utils::errors::prelude::*;
use zellij_utils::input::command::RunCommand;
use zellij_utils::input::options::Clipboard;
@@ -242,7 +244,11 @@ pub enum ScreenInstruction {
MouseHoldRight(Position, ClientId),
MouseHoldMiddle(Position, ClientId),
Copy(ClientId),
- AddClient(ClientId),
+ AddClient(
+ ClientId,
+ Option<usize>, // tab position to focus
+ Option<(u32, bool)>, // (pane_id, is_plugin) => pane_id to focus
+ ),
RemoveClient(ClientId),
AddOverlay(Overlay, ClientId),
RemoveOverlay(ClientId),
@@ -287,6 +293,7 @@ pub enum ScreenInstruction {
BreakPane(Box<Layout>, Option<TerminalAction>, ClientId),
BreakPaneRight(ClientId),
BreakPaneLeft(ClientId),
+ UpdateSessionInfos(BTreeMap<String, SessionInfo>), // String is the session name
}
impl From<&ScreenInstruction> for ScreenContext {
@@ -460,6 +467,7 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::BreakPane(..) => ScreenContext::BreakPane,
ScreenInstruction::BreakPaneRight(..) => ScreenContext::BreakPaneRight,
ScreenInstruction::BreakPaneLeft(..) => ScreenContext::BreakPaneLeft,
+ ScreenInstruction::UpdateSessionInfos(..) => ScreenContext::UpdateSessionInfos,
}
}
}
@@ -524,6 +532,9 @@ pub(crate) struct Screen {
session_is_mirrored: bool,
copy_options: CopyOptions,
debug: bool,
+ session_name: String,
+ session_infos_on_machine: BTreeMap<String, SessionInfo>, // String is the session name, can
+ // also be this session
}
impl Screen {
@@ -539,6 +550,10 @@ impl Screen {
copy_options: CopyOptions,
debug: bool,
) -> Self {
+ let session_name = mode_info.session_name.clone().unwrap_or_default();
+ let session_info = SessionInfo::new(session_name.clone());
+ let mut session_infos_on_machine = BTreeMap::new();
+ session_infos_on_machine.insert(session_name.clone(), session_info);
Screen {
bus,
max_panes,
@@ -561,6 +576,8 @@ impl Screen {
session_is_mirrored,
copy_options,
debug,
+ session_name,
+ session_infos_on_machine,
}
}
@@ -745,8 +762,8 @@ impl Screen {
.non_fatal();
}
- self.report_tab_state().with_context(err_context)?;
- self.report_pane_state().with_context(err_context)?;
+ self.log_and_report_session_state()
+ .with_context(err_context)?;
return self.render().with_context(err_context);
},
Err(err) => Err::<(), _>(err).with_context(err_context).non_fatal(),
@@ -879,8 +896,8 @@ impl Screen {
t.position -= 1;
}
}
- self.report_tab_state().with_context(err_context)?;
- self.report_pane_state().with_context(err_context)?;
+ self.log_and_report_session_state()
+ .with_context(err_context)?;
self.render().with_context(err_context)
}
}
@@ -917,7 +934,8 @@ impl Screen {
.with_context(err_context)?;
tab.set_force_render();
}
- self.report_pane_state().with_context(err_context)?;
+ self.log_and_report_session_state()
+ .with_context(err_context)?;
self.render().with_context(err_context)
}
@@ -1183,10 +1201,9 @@ impl Screen {
self.add_client(client_id).with_context(err_context)?;
}
- self.report_tab_state()
+ self.log_and_report_session_state()
.and_then(|_| self.render())
- .with_context(err_context)?;
- self.report_pane_state().with_context(err_context)
+ .with_context(err_context)
}
pub fn add_client(&mut self, client_id: ClientId) -> Result<()> {
@@ -1237,13 +1254,38 @@ impl Screen {
self.tab_history.remove(&client_id);
}
self.connected_clients.borrow_mut().remove(&client_id);
- self.report_tab_state().with_context(err_context)
+ self.log_and_report_session_state()
+ .with_context(err_context)
}
- pub fn report_tab_state(&self) -> Result<()> {
+ pub fn generate_and_report_tab_state(&mut self) -> Result<Vec<TabInfo>> {
let mut plugin_updates = vec![];
+ let mut tab_infos_for_screen_state = BTreeMap::new();
+ for tab in self.tabs.values() {
+ let all_focused_clients: Vec<ClientId> = self
+ .active_tab_indices
+ .iter()
+ .filter(|(_c_id, tab_position)| **tab_position == tab.index)
+ .map(|(c_id, _)| c_id)
+ .copied()
+ .collect();
+ let (active_swap_layout_name, is_swap_layout_dirty) = tab.swap_layout_info();
+ let tab_info_for_screen = TabInfo {
+ position: tab.position,
+ name: tab.name.clone(),
+ active: self.active_tab_indices.values().any(|i| i == &tab.index),
+ panes_to_hide: tab.panes_to_hide_count(),
+ 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_clients: all_focused_clients,
+ active_swap_layout_name,
+ is_swap_layout_dirty,
+ };
+ tab_infos_for_screen_state.insert(tab.position, tab_info_for_screen);
+ }
for (client_id, active_tab_index) in self.active_tab_indices.iter() {
- let mut tab_data = vec![];
+ let mut plugin_tab_updates = vec![];
for tab in self.tabs.values() {
let other_focused_clients: Vec<ClientId> = if self.session_is_mirrored {
vec![]
@@ -1258,7 +1300,7 @@ impl Screen {
.collect()
};
let (active_swap_layout_name, is_swap_layout_dirty) = tab.swap_layout_info();
- tab_data.push(TabInfo {
+ let tab_info_for_plugins = TabInfo {
position: tab.position,
name: tab.name.clone(),
active: *active_tab_index == tab.index,
@@ -1269,17 +1311,18 @@ impl Screen {
other_focused_clients,
active_swap_layout_name,
is_swap_layout_dirty,
- });
+ };
+ plugin_tab_updates.push(tab_info_for_plugins);
}
- plugin_updates.push((None, Some(*client_id), Event::TabUpdate(tab_data)));
+ plugin_updates.push((None, Some(*client_id), Event::TabUpdate(plugin_tab_updates)));
}
self.bus
.senders
.send_to_plugin(PluginInstruction::Update(plugin_updates))
.context("failed to update tabs")?;
- Ok(())
+ Ok(tab_infos_for_screen_state.values().cloned().collect())
}
- fn report_pane_state(&self) -> Result<()> {
+ fn generate_and_report_pane_state(&mut self) -> Result<PaneManifest> {
let mut pane_manifest = PaneManifest::default();
for tab in self.tabs.values() {
pane_manifest.panes.insert(tab.position, tab.pane_infos());
@@ -1289,9 +1332,51 @@ impl Screen {
.send_to_plugin(PluginInstruction::Update(vec![(
None,
None,
- Event::PaneUpdate(pane_manifest),
+ Event::PaneUpdate(pane_manifest.clone()),
)]))
.context("failed to update tabs")?;
+
+ Ok(pane_manifest)
+ }
+ fn log_and_report_session_state(&mut self) -> Result<()> {
+ let err_context = || format!("Failed to log and report session state");
+ // generate own session info
+ let pane_manifest = self.generate_and_report_pane_state()?;
+ let tab_infos = self.generate_and_report_tab_state()?;
+ let session_info = SessionInfo {
+ name: self.session_name.clone(),
+ tabs: tab_infos,
+ panes: pane_manifest,
+ connected_clients: self.active_tab_indices.keys().len(),
+ is_current_session: true,
+ };
+ self.bus
+ .senders
+ .send_to_background_jobs(BackgroundJob::ReportSessionInfo(
+ self.session_name.to_owned(),
+ session_info,
+ ))
+ .with_context(err_context)?;
+ self.bus
+ .senders
+ .send_to_background_jobs(BackgroundJob::ReadAllSessionInfosOnMachine)
+ .with_context(err_context)?;
+
+ Ok(())
+ }
+ pub fn update_session_infos(
+ &mut self,
+ new_session_infos: BTreeMap<String, SessionInfo>,
+ ) -> Result<()> {
+ self.session_infos_on_machine = new_session_infos;
+ self.bus
+ .senders
+ .send_to_plugin(PluginInstruction::Update(vec![(
+ None,
+ None,
+ Event::SessionUpdate(self.session_infos_on_machine.values().cloned().collect()),
+ )]))
+ .context("failed to update session info")?;
Ok(())
}
@@ -1327,7 +1412,8 @@ impl Screen {
}
},
}
- self.report_tab_state().with_context(err_context)
+ self.log_and_report_session_state()
+ .with_context(err_context)
},
Err(err) => {
Err::<(), _>(err).with_context(err_context).non_fatal();
@@ -1352,7 +1438,7 @@ impl Screen {
Ok(active_tab) => {
if active_tab.name != active_tab.prev_name {
active_tab.name = active_tab.prev_name.clone();
- self.report_tab_state()
+ self.log_and_report_session_state()
.context("failed to undo renaming of active tab")?;
}
},
@@ -1473,7 +1559,8 @@ impl Screen {
Err(err) => Err::<(), _>(err).with_context(err_context).non_fatal(),
};
}
- self.report_pane_state().with_context(err_context)?;
+ self.log_and_report_session_state()
+ .with_context(err_context)?;
Ok(())
}
pub fn move_focus_right_or_next_tab(&mut self, client_id: ClientId) -> Result<()> {
@@ -1508,7 +1595,8 @@ impl Screen {
Err(err) => Err::<(), _>(err).with_context(err_context).non_fatal(),
};
}
- self.report_pane_state().with_context(err_context)?;
+ self.log_and_report_session_state()
+ .with_context(err_context)?;
Ok(())
}
pub fn toggle_tab(&mut self, client_id: ClientId) -> Result<()> {
@@ -1521,8 +1609,8 @@ impl Screen {
.context("failed to toggle tabs")?;
};
- self.report_tab_state().context("failed to toggle tabs")?;
- self.report_pane_state().context("failed to toggle tabs")?;
+ self.log_and_report_session_state()
+ .context("failed to toggle tabs")?;
self.render()
}
@@ -1550,7 +1638,8 @@ impl Screen {
.with_context(err_context)?
.focus_pane_with_id(plugin_pane_id, should_float, client_id)
.context("failed to focus plugin pane")?;
- self.report_pane_state().with_context(err_context)?;
+ self.log_and_report_session_state()
+ .with_context(err_context)?;
Ok(true)
},
None => Ok(false),
@@ -1684,8 +1773,7 @@ impl Screen {
new_active_tab.add_tiled_pane(active_pane, active_pane_id, Some(client_id))?;
}
- self.report_tab_state()?;
- self.report_pane_state()?;
+ self.log_and_report_session_state()?;
} else {
let active_pane_id = {
let active_tab = self.get_active_tab_mut(client_id)?;
@@ -1843,8 +1931,7 @@ pub(crate) fn screen_thread_main(
},
};
screen.unblock_input()?;
- screen.report_tab_state()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
screen.render()?;
},
@@ -1852,8 +1939,7 @@ pub(crate) fn screen_thread_main(
active_tab!(screen, client_id, |tab: &mut Tab| tab
.suppress_active_pane(pid, client_id), ?);
screen.unblock_input()?;
- screen.report_tab_state()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
screen.render()?;
},
@@ -1861,8 +1947,7 @@ pub(crate) fn screen_thread_main(
active_tab_and_connected_client_id!(screen, client_id, |tab: &mut Tab, client_id: ClientId| tab
.toggle_pane_embed_or_floating(client_id), ?);
screen.unblock_input()?;
- screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
screen.render()?;
},
@@ -1870,8 +1955,7 @@ pub(crate) fn screen_thread_main(
active_tab_and_connected_client_id!(screen, client_id, |tab: &mut Tab, client_id: ClientId| tab
.toggle_floating_panes(Some(client_id), default_shell), ?);
screen.unblock_input()?;
- screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
screen.render()?;
},
@@ -1901,8 +1985,7 @@ pub(crate) fn screen_thread_main(
);
}
screen.unblock_input()?;
- screen.report_tab_state()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
screen.render()?;
},
ScreenInstruction::VerticalSplit(
@@ -1931,8 +2014,7 @@ pub(crate) fn screen_thread_main(
);
}
screen.unblock_input()?;
- screen.report_tab_state()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
screen.render()?;
},
ScreenInstruction::WriteCharacter(bytes, client_id) => {
@@ -1953,8 +2035,7 @@ pub(crate) fn screen_thread_main(
?
);
if state_changed {
- screen.report_tab_state()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
}
},
ScreenInstruction::Resize(client_id, strategy) => {
@@ -1966,8 +2047,7 @@ pub(crate) fn screen_thread_main(
);
screen.unblock_input()?;
screen.render()?;
- screen.report_tab_state()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::SwitchFocus(client_id) => {
active_tab_and_connected_client_id!(
@@ -1977,7 +2057,7 @@ pub(crate) fn screen_thread_main(
);
screen.unblock_input()?;
screen.render()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::FocusNextPane(client_id) => {
active_tab_and_connected_client_id!(
@@ -1996,7 +2076,7 @@ pub(crate) fn screen_thread_main(
);
screen.render()?;
screen.unblock_input()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::MoveFocusLeft(client_id) => {
active_tab_and_connected_client_id!(
@@ -2007,13 +2087,13 @@ pub(crate) fn screen_thread_main(
);
screen.render()?;
screen.unblock_input()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::MoveFocusLeftOrPreviousTab(client_id) => {
screen.move_focus_left_or_previous_tab(client_id)?;
screen.unblock_input()?;
screen.render()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::MoveFocusDown(client_id) => {
active_tab_and_connected_client_id!(
@@ -2024,7 +2104,7 @@ pub(crate) fn screen_thread_main(
);
screen.render()?;
screen.unblock_input()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::MoveFocusRight(client_id) => {
active_tab_and_connected_client_id!(
@@ -2035,13 +2115,13 @@ pub(crate) fn screen_thread_main(
);
screen.render()?;
screen.unblock_input()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::MoveFocusRightOrNextTab(client_id) => {
screen.move_focus_right_or_next_tab(client_id)?;
screen.unblock_input()?;
screen.render()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::MoveFocusUp(client_id) => {
active_tab_and_connected_client_id!(
@@ -2052,7 +2132,7 @@ pub(crate) fn screen_thread_main(
);
screen.render()?;
screen.unblock_input()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::ClearScreen(client_id) => {
active_tab_and_connected_client_id!(
@@ -2088,7 +2168,7 @@ pub(crate) fn screen_thread_main(
?
);
screen.render()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::ScrollUp(client_id) => {
active_tab_and_connected_client_id!(
@@ -2105,10 +2185,9 @@ pub(crate) fn screen_thread_main(
client_id,
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane(client_id)
);
- screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins
screen.render()?;
screen.unblock_input()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::MovePaneBackwards(client_id) => {
active_tab_and_connected_client_id!(
@@ -2116,10 +2195,9 @@ pub(crate) fn screen_thread_main(
client_id,
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_backwards(client_id)
);
- screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins
screen.render()?;
screen.unblock_input()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::MovePaneDown(client_id) => {
active_tab_and_connected_client_id!(
@@ -2127,10 +2205,9 @@ pub(crate) fn screen_thread_main(
client_id,
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_down(client_id)
);
- screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins
screen.render()?;
screen.unblock_input()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::MovePaneUp(client_id) => {
active_tab_and_connected_client_id!(
@@ -2138,10 +2215,9 @@ pub(crate) fn screen_thread_main(
client_id,
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_up(client_id)
);
- screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins
screen.render()?;
screen.unblock_input()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::MovePaneRight(client_id) => {
active_tab_and_connected_client_id!(
@@ -2149,10 +2225,9 @@ pub(crate) fn screen_thread_main(
client_id,
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_right(client_id)
);
- screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins
screen.render()?;
screen.unblock_input()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::MovePaneLeft(client_id) => {
active_tab_and_connected_client_id!(
@@ -2160,10 +2235,9 @@ pub(crate) fn screen_thread_main(
client_id,
|tab: &mut Tab, client_id: ClientId| tab.move_active_pane_left(client_id)
);
- screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins
screen.render()?;
screen.unblock_input()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::ScrollUpAt(point, client_id) => {
active_tab_and_connected_client_id!(
@@ -2270,10 +2344,9 @@ pub(crate) fn screen_thread_main(
client_id,
|tab: &mut Tab, client_id: ClientId| tab.close_focused_pane(client_id), ?
);
- screen.report_tab_state()?;
screen.render()?;
screen.unblock_input()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::SetSelectable(id, selectable, tab_index) => {
screen.get_indexed_tab_mut(tab_index).map_or_else(
@@ -2288,7 +2361,7 @@ pub(crate) fn screen_thread_main(
);
screen.render()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::ClosePane(id, client_id) => {
match client_id {
@@ -2308,9 +2381,8 @@ pub(crate) fn screen_thread_main(
}
},
}
- screen.report_tab_state()?;
screen.unblock_input()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::HoldPane(id, exit_status, run_command, tab_index, client_id) => {
let is_first_run = false;
@@ -2339,9 +2411,8 @@ pub(crate) fn screen_thread_main(
}
},
}
- screen.report_tab_state()?;
screen.unblock_input()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::UpdatePaneName(c, client_id) => {
active_tab_and_connected_client_id!(
@@ -2351,7 +2422,7 @@ pub(crate) fn screen_thread_main(
);
screen.render()?;
screen.unblock_input()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::UndoRenamePane(client_id) => {
active_tab_and_connected_client_id!(
@@ -2369,10 +2440,9 @@ pub(crate) fn screen_thread_main(
|tab: &mut Tab, client_id: ClientId| tab
.toggle_active_pane_fullscreen(client_id)
);
- screen.report_tab_state()?;
screen.render()?;
screen.unblock_input()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::TogglePaneFrames => {
screen.draw_pane_frames = !screen.draw_pane_frames;
@@ -2381,7 +2451,7 @@ pub(crate) fn screen_thread_main(
}
screen.render()?;
screen.unblock_input()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::SwitchTabNext(client_id) => {
screen.switch_tab_next(None, true, client_id)?;
@@ -2529,7 +2599,7 @@ pub(crate) fn screen_thread_main(
},
ScreenInstruction::TerminalResize(new_size) => {
screen.resize_to_screen(new_size)?;
- screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins
+ screen.log_and_report_session_state()?; // update tabs so that the ui indication will be send to the plugins
screen.render()?;
},
ScreenInstruction::TerminalPixelDimensions(pixel_dimensions) => {
@@ -2560,31 +2630,28 @@ pub(crate) fn screen_thread_main(
client_id,
|tab: &mut Tab, _client_id: ClientId| tab.toggle_sync_panes_is_active()
);
- screen.report_tab_state()?;
+ screen.log_and_report_session_state()?;
screen.render()?;
screen.unblock_input()?;
},
ScreenInstruction::LeftClick(point, client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| tab
.handle_left_click(&point, client_id), ?);
- screen.report_tab_state()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
screen.render()?;
screen.unblock_input()?;
},
ScreenInstruction::RightClick(point, client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| tab
.handle_right_click(&point, client_id), ?);
- screen.report_tab_state()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
screen.render()?;
screen.unblock_input()?;
},
ScreenInstruction::MiddleClick(point, client_id) => {
active_tab!(screen, client_id, |tab: &mut Tab| tab
.handle_middle_click(&point, client_id), ?);
- screen.report_tab_state()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
screen.render()?;
screen.unblock_input()?;
},
@@ -2632,15 +2699,26 @@ pub(crate) fn screen_thread_main(
screen.unblock_input()?;
screen.render()?;
},
- ScreenInstruction::AddClient(client_id) => {
+ ScreenInstruction::AddClient(client_id, tab_position_to_focus, pane_id_to_focus) => {
screen.add_client(client_id)?;
- screen.report_tab_state()?;
- screen.report_pane_state()?;
+ let pane_id = pane_id_to_focus.map(|(pane_id, is_plugin)| {
+ if is_plugin {
+ PaneId::Plugin(pane_id)
+ } else {
+ PaneId::Terminal(pane_id)
+ }
+ });
+ if let Some(pane_id) = pane_id {
+ screen.focus_pane_with_id(pane_id, true, client_id)?;
+ } else if let Some(tab_position_to_focus) = tab_position_to_focus {
+ screen.go_to_tab(tab_position_to_focus, client_id)?;
+ }
+ screen.log_and_report_session_state()?;
screen.render()?;
},
ScreenInstruction::RemoveClient(client_id) => {
screen.remove_client(client_id)?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
screen.render()?;
},
ScreenInstruction::AddOverlay(overlay, _client_id) => {
@@ -2755,8 +2833,7 @@ pub(crate) fn screen_thread_main(
?
);
screen.render()?;
- screen.report_tab_state()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
screen.unblock_input()?;
},
ScreenInstruction::NextSwapLayout(client_id) => {
@@ -2767,8 +2844,7 @@ pub(crate) fn screen_thread_main(
?
);
screen.render()?;
- screen.report_tab_state()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
screen.unblock_input()?;
},
ScreenInstruction::QueryTabNames(client_id) => {
@@ -2852,7 +2928,7 @@ pub(crate) fn screen_thread_main(
} else {
log::error!("Tab index not found: {:?}", tab_index);
}
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
screen.unblock_input()?;
},
ScreenInstruction::UpdatePluginLoadingStage(pid, loading_indication) => {
@@ -2890,8 +2966,7 @@ pub(crate) fn screen_thread_main(
for tab in all_tabs.values_mut() {
tab.update_input_modes()?;
}
- screen.report_tab_state()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
screen.render()?;
},
ScreenInstruction::LaunchOrFocusPlugin(run_plugin, should_float, client_id) => {
@@ -2910,7 +2985,7 @@ pub(crate) fn screen_thread_main(
Some((tab_index, client_id)) => {
if screen.focus_plugin_pane(&run_plugin, should_float, client_id)? {
screen.render()?;
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
} else {
screen.bus.senders.send_to_plugin(PluginInstruction::Load(
Some(should_float),
@@ -2934,12 +3009,11 @@ pub(crate) fn screen_thread_main(
break;
}
}
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::FocusPaneWithId(pane_id, should_float_if_hidden, client_id) => {
screen.focus_pane_with_id(pane_id, should_float_if_hidden, client_id)?;
- screen.report_pane_state()?;
- screen.report_tab_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::RenamePane(pane_id, new_name) => {
let all_tabs = screen.get_tabs_mut();
@@ -2952,7 +3026,7 @@ pub(crate) fn screen_thread_main(
break;
}
}
- screen.report_pane_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::RenameTab(tab_index, new_name) => {
match screen.tabs.get_mut(&tab_index.saturating_sub(1)) {
@@ -2963,7 +3037,7 @@ pub(crate) fn screen_thread_main(
log::error!("Failed to find tab with index: {:?}", tab_index);
},
}
- screen.report_tab_state()?;
+ screen.log_and_report_session_state()?;
},
ScreenInstruction::RequestPluginPermissions(plugin_id, plugin_permission) => {
let all_tabs = screen.get_tabs_mut();
@@ -2992,6 +3066,9 @@ pub(crate) fn screen_thread_main(
ScreenInstruction::BreakPaneLeft(client_id) => {
screen.break_pane_to_new_tab(Direction::Left, client_id)?;
},
+ ScreenInstruction::UpdateSessionInfos(new_session_infos) => {
+ screen.update_session_infos(new_session_infos)?;
+ },
}
}
Ok(())
diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs
index fb6d4e17..2da5306a 100644
--- a/zellij-server/src/tab/mod.rs
+++ b/zellij-server/src/tab/mod.rs
@@ -3328,6 +3328,7 @@ impl Tab {
// this function is to be preferred to directly invoking floating_panes.toggle_show_panes(true)
self.floating_panes.toggle_show_panes(true);
self.tiled_panes.unfocus_all_panes();
+ self.set_force_render();
}
pub fn hide_floating_panes(&mut self) {
@@ -3335,6 +3336,7 @@ impl Tab {
// floating_panes.toggle_show_panes(false)
self.floating_panes.toggle_show_panes(false);
self.tiled_panes.focus_all_panes();
+ self.set_force_render();
}
pub fn find_plugin(&self, run_plugin: &RunPlugin) -> Option<PaneId> {
@@ -3359,6 +3361,7 @@ impl Tab {
// TODO: should error if pane is not selectable
self.tiled_panes
.focus_pane_if_exists(pane_id, client_id)
+ .map(|_| self.hide_floating_panes())
.or_else(|_| {
let focused_floating_pane =
self.floating_panes.focus_pane_if_exists(pane_id, client_id);
diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs
index 6204767a..2ae6770d 100644
--- a/zellij-tile/src/shim.rs
+++ b/zellij-tile/src/shim.rs
@@ -563,6 +563,34 @@ where
unsafe { host_run_plugin_command() };
}
+/// Switch to a session with the given name, create one if no name is given
+pub fn switch_session(name: Option<&str>) {
+ let plugin_command = PluginCommand::SwitchSession(ConnectToSession {
+ name: name.map(|n| n.to_string()),
+ ..Default::default()
+ });
+ let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
+ object_to_stdout(&protobuf_plugin_command.encode_to_vec());
+ unsafe { host_run_plugin_command() };
+}
+
+/// Switch to a session with the given name, focusing either the provided pane_id or the provided
+/// tab position (in that order)
+pub fn switch_session_with_focus(
+ name: &str,
+ tab_position: Option<usize>,
+ pane_id: Option<(u32, bool)>,
+) {
+ let plugin_command = PluginCommand::SwitchSession(ConnectToSession {
+ name: Some(name.to_owned()),
+ tab_position,
+ pane_id,
+ });
+ let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
+ object_to_stdout(&protobuf_plugin_command.encode_to_vec());
+ unsafe { host_run_plugin_command() };
+}
+
// Utility Functions
/// Returns the `TabInfo` corresponding to the currently active tab
diff --git a/zellij-utils/assets/config/default.kdl b/zellij-utils/assets/config/default.kdl
index 32dfb86e..aea41f27 100644
--- a/zellij-utils/assets/config/default.kdl
+++ b/zellij-utils/assets/config/default.kdl
@@ -113,6 +113,12 @@ keybinds {
bind "Ctrl o" { SwitchToMode "Normal"; }
bind "Ctrl s" { SwitchToMode "Scroll"; }
bind "d" { Detach; }
+ bind "w" {
+ LaunchOrFocusPlugin "zellij:session-manager" {
+ floating true
+ };
+ SwitchToMode "Normal"
+ }
}
tmux {
bind "[" { SwitchToMode "Scroll"; }
@@ -181,6 +187,7 @@ plugins {
status-bar { path "status-bar"; }
strider { path "strider"; }
compact-bar { path "compact-bar"; }
+ session-manager { path "session-manager"; }
}
// Choose what to do when zellij receives SIGTERM, SIGINT, SIGQUIT or SIGHUP
diff --git a/zellij-utils/assets/plugins/compact-bar.wasm b/zellij-utils/assets/plugins/compact-bar.wasm
index 9e2112cf..20af974e 100755
--- a/zellij-utils/assets/plugins/compact-bar.wasm
+++ b/zellij-utils/assets/plugins/compact-bar.wasm
Binary files differ
diff --git a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm
index ff13cefd..f3cdb15d 100755
--- a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm
+++ b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm
Binary files differ
diff --git a/zellij-utils/assets/plugins/session-manager.wasm b/zellij-utils/assets/plugins/session-manager.wasm
new file mode 100755
index 00000000..673af58a
--- /dev/null
+++ b/zellij-utils/assets/plugins/session-manager.wasm
Binary files differ
diff --git a/zellij-utils/assets/plugins/status-bar.wasm b/zellij-utils/assets/plugins/status-bar.wasm
index e18d4edf..e91ee3eb 100755
--- a/zellij-utils/assets/plugins/status-bar.wasm
+++ b/zellij-utils/assets/plugins/status-bar.wasm
Binary files differ
diff --git a/zellij-utils/assets/plugins/strider.wasm b/zellij-utils/assets/plugins/strider.wasm
index 3aff9cc3..c85bd48a 100755
--- a/zellij-utils/assets/plugins/strider.wasm
+++ b/zellij-utils/assets/plugins/strider.wasm
Binary files differ
diff --git a/zellij-utils/assets/plugins/tab-bar.wasm b/zellij-utils/assets/plugins/tab-bar.wasm
index ee53ba18..2591e067 100755
--- a/zellij-utils/assets/plugins/tab-bar.wasm
+++ b/zellij-utils/assets/plugins/tab-bar.wasm
Binary files differ
diff --git a/zellij-utils/src/consts.rs b/zellij-utils/src/consts.rs
index a2c3a42a..43990b4b 100644
--- a/zellij-utils/src/consts.rs
+++ b/zellij-utils/src/consts.rs
@@ -38,6 +38,8 @@ lazy_static! {
.join(format!("{}", Uuid::new_v4()));
pub static ref ZELLIJ_PLUGIN_PERMISSIONS_CACHE: PathBuf =
ZELLIJ_CACHE_DIR.join("permissions.kdl");
+ pub static ref ZELLIJ_SESSION_INFO_CACHE_DIR: PathBuf =
+ ZELLIJ_CACHE_DIR.join(VERSION).join("session_info");
}
pub const FEATURES: &[&str] = &[
@@ -92,6 +94,7 @@ mod not_wasm {
add_plugin!(assets, "status-bar.wasm");
add_plugin!(assets, "tab-bar.wasm");
add_plugin!(assets, "strider.wasm");
+ add_plugin!(assets, "session-manager.wasm");
assets
};
}
@@ -104,20 +107,13 @@ pub use unix_only::*;
mod unix_only {
use super::*;
use crate::envs;
- use crate::shared::set_permissions;
+ pub use crate::shared::set_permissions;
use lazy_static::lazy_static;
use nix::unistd::Uid;
- use std::{env::temp_dir, fs};
+ use std::env::temp_dir;
lazy_static! {
static ref UID: Uid = Uid::current();
- pub static ref ZELLIJ_IPC_PIPE: PathBuf = {
- let mut sock_dir = ZELLIJ_SOCK_DIR.clone();
- fs::create_dir_all(&sock_dir).unwrap();
- set_permissions(&sock_dir, 0o700).unwrap();
- sock_dir.push(envs::get_session_name().unwrap());
- sock_dir
- };
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");
diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs
index a08e642f..31919136 100644
--- a/zellij-utils/src/data.rs
+++ b/zellij-utils/src/data.rs
@@ -495,6 +495,7 @@ pub enum Event {
FileSystemDelete(Vec<PathBuf>),
/// A Result of plugin permission request
PermissionRequestResult(PermissionStatus),
+ SessionUpdate(Vec<SessionInfo>),
}
#[derive(
@@ -734,6 +735,42 @@ impl ModeInfo {
}
}
+#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
+pub struct SessionInfo {
+ pub name: String,
+ pub tabs: Vec<TabInfo>,
+ pub panes: PaneManifest,
+ pub connected_clients: usize,
+ pub is_current_session: bool,
+}
+
+use std::hash::{Hash, Hasher};
+
+#[allow(clippy::derive_hash_xor_eq)]
+impl Hash for SessionInfo {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.name.hash(state);
+ }
+}
+
+impl SessionInfo {
+ pub fn new(name: String) -> Self {
+ SessionInfo {
+ name,
+ ..Default::default()
+ }
+ }
+ pub fn update_tab_info(&mut self, new_tab_info: Vec<TabInfo>) {
+ self.tabs = new_tab_info;
+ }
+ pub fn update_pane_info(&mut self, new_pane_info: PaneManifest) {
+ self.panes = new_pane_info;
+ }
+ pub fn update_connected_clients(&mut self, new_connected_clients: usize) {
+ self.connected_clients = new_connected_clients;
+ }
+}
+
/// Contains all the information for a currently opened tab.
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct TabInfo {
@@ -921,6 +958,13 @@ impl CommandToRun {
}
}
+#[derive(Debug, Default, Clone, Serialize, Deserialize)]
+pub struct ConnectToSession {
+ pub name: Option<String>,
+ pub tab_position: Option<usize>,
+ pub pane_id: Option<(u32, bool)>, // (id, is_plugin)
+}
+
#[derive(Debug, Default, Clone)]
pub struct PluginMessage {
pub name: String,
@@ -1016,4 +1060,5 @@ pub enum PluginCommand {
RenameTab(u32, String), // tab index, new name
ReportPanic(String), // stringified panic
RequestPluginPermissions(Vec<PermissionType>),
+ SwitchSession(ConnectToSession),
}
diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs
index 306db448..677cbc42 100644
--- a/zellij-utils/src/errors.rs
+++ b/zellij-utils/src/errors.rs
@@ -342,6 +342,7 @@ pub enum ScreenContext {
BreakPane,
BreakPaneRight,
BreakPaneLeft,
+ UpdateSessionInfos,
}
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
@@ -396,6 +397,7 @@ pub enum ClientContext {
OwnClientId,
StartedParsingStdinQuery,
DoneParsingStdinQuery,
+ SwitchSession,
}
/// Stack call representations corresponding to the different types of [`ServerInstruction`]s.
@@ -413,6 +415,7 @@ pub enum ServerContext {
ConnStatus,
ActiveClients,
Log,
+ SwitchSession,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
@@ -429,6 +432,8 @@ pub enum BackgroundJobContext {
DisplayPaneError,
AnimatePluginLoading,
StopPluginLoadingAnimation,
+ ReadAllSessionInfosOnMachine,
+ ReportSessionInfo,
Exit,
}
diff --git a/zellij-utils/src/input/permission.rs b/zellij-utils/src/input/permission.rs
index 1755fe1e..9ef7182a 100644
--- a/zellij-utils/src/input/permission.rs
+++ b/zellij-utils/src/input/permission.rs
@@ -27,12 +27,16 @@ impl PermissionCache {
pub fn check_permissions(
&self,
plugin_name: String,
- permissions: &Vec<PermissionType>,
+ permissions_to_check: &Vec<PermissionType>,
) -> bool {
if let Some(target) = self.granted.get(&plugin_name) {
- if target == permissions {
- return true;
+ let mut all_granted = true;
+ for permission in permissions_to_check {
+ if !target.contains(permission) {
+ all_granted = false;
+ }
}
+ return all_granted;
}
false
@@ -43,7 +47,10 @@ impl PermissionCache {
let granted = match fs::read_to_string(cache_path.clone()) {
Ok(raw_string) => PermissionCache::from_string(raw_string).unwrap_or_default(),
- Err(_) => GrantedPermission::default(),
+ Err(e) => {
+ log::error!("Failed to read permission cache file: {}", e);
+ GrantedPermission::default()
+ },
};
PermissionCache {
diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs
index 149a157a..0f341520 100644
--- a/zellij-utils/src/ipc.rs
+++ b/zellij-utils/src/ipc.rs
@@ -1,7 +1,7 @@
//! IPC stuff for starting to split things into a client and server model.
use crate::{
cli::CliArgs,
- data::{ClientId, InputMode, Style},
+ data::{ClientId, ConnectToSession, InputMode, Style},
errors::{get_current_ctx, prelude::*, ErrorContext},
input::keybinds::Keybinds,
input::{actions::Action, layout::Layout, options::Options, plugins::PluginsConfig},
@@ -65,16 +65,6 @@ impl PixelDimensions {
#[allow(clippy::large_enum_variant)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ClientToServerMsg {
- /*// List which sessions are available
- ListSessions,
- // Create a new session
- CreateSession,
- // Attach to a running session
- AttachToSession(SessionId, ClientType),
- // Force detach
- DetachSession(SessionId),
- // Disconnect from the session we're connected to
- DisconnectFromSession,*/
DetachSession(Vec<ClientId>),
TerminalPixelDimensions(PixelDimensions),
BackgroundColor(String),
@@ -88,7 +78,12 @@ pub enum ClientToServerMsg {
Box<Layout>,
Option<PluginsConfig>,
),
- AttachClient(ClientAttributes, Options),
+ AttachClient(
+ ClientAttributes,
+ Options,
+ Option<usize>, // tab position to focus
+ Option<(u32, bool)>, // (pane_id, is_plugin) => pane id to focus
+ ),
Action(Action, Option<ClientId>),
ClientExited,
KillSession,
@@ -99,10 +94,6 @@ pub enum ClientToServerMsg {
// Types of messages sent from the server to the client
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ServerToClientMsg {
- /*// Info about a particular session
- SessionInfo(Session),
- // A list of sessions
- SessionList(HashSet<Session>),*/
Render(String),
UnblockInputThread,
Exit(ExitReason),
@@ -110,6 +101,7 @@ pub enum ServerToClientMsg {
Connected,
ActiveClients(Vec<ClientId>),
Log(Vec<String>),
+ SwitchSession(ConnectToSession),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs
index 13a03ad1..af49dbb3 100644
--- a/zellij-utils/src/kdl/mod.rs
+++ b/zellij-utils/src/kdl/mod.rs
@@ -1,5 +1,8 @@
mod kdl_layout_parser;
-use crate::data::{Direction, InputMode, Key, Palette, PaletteColor, PermissionType, Resize};
+use crate::data::{
+ Direction, InputMode, Key, Palette, PaletteColor, PaneInfo, PaneManifest, PermissionType,
+ Resize, SessionInfo, TabInfo,
+};
use crate::envs::EnvironmentVariables;
use crate::input::config::{Config, ConfigError, KdlError};
use crate::input::keybinds::Keybinds;
@@ -325,7 +328,6 @@ macro_rules! actions_from_kdl {
pub fn kdl_arguments_that_are_strings<'a>(
arguments: impl Iterator<Item = &'a KdlEntry>,
) -> Result<Vec<String>, ConfigError> {
- // pub fn kdl_arguments_that_are_strings <'a>(arguments: impl Iterator<Item=&'a KdlValue>) -> Result<Vec<String>, ConfigError> {
let mut args: Vec<String> = vec![];
for kdl_entry in arguments {
match kdl_entry.value().as_string() {
@@ -1841,6 +1843,419 @@ impl PermissionCache {
}
}
+impl SessionInfo {
+ pub fn from_string(raw_session_info: &str, current_session_name: &str) -> Result<Self, String> {
+ let kdl_document: KdlDocument = raw_session_info
+ .parse()
+ .map_err(|e| format!("Failed to parse kdl document: {}", e))?;
+ let name = kdl_document
+ .get("name")
+ .and_then(|n| n.entries().iter().next())
+ .and_then(|e| e.value().as_string())
+ .map(|s| s.to_owned())
+ .ok_or("Failed to parse session name")?;
+ let connected_clients = kdl_document
+ .get("connected_clients")
+ .and_then(|n| n.entries().iter().next())
+ .and_then(|e| e.value().as_i64())
+ .map(|c| c as usize)
+ .ok_or("Failed to parse connected_clients")?;
+ let tabs: Vec<TabInfo> = kdl_document
+ .get("tabs")
+ .and_then(|t| t.children())
+ .and_then(|c| {
+ let mut tab_nodes = vec![];
+ for tab_node in c.nodes() {
+ if let Some(tab) = tab_node.children() {
+ tab_nodes.push(TabInfo::decode_from_kdl(tab).ok()?);
+ }
+ }
+ Some(tab_nodes)
+ })
+ .ok_or("Failed to parse tabs")?;
+ let panes: PaneManifest = kdl_document
+ .get("panes")
+ .and_then(|p| p.children())
+ .map(|p| PaneManifest::decode_from_kdl(p))
+ .ok_or("Failed to parse panes")?;
+ let is_current_session = name == current_session_name;
+ Ok(SessionInfo {
+ name,
+ tabs,
+ panes,
+ connected_clients,
+ is_current_session,
+ })
+ }
+ pub fn to_string(&self) -> String {
+ let mut kdl_document = KdlDocument::new();
+
+ let mut name = KdlNode::new("name");
+ name.push(self.name.clone());
+
+ let mut connected_clients = KdlNode::new("connected_clients");
+ connected_clients.push(self.connected_clients as i64);
+
+ let mut tabs = KdlNode::new("tabs");
+ let mut tab_children = KdlDocument::new();
+ for tab_info in &self.tabs {
+ let mut tab = KdlNode::new("tab");
+ let kdl_tab_info = tab_info.encode_to_kdl();
+ tab.set_children(kdl_tab_info);
+ tab_children.nodes_mut().push(tab);
+ }
+ tabs.set_children(tab_children);
+
+ let mut panes = KdlNode::new("panes");
+ panes.set_children(self.panes.encode_to_kdl());
+
+ kdl_document.nodes_mut().push(name);
+ kdl_document.nodes_mut().push(tabs);
+ kdl_document.nodes_mut().push(panes);
+ kdl_document.nodes_mut().push(connected_clients);
+ kdl_document.fmt();
+ kdl_document.to_string()
+ }
+}
+
+impl TabInfo {
+ pub fn decode_from_kdl(kdl_document: &KdlDocument) -> Result<Self, String> {
+ macro_rules! int_node {
+ ($name:expr, $type:ident) => {{
+ kdl_document
+ .get($name)
+ .and_then(|n| n.entries().iter().next())
+ .and_then(|e| e.value().as_i64())
+ .map(|e| e as $type)
+ .ok_or(format!("Failed to parse tab {}", $name))?
+ }};
+ }
+ macro_rules! string_node {
+ ($name:expr) => {{
+ kdl_document
+ .get($name)
+ .and_then(|n| n.entries().iter().next())
+ .and_then(|e| e.value().as_string())
+ .map(|s| s.to_owned())
+ .ok_or(format!("Failed to parse tab {}", $name))?
+ }};
+ }
+ macro_rules! optional_string_node {
+ ($name:expr) => {{
+ kdl_document
+ .get($name)
+ .and_then(|n| n.entries().iter().next())
+ .and_then(|e| e.value().as_string())
+ .map(|s| s.to_owned())
+ }};
+ }
+ macro_rules! bool_node {
+ ($name:expr) => {{
+ kdl_document
+ .get($name)
+ .and_then(|n| n.entries().iter().next())
+ .and_then(|e| e.value().as_bool())
+ .ok_or(format!("Failed to parse tab {}", $name))?
+ }};
+ }
+
+ let position = int_node!("position", usize);
+ let name = string_node!("name");
+ let active = bool_node!("active");
+ let panes_to_hide = int_node!("panes_to_hide", usize);
+ let is_fullscreen_active = bool_node!("is_fullscreen_active");
+ let is_sync_panes_active = bool_node!("is_sync_panes_active");
+ let are_floating_panes_visible = bool_node!("are_floating_panes_visible");
+ let mut other_focused_clients = vec![];
+ if let Some(tab_other_focused_clients) = kdl_document
+ .get("other_focused_clients")
+ .map(|n| n.entries())
+ {
+ for entry in tab_other_focused_clients {
+ if let Some(entry_parsed) = entry.value().as_i64() {
+ other_focused_clients.push(entry_parsed as u16);
+ }
+ }
+ }
+ let active_swap_layout_name = optional_string_node!("active_swap_layout_name");
+ let is_swap_layout_dirty = bool_node!("is_swap_layout_dirty");
+ Ok(TabInfo {
+ position,
+ name,
+ active,
+ panes_to_hide,
+ is_fullscreen_active,
+ is_sync_panes_active,
+ are_floating_panes_visible,
+ other_focused_clients,
+ active_swap_layout_name,
+ is_swap_layout_dirty,
+ })
+ }
+ pub fn encode_to_kdl(&self) -> KdlDocument {
+ let mut kdl_doucment = KdlDocument::new();
+
+ let mut position = KdlNode::new("position");
+ position.push(self.position as i64);
+ kdl_doucment.nodes_mut().push(position);
+
+ let mut name = KdlNode::new("name");
+ name.push(self.name.clone());
+ kdl_doucment.nodes_mut().push(name);
+
+ let mut active = KdlNode::new("active");
+ active.push(self.active);
+ kdl_doucment.nodes_mut().push(active);
+
+ let mut panes_to_hide = KdlNode::new("panes_to_hide");
+ panes_to_hide.push(self.panes_to_hide as i64);
+ kdl_doucment.nodes_mut().push(panes_to_hide);
+
+ let mut is_fullscreen_active = KdlNode::new("is_fullscreen_active");
+ is_fullscreen_active.push(self.is_fullscreen_active);
+ kdl_doucment.nodes_mut().push(is_fullscreen_active);
+
+ let mut is_sync_panes_active = KdlNode::new("is_sync_panes_active");
+ is_sync_panes_active.push(self.is_sync_panes_active);
+ kdl_doucment.nodes_mut().push(is_sync_panes_active);
+
+ let mut are_floating_panes_visible = KdlNode::new("are_floating_panes_visible");
+ are_floating_panes_visible.push(self.are_floating_panes_visible);
+ kdl_doucment.nodes_mut().push(are_floating_panes_visible);
+
+ if !self.other_focused_clients.is_empty() {
+ let mut other_focused_clients = KdlNode::new("other_focused_clients");
+ for client_id in &self.other_focused_clients {
+ other_focused_clients.push(*client_id as i64);
+ }
+ kdl_doucment.nodes_mut().push(other_focused_clients);
+ }
+
+ if let Some(active_swap_layout_name) = self.active_swap_layout_name.as_ref() {
+ let mut active_swap_layout = KdlNode::new("active_swap_layout_name");
+ active_swap_layout.push(active_swap_layout_name.to_string());
+ kdl_doucment.nodes_mut().push(active_swap_layout);
+ }
+
+ let mut is_swap_layout_dirty = KdlNode::new("is_swap_layout_dirty");
+ is_swap_layout_dirty.push(self.is_swap_layout_dirty);
+ kdl_doucment.nodes_mut().push(is_swap_layout_dirty);
+
+ kdl_doucment
+ }
+}
+
+impl PaneManifest {
+ pub fn decode_from_kdl(kdl_doucment: &KdlDocument) -> Self {
+ let mut panes: HashMap<usize, Vec<PaneInfo>> = HashMap::new();
+ for node in kdl_doucment.nodes() {
+ if node.name().to_string() == "pane" {
+ if let Some(pane_document) = node.children() {
+ if let Ok((tab_position, pane_info)) = PaneInfo::decode_from_kdl(pane_document)
+ {
+ let panes_in_tab_position =
+ panes.entry(tab_position).or_insert_with(Vec::new);
+ panes_in_tab_position.push(pane_info);
+ }
+ }
+ }
+ }
+ PaneManifest { panes }
+ }
+ pub fn encode_to_kdl(&self) -> KdlDocument {
+ let mut kdl_doucment = KdlDocument::new();
+ for (tab_position, panes) in &self.panes {
+ for pane in panes {
+ let mut pane_node = KdlNode::new("pane");
+ let mut pane = pane.encode_to_kdl();
+
+ let mut position_node = KdlNode::new("tab_position");
+ position_node.push(*tab_position as i64);
+ pane.nodes_mut().push(position_node);
+
+ pane_node.set_children(pane);
+ kdl_doucment.nodes_mut().push(pane_node);
+ }
+ }
+ kdl_doucment
+ }
+}
+
+impl PaneInfo {
+ pub fn decode_from_kdl(kdl_document: &KdlDocument) -> Result<(usize, Self), String> {
+ // usize is the tab position
+ macro_rules! int_node {
+ ($name:expr, $type:ident) => {{
+ kdl_document
+ .get($name)
+ .and_then(|n| n.entries().iter().next())
+ .and_then(|e| e.value().as_i64())
+ .map(|e| e as $type)
+ .ok_or(format!("Failed to parse pane {}", $name))?
+ }};
+ }
+ macro_rules! optional_int_node {
+ ($name:expr, $type:ident) => {{
+ kdl_document
+ .get($name)
+ .and_then(|n| n.entries().iter().next())
+ .and_then(|e| e.value().as_i64())
+ .map(|e| e as $type)
+ }};
+ }
+ macro_rules! bool_node {
+ ($name:expr) => {{
+ kdl_document
+ .get($name)
+ .and_then(|n| n.entries().iter().next())
+ .and_then(|e| e.value().as_bool())
+ .ok_or(format!("Failed to parse pane {}", $name))?
+ }};
+ }
+ macro_rules! string_node {
+ ($name:expr) => {{
+ kdl_document
+ .get($name)
+ .and_then(|n| n.entries().iter().next())
+ .and_then(|e| e.value().as_string())
+ .map(|s| s.to_owned())
+ .ok_or(format!("Failed to parse pane {}", $name))?
+ }};
+ }
+ macro_rules! optional_string_node {
+ ($name:expr) => {{
+ kdl_document
+ .get($name)
+ .and_then(|n| n.entries().iter().next())
+ .and_then(|e| e.value().as_string())
+ .map(|s| s.to_owned())
+ }};
+ }
+ let tab_position = int_node!("tab_position", usize);
+ let id = int_node!("id", u32);
+
+ let is_plugin = bool_node!("is_plugin");
+ let is_focused = bool_node!("is_focused");
+ let is_fullscreen = bool_node!("is_fullscreen");
+ let is_floating = bool_node!("is_floating");
+ let is_suppressed = bool_node!("is_suppressed");
+ let title = string_node!("title");
+ let exited = bool_node!("exited");
+ let exit_status = optional_int_node!("exit_status", i32);
+ let is_held = bool_node!("is_held");
+ let pane_x = int_node!("pane_x", usize);
+ let pane_content_x = int_node!("pane_content_x", usize);
+ let pane_y = int_node!("pane_y", usize);
+ let pane_content_y = int_node!("pane_content_y", usize);
+ let pane_rows = int_node!("pane_rows", usize);
+ let pane_content_rows = int_node!("pane_content_rows", usize);
+ let pane_columns = int_node!("pane_columns", usize);
+ let pane_content_columns = int_node!("pane_content_columns", usize);
+ let cursor_coordinates_in_pane = kdl_document
+ .get("cursor_coordinates_in_pane")
+ .map(|n| {
+ let mut entries = n.entries().iter();
+ (entries.next(), entries.next())
+ })
+ .and_then(|(x, y)| {
+ let x = x.and_then(|x| x.value().as_i64()).map(|x| x as usize);
+ let y = y.and_then(|y| y.value().as_i64()).map(|y| y as usize);
+ match (x, y) {
+ (Some(x), Some(y)) => Some((x, y)),
+ _ => None,
+ }
+ });
+ let terminal_command = optional_string_node!("terminal_command");
+ let plugin_url = optional_string_node!("plugin_url");
+ let is_selectable = bool_node!("is_selectable");
+
+ let pane_info = PaneInfo {
+ id,
+ is_plugin,
+ is_focused,
+ is_fullscreen,
+ is_floating,
+ is_suppressed,
+ title,
+ exited,
+ exit_status,
+ is_held,
+ pane_x,
+ pane_content_x,
+ pane_y,
+ pane_content_y,
+ pane_rows,
+ pane_content_rows,
+ pane_columns,
+ pane_content_columns,
+ cursor_coordinates_in_pane,
+ terminal_command,
+ plugin_url,
+ is_selectable,
+ };
+ Ok((tab_position, pane_info))
+ }
+ pub fn encode_to_kdl(&self) -> KdlDocument {
+ let mut kdl_doucment = KdlDocument::new();
+ macro_rules! int_node {
+ ($name:expr, $val:expr) => {{
+ let mut att = KdlNode::new($name);
+ att.push($val as i64);
+ kdl_doucment.nodes_mut().push(att);
+ }};
+ }
+ macro_rules! bool_node {
+ ($name:expr, $val:expr) => {{
+ let mut att = KdlNode::new($name);
+ att.push($val);
+ kdl_doucment.nodes_mut().push(att);
+ }};
+ }
+ macro_rules! string_node {
+ ($name:expr, $val:expr) => {{
+ let mut att = KdlNode::new($name);
+ att.push($val);
+ kdl_doucment.nodes_mut().push(att);
+ }};
+ }
+
+ int_node!("id", self.id);
+ bool_node!("is_plugin", self.is_plugin);
+ bool_node!("is_focused", self.is_focused);
+ bool_node!("is_fullscreen", self.is_fullscreen);
+ bool_node!("is_floating", self.is_floating);
+ bool_node!("is_suppressed", self.is_suppressed);
+ string_node!("title", self.title.to_string());
+ bool_node!("exited", self.exited);
+ if let Some(exit_status) = self.exit_status {
+ int_node!("exit_status", exit_status);
+ }
+ bool_node!("is_held", self.is_held);
+ int_node!("pane_x", self.pane_x);
+ int_node!("pane_content_x", self.pane_content_x);
+ int_node!("pane_y", self.pane_y);
+ int_node!("pane_content_y", self.pane_content_y);
+ int_node!("pane_rows", self.pane_rows);
+ int_node!("pane_content_rows", self.pane_content_rows);
+ int_node!("pane_columns", self.pane_columns);
+ int_node!("pane_content_columns", self.pane_content_columns);
+ if let Some((cursor_x, cursor_y)) = self.cursor_coordinates_in_pane {
+ let mut cursor_coordinates_in_pane = KdlNode::new("cursor_coordinates_in_pane");
+ cursor_coordinates_in_pane.push(cursor_x as i64);
+ cursor_coordinates_in_pane.push(cursor_y as i64);
+ kdl_doucment.nodes_mut().push(cursor_coordinates_in_pane);
+ }
+ if let Some(terminal_command) = &self.terminal_command {
+ string_node!("terminal_command", terminal_command.to_string());
+ }
+ if let Some(plugin_url) = &self.plugin_url {
+ string_node!("plugin_url", plugin_url.to_string());
+ }
+ bool_node!("is_selectable", self.is_selectable);
+ kdl_doucment
+ }
+}
+
pub fn parse_plugin_user_configuration(
plugin_block: &KdlNode,
) -> Result<BTreeMap<String, String>, ConfigError> {
@@ -1888,3 +2303,104 @@ pub fn parse_plugin_user_configuration(
}
Ok(configuration)
}
+
+#[test]
+fn serialize_and_deserialize_session_info() {
+ let session_info = SessionInfo::default();
+ let serialized = session_info.to_string();
+ let deserealized = SessionInfo::from_string(&serialized, "not this session").unwrap();
+ assert_eq!(session_info, deserealized);
+ insta::assert_snapshot!(serialized);
+}
+
+#[test]
+fn serialize_and_deserialize_session_info_with_data() {
+ let panes_list = vec![
+ PaneInfo {
+ id: 1,
+ is_plugin: false,
+ is_focused: true,
+ is_fullscreen: true,
+ is_floating: false,
+ is_suppressed: false,
+ title: "pane 1".to_owned(),
+ exited: false,
+ exit_status: None,
+ is_held: false,
+ pane_x: 0,
+ pane_content_x: 1,
+ pane_y: 0,
+ pane_content_y: 1,
+ pane_rows: 5,
+ pane_content_rows: 4,
+ pane_columns: 22,
+ pane_content_columns: 21,
+ cursor_coordinates_in_pane: Some((0, 0)),
+ terminal_command: Some("foo".to_owned()),
+ plugin_url: None,
+ is_selectable: true,
+ },
+ PaneInfo {
+ id: 1,
+ is_plugin: true,
+ is_focused: true,
+ is_fullscreen: true,
+ is_floating: false,
+ is_suppressed: false,
+ title: "pane 1".to_owned(),
+ exited: false,
+ exit_status: None,
+ is_held: false,
+ pane_x: 0,
+ pane_content_x: 1,
+ pane_y: 0,
+ pane_content_y: 1,
+ pane_rows: 5,
+ pane_content_rows: 4,
+ pane_columns: 22,
+ pane_content_columns: 21,
+ cursor_coordinates_in_pane: Some((0, 0)),
+ terminal_command: None,
+ plugin_url: Some("i_am_a_fake_plugin".to_owned()),
+ is_selectable: true,
+ },
+ ];
+ let mut panes = HashMap::new();
+ panes.insert(0, panes_list);
+ let session_info = SessionInfo {
+ name: "my session name".to_owned(),
+ tabs: vec![
+ TabInfo {
+ position: 0,
+ name: "tab 1".to_owned(),
+ active: true,
+ panes_to_hide: 1,
+ is_fullscreen_active: true,
+ is_sync_panes_active: false,
+ are_floating_panes_visible: true,
+ other_focused_clients: vec![2, 3],
+ active_swap_layout_name: Some("BASE".to_owned()),
+ is_swap_layout_dirty: true,
+ },
+ TabInfo {
+ position: 1,
+ name: "tab 2".to_owned(),
+ active: true,
+ panes_to_hide: 0,
+ is_fullscreen_active: false,
+ is_sync_panes_active: true,
+ are_floating_panes_visible: true,
+ other_focused_clients: vec![2, 3],
+ active_swap_layout_name: None,
+ is_swap_layout_dirty: false,
+ },
+ ],
+ panes: PaneManifest { panes },
+ connected_clients: 2,
+ is_current_session: false,
+ };
+ let serialized = session_info.to_string();
+ let deserealized = SessionInfo::from_string(&serialized, "not this session").unwrap();
+ assert_eq!(session_info, deserealized);
+ insta::assert_snapshot!(serialized);
+}
diff --git a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info.snap b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info.snap
new file mode 100644
index 00000000..4a231c37
--- /dev/null
+++ b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info.snap
@@ -0,0 +1,12 @@
+---
+source: zellij-utils/src/kdl/mod.rs
+assertion_line: 2284
+expression: serialized
+---
+name ""
+tabs {
+}
+panes {
+}
+connected_clients 0
+
diff --git a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info_with_data.snap b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info_with_data.snap
new file mode 100644
index 00000000..99e3e03f
--- /dev/null
+++ b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__serialize_and_deserialize_session_info_with_data.snap
@@ -0,0 +1,81 @@
+---
+source: zellij-utils/src/kdl/mod.rs
+assertion_line: 2377
+expression: serialized
+---
+name "my session name"
+tabs {
+ tab {
+ position 0
+ name "tab 1"
+ active true
+ panes_to_hide 1
+ is_fullscreen_active true
+ is_sync_panes_active false
+ are_floating_panes_visible true
+ other_focused_clients 2 3
+ active_swap_layout_name "BASE"
+ is_swap_layout_dirty true
+ }
+ tab {
+ position 1
+ name "tab 2"
+ active true
+ panes_to_hide 0
+ is_fullscreen_active false
+ is_sync_panes_active true
+ are_floating_panes_visible true
+ other_focused_clients 2 3
+ is_swap_layout_dirty false
+ }
+}
+panes {
+ pane {
+ id 1
+ is_plugin false
+ is_focused true
+ is_fullscreen true
+ is_floating false
+ is_suppressed false
+ title "pane 1"
+ exited false
+ is_held false
+ pane_x 0
+ pane_content_x 1
+ pane_y 0
+ pane_content_y 1
+ pane_rows 5
+ pane_content_rows 4
+ pane_columns 22
+ pane_content_columns 21
+ cursor_coordinates_in_pane 0 0
+ terminal_command "foo"
+ is_selectable true
+ tab_position 0
+ }
+ pane {
+ id 1
+ is_plugin true
+ is_focused true
+ is_fullscreen true
+ is_floating false
+ is_suppressed false
+ title "pane 1"
+ exited false
+ is_held false
+ pane_x 0
+ pane_content_x 1
+ pane_y 0
+ pane_content_y 1
+ pane_rows 5
+ pane_content_rows 4
+ pane_columns 22
+ pane_content_columns 21
+ cursor_coordinates_in_pane 0 0
+ plugin_url "i_am_a_fake_plugin"
+ is_selectable true
+ tab_position 0
+ }
+}
+connected_clients 2
+
diff --git a/zellij-utils/src/plugin_api/event.proto b/zellij-utils/src/plugin_api/event.proto
index 3a9838fa..4d5b805d 100644
--- a/zellij-utils/src/plugin_api/event.proto
+++ b/zellij-utils/src/plugin_api/event.proto
@@ -39,6 +39,7 @@ enum EventType {
/// A file was deleted somewhere in the Zellij CWD folder
FileSystemDelete = 14;
PermissionRequestResult = 15;
+ SessionUpdate = 16;
}
message EventNameList {
@@ -59,9 +60,14 @@ message Event {
CustomMessagePayload custom_message_payload = 10;
FileListPayload file_list_payload = 11;
PermissionRequestResultPayload permission_request_result_payload = 12;
+ SessionUpdatePayload session_update_payload = 13;
}
}
+message SessionUpdatePayload {
+ repeated SessionManifest session_manifests = 1;
+}
+
message PermissionRequestResultPayload {
bool granted = 1;
}
@@ -111,6 +117,14 @@ message PaneManifest {
repeated PaneInfo panes = 2;
}
+message SessionManifest {
+ string name = 1;
+ repeated TabInfo tabs = 2;
+ repeated PaneManifest panes = 3;
+ uint32 connected_clients = 4;
+ bool is_current_session = 5;
+}
+
message PaneInfo {
uint32 id = 1;
bool is_plugin = 2;
diff --git a/zellij-utils/src/plugin_api/event.rs b/zellij-utils/src/plugin_api/event.rs
index 6115fe1e..9b0e9886 100644
--- a/zellij-utils/src/plugin_api/event.rs
+++ b/zellij-utils/src/plugin_api/event.rs
@@ -6,7 +6,7 @@ pub use super::generated_api::api::{
EventType as ProtobufEventType, InputModeKeybinds as ProtobufInputModeKeybinds,
KeyBind as ProtobufKeyBind, ModeUpdatePayload as ProtobufModeUpdatePayload,
PaneInfo as ProtobufPaneInfo, PaneManifest as ProtobufPaneManifest,
- TabInfo as ProtobufTabInfo, *,
+ SessionManifest as ProtobufSessionManifest, TabInfo as ProtobufTabInfo, *,
},
input_mode::InputMode as ProtobufInputMode,
key::Key as ProtobufKey,
@@ -14,7 +14,7 @@ pub use super::generated_api::api::{
};
use crate::data::{
CopyDestination, Event, EventType, InputMode, Key, ModeInfo, Mouse, PaneInfo, PaneManifest,
- PermissionStatus, PluginCapabilities, Style, TabInfo,
+ PermissionStatus, PluginCapabilities, SessionInfo, Style, TabInfo,
};
use crate::errors::prelude::*;
@@ -171,6 +171,18 @@ impl TryFrom<ProtobufEvent> for Event {
},
_ => Err("Malformed payload for the file system delete Event"),
},
+ Some(ProtobufEventType::SessionUpdate) => match protobuf_event.payload {
+ Some(ProtobufEventPayload::SessionUpdatePayload(
+ protobuf_session_update_payload,
+ )) => {
+ let mut session_infos: Vec<SessionInfo> = vec![];
+ for protobuf_session_info in protobuf_session_update_payload.session_manifests {
+ session_infos.push(SessionInfo::try_from(protobuf_session_info)?);
+ }
+ Ok(Event::SessionUpdate(session_infos))
+ },
+ _ => Err("Malformed payload for the SessionUpdate Event"),
+ },
None => Err("Unknown Protobuf Event"),
}
}
@@ -313,7 +325,80 @@ impl TryFrom<Event> for ProtobufEvent {
)),
})
},
+ Event::SessionUpdate(session_infos) => {
+ let mut protobuf_session_manifests = vec![];
+ for session_info in session_infos {
+ protobuf_session_manifests.push(session_info.try_into()?);
+ }
+ let session_update_payload = SessionUpdatePayload {
+ session_manifests: protobuf_session_manifests,
+ };
+ Ok(ProtobufEvent {
+ name: ProtobufEventType::SessionUpdate as i32,
+ payload: Some(event::Payload::SessionUpdatePayload(session_update_payload)),
+ })
+ },
+ }
+ }
+}
+
+impl TryFrom<SessionInfo> for ProtobufSessionManifest {
+ type Error = &'static str;
+ fn try_from(session_info: SessionInfo) -> Result<Self, &'static str> {
+ let mut protobuf_pane_manifests = vec![];
+ for (tab_index, pane_infos) in session_info.panes.panes {
+ let mut protobuf_pane_infos = vec![];
+ for pane_info in pane_infos {
+ protobuf_pane_infos.push(pane_info.try_into()?);
+ }
+ protobuf_pane_manifests.push(ProtobufPaneManifest {
+ tab_index: tab_index as u32,
+ panes: protobuf_pane_infos,
+ });
+ }
+ Ok(ProtobufSessionManifest {
+ name: session_info.name,
+ panes: protobuf_pane_manifests,
+ tabs: session_info
+ .tabs
+ .iter()
+ .filter_map(|t| t.clone().try_into().ok())
+ .collect(),
+ connected_clients: session_info.connected_clients as u32,
+ is_current_session: session_info.is_current_session,
+ })
+ }
+}
+
+impl TryFrom<ProtobufSessionManifest> for SessionInfo {
+ type Error = &'static str;
+ fn try_from(protobuf_session_manifest: ProtobufSessionManifest) -> Result<Self, &'static str> {
+ let mut pane_manifest: HashMap<usize, Vec<PaneInfo>> = HashMap::new();
+ for protobuf_pane_manifest in protobuf_session_manifest.panes {
+ let tab_index = protobuf_pane_manifest.tab_index as usize;
+ let mut panes = vec![];
+ for protobuf_pane_info in protobuf_pane_manifest.panes {
+ panes.push(protobuf_pane_info.try_into()?);
+ }
+ if pane_manifest.contains_key(&tab_index) {
+ return Err("Duplicate tab definition in pane manifest");
+ }
+ pane_manifest.insert(tab_index, panes);
}
+ let panes = PaneManifest {
+ panes: pane_manifest,
+ };
+ Ok(SessionInfo {
+ name: protobuf_session_manifest.name,
+ tabs: protobuf_session_manifest
+ .tabs
+ .iter()
+ .filter_map(|t| t.clone().try_into().ok())
+ .collect(),
+ panes,
+ connected_clients: protobuf_session_manifest.connected_clients as usize,
+ is_current_session: protobuf_session_manifest.is_current_session,
+ })
}
}
@@ -697,6 +782,7 @@ impl TryFrom<ProtobufEventType> for EventType {
ProtobufEventType::FileSystemUpdate => EventType::FileSystemUpdate,
ProtobufEventType::FileSystemDelete => EventType::FileSystemDelete,
ProtobufEventType::PermissionRequestResult => EventType::PermissionRequestResult,
+ ProtobufEventType::SessionUpdate => EventType::SessionUpdate,
})
}
}
@@ -721,6 +807,7 @@ impl TryFrom<EventType> for ProtobufEventType {
EventType::FileSystemUpdate => ProtobufEventType::FileSystemUpdate,
EventType::FileSystemDelete => ProtobufEventType::FileSystemDelete,
EventType::PermissionRequestResult => ProtobufEventType::PermissionRequestResult,
+ EventType::SessionUpdate => ProtobufEventType::SessionUpdate,
})
}
}
@@ -1083,3 +1170,130 @@ fn serialize_file_system_delete_event() {
"Event properly serialized/deserialized without change"
);
}
+
+#[test]
+fn serialize_session_update_event() {
+ use prost::Message;
+ let session_update_event = Event::SessionUpdate(Default::default());
+ let protobuf_event: ProtobufEvent = session_update_event.clone().try_into().unwrap();
+ let serialized_protobuf_event = protobuf_event.encode_to_vec();
+ let deserialized_protobuf_event: ProtobufEvent =
+ Message::decode(serialized_protobuf_event.as_slice()).unwrap();
+ let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap();
+ assert_eq!(
+ session_update_event, deserialized_event,
+ "Event properly serialized/deserialized without change"
+ );
+}
+
+#[test]
+fn serialize_session_update_event_with_non_default_values() {
+ use prost::Message;
+ let tab_infos = vec![
+ TabInfo {
+ position: 0,
+ name: "First tab".to_owned(),
+ active: true,
+ panes_to_hide: 2,
+ is_fullscreen_active: true,
+ is_sync_panes_active: false,
+ are_floating_panes_visible: true,
+ other_focused_clients: vec![2, 3, 4],
+ active_swap_layout_name: Some("my cool swap layout".to_owned()),
+ is_swap_layout_dirty: false,
+ },
+ TabInfo {
+ position: 1,
+ name: "Secondtab".to_owned(),
+ active: false,
+ panes_to_hide: 5,
+ is_fullscreen_active: false,
+ is_sync_panes_active: true,
+ are_floating_panes_visible: true,
+ other_focused_clients: vec![1, 5, 111],
+ active_swap_layout_name: None,
+ is_swap_layout_dirty: true,
+ },
+ TabInfo::default(),
+ ];
+ let mut panes = HashMap::new();
+ let panes_list = vec![
+ PaneInfo {
+ id: 1,
+ is_plugin: false,
+ is_focused: true,
+ is_fullscreen: true,
+ is_floating: false,
+ is_suppressed: false,
+ title: "pane 1".to_owned(),
+ exited: false,
+ exit_status: None,
+ is_held: false,
+ pane_x: 0,
+ pane_content_x: 1,
+ pane_y: 0,
+ pane_content_y: 1,
+ pane_rows: 5,
+ pane_content_rows: 4,
+ pane_columns: 22,
+ pane_content_columns: 21,
+ cursor_coordinates_in_pane: Some((0, 0)),
+ terminal_command: Some("foo".to_owned()),
+ plugin_url: None,
+ is_selectable: true,
+ },
+ PaneInfo {
+ id: 1,
+ is_plugin: true,
+ is_focused: true,
+ is_fullscreen: true,
+ is_floating: false,
+ is_suppressed: false,
+ title: "pane 1".to_owned(),
+ exited: false,
+ exit_status: None,
+ is_held: false,
+ pane_x: 0,
+ pane_content_x: 1,
+ pane_y: 0,
+ pane_content_y: 1,
+ pane_rows: 5,
+ pane_content_rows: 4,
+ pane_columns: 22,
+ pane_content_columns: 21,
+ cursor_coordinates_in_pane: Some((0, 0)),
+ terminal_command: None,
+ plugin_url: Some("i_am_a_fake_plugin".to_owned()),
+ is_selectable: true,
+ },
+ ];
+ panes.insert(0, panes_list);
+ let session_info_1 = SessionInfo {
+ name: "session 1".to_owned(),
+ tabs: tab_infos,
+ panes: PaneManifest { panes },
+ connected_clients: 2,
+ is_current_session: true,
+ };
+ let session_info_2 = SessionInfo {
+ name: "session 2".to_owned(),
+ tabs: vec![],
+ panes: PaneManifest {
+ panes: HashMap::new(),
+ },
+ connected_clients: 0,
+ is_current_session: false,
+ };
+ let session_infos = vec![session_info_1, session_info_2];
+
+ let session_update_event = Event::SessionUpdate(session_infos);
+ let protobuf_event: ProtobufEvent = session_update_event.clone().try_into().unwrap();
+ let serialized_protobuf_event = protobuf_event.encode_to_vec();
+ let deserialized_protobuf_event: ProtobufEvent =
+ Message::decode(serialized_protobuf_event.as_slice()).unwrap();
+ let deserialized_event: Event = deserialized_protobuf_event.try_into().unwrap();
+ assert_eq!(
+ session_update_event, deserialized_event,
+ "Event properly serialized/deserialized without change"
+ );
+}
diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto
index 9ba8bb7f..69d4c97b 100644
--- a/zellij-utils/src/plugin_api/plugin_command.proto
+++ b/zellij-utils/src/plugin_api/plugin_command.proto
@@ -78,6 +78,7 @@ enum CommandName {
RenameTab = 64;
ReportCrash = 65;
RequestPluginPermissions = 66;
+ SwitchSession = 67;
}
message PluginCommand {
@@ -120,9 +121,17 @@ message PluginCommand {
IdAndNewName rename_tab_payload = 36;
string report_crash_payload = 37;
RequestPluginPermissionPayload request_plugin_permission_payload = 38;
+ SwitchSessionPayload switch_session_payload = 39;
}
}
+message SwitchSessionPayload {
+ optional string name = 1;
+ optional uint32 tab_position = 2;
+ optional uint32 pane_id = 3;
+ optional bool pane_id_is_plugin = 4;
+}
+
message RequestPluginPermissionPayload {
repeated plugin_permission.PermissionType permissions = 1;
}
diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs
index ff50a6a7..71289a14 100644
--- a/zellij-utils/src/plugin_api/plugin_command.rs
+++ b/zellij-utils/src/plugin_api/plugin_command.rs
@@ -5,13 +5,13 @@ pub use super::generated_api::api::{
OpenCommandPanePayload, OpenFilePayload, PaneIdAndShouldFloat,
PluginCommand as ProtobufPluginCommand, PluginMessagePayload,
RequestPluginPermissionPayload, ResizePayload, SetTimeoutPayload, SubscribePayload,
- SwitchTabToPayload, SwitchToModePayload, UnsubscribePayload,
+ SwitchSessionPayload, SwitchTabToPayload, SwitchToModePayload, UnsubscribePayload,
},
plugin_permission::PermissionType as ProtobufPermissionType,
resize::ResizeAction as ProtobufResizeAction,
};
-use crate::data::{PermissionType, PluginCommand};
+use crate::data::{ConnectToSession, PermissionType, PluginCommand};
use std::convert::TryFrom;
@@ -500,6 +500,23 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
},
_ => Err("Mismatched payload for RequestPluginPermission"),
},
+ Some(CommandName::SwitchSession) => match protobuf_plugin_command.payload {
+ Some(Payload::SwitchSessionPayload(payload)) => {
+ let pane_id = match (payload.pane_id, payload.pane_id_is_plugin) {
+ (Some(pane_id), Some(is_plugin)) => Some((pane_id, is_plugin)),
+ (None, None) => None,
+ _ => {
+ return Err("Malformed payload for SwitchSession, 'pane_id' and 'is_plugin' must be included together or not at all")
+ }
+ };
+ Ok(PluginCommand::SwitchSession(ConnectToSession {
+ name: payload.name,
+ tab_position: payload.tab_position.map(|p| p as usize),
+ pane_id,
+ }))
+ },
+ _ => Err("Mismatched payload for SwitchSession"),
+ },
None => Err("Unrecognized plugin command"),
}
}
@@ -846,6 +863,15 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
},
)),
}),
+ PluginCommand::SwitchSession(switch_to_session) => Ok(ProtobufPluginCommand {
+ name: CommandName::SwitchSession as i32,
+ payload: Some(Payload::SwitchSessionPayload(SwitchSessionPayload {
+ name: switch_to_session.name,
+ tab_position: switch_to_session.tab_position.map(|t| t as u32),
+ pane_id: switch_to_session.pane_id.map(|p| p.0),
+ pane_id_is_plugin: switch_to_session.pane_id.map(|p| p.1),
+ })),
+ }),
}
}
}
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap
index 7f681757..367866e0 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap
@@ -2489,6 +2489,29 @@ Config {
): [
Detach,
],
+ Char(
+ 'w',
+ ): [
+ LaunchOrFocusPlugin(
+ RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "session-manager",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {
+ "floating": "true",
+ },
+ ),
+ },
+ true,
+ ),
+ SwitchToMode(
+ Normal,
+ ),
+ ],
Alt(
Char(
'+',
@@ -3587,6 +3610,23 @@ Config {
),
},
PluginTag(
+ "session-manager",
+ ): PluginConfig {
+ path: "session-manager",
+ run: Pane(
+ None,
+ ),
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "session-manager",
+ ),
+ ),
+ userspace_configuration: PluginUserConfiguration(
+ {},
+ ),
+ },
+ PluginTag(
"status-bar",
): PluginConfig {
path: "status-bar",
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap
index f8cbc413..5af23a0a 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap
@@ -2489,6 +2489,29 @@ Config {
): [
Detach,
],
+ Char(
+ 'w',
+ ): [
+ LaunchOrFocusPlugin(
+ RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "session-manager",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {
+ "floating": "true",
+ },
+ ),
+ },
+ true,
+ ),
+ SwitchToMode(
+ Normal,
+ ),
+ ],
Alt(
Char(
'+',
@@ -3587,6 +3610,23 @@ Config {
),
},
PluginTag(
+ "session-manager",
+ ): PluginConfig {
+ path: "session-manager",
+ run: Pane(
+ None,
+ ),
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "session-manager",
+ ),
+ ),
+ userspace_configuration: PluginUserConfiguration(
+ {},
+ ),
+ },
+ PluginTag(
"status-bar",
): PluginConfig {
path: "status-bar",
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap
index bb06be46..0a7f74b7 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap
@@ -103,6 +103,23 @@ Config {
),
},
PluginTag(
+ "session-manager",
+ ): PluginConfig {
+ path: "session-manager",
+ run: Pane(
+ None,
+ ),
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "session-manager",
+ ),
+ ),
+ userspace_configuration: PluginUserConfiguration(
+ {},
+ ),
+ },
+ PluginTag(
"status-bar",
): PluginConfig {
path: "status-bar",
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap
index 43501663..55173c3a 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap
@@ -2489,6 +2489,29 @@ Config {
): [
Detach,
],
+ Char(
+ 'w',
+ ): [
+ LaunchOrFocusPlugin(
+ RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "session-manager",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {
+ "floating": "true",
+ },
+ ),
+ },
+ true,
+ ),
+ SwitchToMode(
+ Normal,
+ ),
+ ],
Alt(
Char(
'+',
@@ -3587,6 +3610,23 @@ Config {
),
},
PluginTag(
+ "session-manager",
+ ): PluginConfig {
+ path: "session-manager",
+ run: Pane(
+ None,
+ ),
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "session-manager",
+ ),
+ ),
+ userspace_configuration: PluginUserConfiguration(
+ {},
+ ),
+ },
+ PluginTag(
"some-other-plugin",
): PluginConfig {
path: "i-am-defined-in-the-layout",
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap
index 4159c525..a7613eee 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap
@@ -2489,6 +2489,29 @@ Config {
): [
Detach,
],
+ Char(
+ 'w',
+ ): [
+ LaunchOrFocusPlugin(
+ RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "session-manager",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {
+ "floating": "true",
+ },
+ ),
+ },
+ true,
+ ),
+ SwitchToMode(
+ Normal,
+ ),
+ ],
Alt(
Char(
'+',
@@ -3891,6 +3914,23 @@ Config {
),
},
PluginTag(
+ "session-manager",
+ ): PluginConfig {
+ path: "session-manager",
+ run: Pane(
+ None,
+ ),
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "session-manager",
+ ),
+ ),
+ userspace_configuration: PluginUserConfiguration(
+ {},
+ ),
+ },
+ PluginTag(
"status-bar",
): PluginConfig {
path: "status-bar",
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap
index ebf3c501..dae91816 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap
@@ -2489,6 +2489,29 @@ Config {
): [
Detach,
],
+ Char(
+ 'w',
+ ): [
+ LaunchOrFocusPlugin(
+ RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "session-manager",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {
+ "floating": "true",
+ },
+ ),
+ },
+ true,
+ ),
+ SwitchToMode(
+ Normal,
+ ),
+ ],
Alt(
Char(
'+',
@@ -3587,6 +3610,23 @@ Config {
),
},
PluginTag(
+ "session-manager",
+ ): PluginConfig {
+ path: "session-manager",
+ run: Pane(
+ None,
+ ),
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "session-manager",
+ ),
+ ),
+ userspace_configuration: PluginUserConfiguration(
+ {},
+ ),
+ },
+ PluginTag(
"status-bar",
): PluginConfig {
path: "status-bar",