diff --git a/Cargo.lock b/Cargo.lock index 918d8a87b5..9fda1060a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2868,6 +2868,17 @@ dependencies = [ "yaml-rust", ] +[[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" @@ -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 65b4f04af5..4d74bfadef 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 0000000000..bc255e30b9 --- /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 0000000000..ea8c4bf7f3 --- /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 0000000000..edd9db576d --- /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 0000000000..c2030f9610 --- /dev/null +++ b/default-plugins/session-manager/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "session-manager" +version = "0.1.0" +authors = ["Aram Drevekenin "] +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 0000000000..66ff54c4f8 --- /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, + sessions: SessionList, + selected_index: Option, + search_term: String, + new_session_name: Option, + colors: Colors, +} + +register_plugin!(State); + +impl ZellijPlugin for State { + fn load(&mut self, _configuration: BTreeMap) { + 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) { + let session_infos: Vec = 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 0000000000..6cf6f71637 --- /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, + pub selected_index: SelectedIndex, + pub selected_search_index: Option, + pub search_results: Vec, + pub is_searching: bool, +} + +impl SessionList { + pub fn set_sessions(&mut self, mut session_ui_infos: Vec) { + 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, 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 { + 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 { + 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, pub Option, pub Option); + +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 { + 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, + list_item: ListItem, + session_name: String, + tab_position: Option, + pane_id: Option<(u32, bool)>, + is_current_session: bool, +} + +impl SearchResult { + pub fn new( + score: i64, + indices: Vec, + list_item: ListItem, + session_name: String, + tab_position: Option, + 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 { + 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 0000000000..6b606589ab --- /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>, + pub tab_name: Option>, + pub pane_name: Option>, + 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>, max_cols: usize) -> Vec { + 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)>, + 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)>, + 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); + +impl UiSpanTelescope { + pub fn new(string_and_lengths: Vec) -> 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 { + 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!(" ")), 18), + StringAndLength::new(colors.orange(&format!(" ")), 10), + StringAndLength::new(colors.orange(&format!(" ")), 4), + ])); + ui_spans.push(current_session_indication); + } + ui_spans +} + +pub fn build_tab_ui_line(tab_ui_info: &TabUiInfo, colors: Colors) -> Vec { + 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 { + 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) { + // 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, is_searching: bool, colors: Colors) { + if is_searching { + return; + } + let new_session_shortcut_text = ""; + let new_session_shortcut = colors.magenta(new_session_shortcut_text); + let new_session = colors.bold("New session"); + let enter = colors.magenta(""); + 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(""); + let select = colors.bold("Switch to selected"); + let esc = colors.magenta(""); + 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 0000000000..94a1a8a722 --- /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 { + 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 { + 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 { + 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, + to_render_after_selected: &mut Vec, + 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, + to_render_after_selected: &mut Vec, + 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, + to_render_after_selected: &mut Vec, + 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, + 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, + 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, + 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 6e5608e10e..5ab188d69f 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 = 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 c6e75118e2..1f76fb4e72 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 3efe6a7225..af977c7962 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 fb58db2756..8dbae4f0ab 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), + SwitchSession(ConnectToSession), } impl From for ClientInstruction { @@ -60,6 +61,9 @@ impl From 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), StartedParsing, DoneParsing, + Exit, } pub fn start_client( @@ -139,8 +145,12 @@ pub fn start_client( config_options: Options, info: ClientInfo, layout: Option, -) { + tab_position_to_focus: Option, + pane_id_to_focus: Option<(u32, bool)>, // (pane_id, is_plugin) +) -> Option { 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); - - spawn_server(&*ZELLIJ_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()), + envs::set_session_name(name.clone()); + os_input.update_session_name(name); + let ipc_pipe = create_ipc_pipe(); + + 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()), + ), + 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 - ); - - 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(); + 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); + + reconnect_to_session } #[cfg(test)] diff --git a/zellij-client/src/os_input_output.rs b/zellij-client/src/os_input_output.rs index 192c2159d7..212ed2f7d2 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>>, send_instructions_to_server: Arc>>>, receive_instructions_from_server: Arc>>>, + reading_from_stdin: Arc>>>, + session_name: Arc>>, } /// 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; fn get_stdin_reader(&self) -> Box; + fn update_session_name(&mut self, new_session_name: String); /// Returns the raw contents of standard input. - fn read_from_stdin(&mut self) -> Vec; + fn read_from_stdin(&mut self) -> Result, &'static str>; /// Returns a [`Box`] pointer to this [`ClientOsApi`] struct. fn box_clone(&self) -> Box; /// Sends a message to the server. @@ -135,14 +138,46 @@ impl ClientOsApi for ClientOsInputOutput { fn box_clone(&self) -> Box { Box::new((*self).clone()) } - fn read_from_stdin(&mut self) -> Vec { - 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, &'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 { let stdout = ::std::io::stdout(); @@ -258,19 +293,25 @@ impl Clone for Box { pub fn get_client_os_input() -> Result { 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 { 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 01f1ab2327..eae0958ba5 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 6bdd489bc9..31c7fe0750 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 { unimplemented!() } - fn read_from_stdin(&mut self) -> Vec { - self.stdin_buffer.drain(..).collect() + fn update_session_name(&mut self, new_session_name: String) {} + fn read_from_stdin(&mut self) -> Result, &'static str> { + Ok(self.stdin_buffer.drain(..).collect()) } fn box_clone(&self) -> Box { unimplemented!() diff --git a/zellij-server/src/background_jobs.rs b/zellij-server/src/background_jobs.rs index 8061b4a47f..67bd62c165 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, 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) -> Result<()> { let err_context = || "failed to write to pty".to_string(); let mut running_jobs: HashMap = HashMap::new(); let mut loading_plugins: HashMap> = HashMap::new(); // u32 - plugin_id + let current_session_name = Arc::new(Mutex::new(String::default())); + let current_session_info = Arc::new(Mutex::new(SessionInfo::default())); loop { let (event, mut err_ctx) = bus.recv().with_context(err_context)?; @@ -93,10 +108,89 @@ pub(crate) fn background_jobs_main(bus: Bus) -> 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(¤t_session_name); + let current_session_info = current_session_info.lock().unwrap().clone(); + let _wrote_file = + std::fs::create_dir_all(ZELLIJ_SESSION_INFO_CACHE_DIR.as_path()) + .and_then(|_| std::fs::File::create(cache_file_name)) + .and_then(|mut f| { + write!(f, "{}", current_session_info.to_string()) + }); + // 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, + ¤t_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(¤t_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 53a08f6565..0b5f2ea8d2 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), - AttachClient(ClientAttributes, Options, ClientId), + AttachClient( + ClientAttributes, + Options, + Option, // tab position to focus + Option<(u32, bool)>, // (pane_id, is_plugin) => pane_id to focus + ClientId, + ), ConnStatus(ClientId), ActiveClients(ClientId), Log(Vec, 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, 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, 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, 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 = 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 d63fac5621..6cafad7d39 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 736c16bc95..e85911b4ff 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 e995a07b92..222b14c659 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 c3b12bd15e..8e2a7570d0 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 5c3c7c0d2c..fdc8f3890e 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 f2effa19a0..16943dbb0f 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, + tab_position: Option, + 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 603233ff1c..26b551866b 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 456bea0985..2c018dcf3b 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, // 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, Option, ClientId), BreakPaneRight(ClientId), BreakPaneLeft(ClientId), + UpdateSessionInfos(BTreeMap), // 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 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> { 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 = 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 = 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 { 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, + ) -> 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 fb6d4e170f..2da5306a9d 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 { @@ -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 6204767adc..2ae6770db5 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, + 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 32dfb86e07..aea41f2741 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 9e2112cf9c..20af974ea6 100755 Binary files a/zellij-utils/assets/plugins/compact-bar.wasm and b/zellij-utils/assets/plugins/compact-bar.wasm differ diff --git a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm index ff13cefd08..f3cdb15d00 100755 Binary files a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm and b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm differ diff --git a/zellij-utils/assets/plugins/session-manager.wasm b/zellij-utils/assets/plugins/session-manager.wasm new file mode 100755 index 0000000000..673af58abb Binary files /dev/null and b/zellij-utils/assets/plugins/session-manager.wasm differ diff --git a/zellij-utils/assets/plugins/status-bar.wasm b/zellij-utils/assets/plugins/status-bar.wasm index e18d4edf9c..e91ee3eb39 100755 Binary files a/zellij-utils/assets/plugins/status-bar.wasm and b/zellij-utils/assets/plugins/status-bar.wasm differ diff --git a/zellij-utils/assets/plugins/strider.wasm b/zellij-utils/assets/plugins/strider.wasm index 3aff9cc30e..c85bd48a35 100755 Binary files a/zellij-utils/assets/plugins/strider.wasm and b/zellij-utils/assets/plugins/strider.wasm differ diff --git a/zellij-utils/assets/plugins/tab-bar.wasm b/zellij-utils/assets/plugins/tab-bar.wasm index ee53ba180a..2591e06751 100755 Binary files a/zellij-utils/assets/plugins/tab-bar.wasm and b/zellij-utils/assets/plugins/tab-bar.wasm differ diff --git a/zellij-utils/src/consts.rs b/zellij-utils/src/consts.rs index a2c3a42a50..43990b4baf 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 a08e642f91..3191913614 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -495,6 +495,7 @@ pub enum Event { FileSystemDelete(Vec), /// A Result of plugin permission request PermissionRequestResult(PermissionStatus), + SessionUpdate(Vec), } #[derive( @@ -734,6 +735,42 @@ impl ModeInfo { } } +#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct SessionInfo { + pub name: String, + pub tabs: Vec, + 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(&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) { + 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, + pub tab_position: Option, + 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), + SwitchSession(ConnectToSession), } diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index 306db448c2..677cbc420e 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 1755fe1e66..9ef7182ade 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, + permissions_to_check: &Vec, ) -> 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 149a157ae4..0f3415209f 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), TerminalPixelDimensions(PixelDimensions), BackgroundColor(String), @@ -88,7 +78,12 @@ pub enum ClientToServerMsg { Box, Option, ), - AttachClient(ClientAttributes, Options), + AttachClient( + ClientAttributes, + Options, + Option, // tab position to focus + Option<(u32, bool)>, // (pane_id, is_plugin) => pane id to focus + ), Action(Action, Option), 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),*/ Render(String), UnblockInputThread, Exit(ExitReason), @@ -110,6 +101,7 @@ pub enum ServerToClientMsg { Connected, ActiveClients(Vec), Log(Vec), + SwitchSession(ConnectToSession), } #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs index 13a03ad15d..af49dbb309 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, ) -> Result, ConfigError> { - // pub fn kdl_arguments_that_are_strings <'a>(arguments: impl Iterator) -> Result, ConfigError> { let mut args: Vec = 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 { + 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 = 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 { + 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> = 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, 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 0000000000..4a231c37b4 --- /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 0000000000..99e3e03fe6 --- /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 3a9838fa80..4d5b805d4e 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 6115fe1e82..9b0e98860b 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 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 = 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 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 for ProtobufSessionManifest { + type Error = &'static str; + fn try_from(session_info: SessionInfo) -> Result { + 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 for SessionInfo { + type Error = &'static str; + fn try_from(protobuf_session_manifest: ProtobufSessionManifest) -> Result { + let mut pane_manifest: HashMap> = 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 for EventType { ProtobufEventType::FileSystemUpdate => EventType::FileSystemUpdate, ProtobufEventType::FileSystemDelete => EventType::FileSystemDelete, ProtobufEventType::PermissionRequestResult => EventType::PermissionRequestResult, + ProtobufEventType::SessionUpdate => EventType::SessionUpdate, }) } } @@ -721,6 +807,7 @@ impl TryFrom 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 9ba8bb7f48..69d4c97bd6 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 ff50a6a767..71289a1497 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 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 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 7f681757df..367866e090 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( '+', @@ -3586,6 +3609,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 { 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 f8cbc41322..5af23a0a5b 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( '+', @@ -3586,6 +3609,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 { 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 bb06be461b..0a7f74b72c 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 @@ -102,6 +102,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 { 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 435016634a..55173c3ab5 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( '+', @@ -3586,6 +3609,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 { 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 4159c5254f..a7613eee18 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( '+', @@ -3890,6 +3913,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 { 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 ebf3c501e2..dae918163b 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( '+', @@ -3586,6 +3609,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 {