diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 2443c060..744bfdb0 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -16,10 +16,18 @@ jobs: steps: - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Add WASM target run: rustup target add wasm32-wasi - name: Install cargo-make - run: cargo install --debug cargo-make + run: test -x "${HOME}/.cargo/bin/cargo-make" || cargo install --debug cargo-make - name: Build run: cargo make build - name: Test @@ -31,8 +39,16 @@ jobs: steps: - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Install cargo-make - run: cargo install --debug cargo-make + run: test -x "${HOME}/.cargo/bin/cargo-make" || cargo install --debug cargo-make - name: Check Format run: cargo make check-format @@ -42,7 +58,15 @@ jobs: steps: - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Install cargo-make - run: cargo install --debug cargo-make + run: test -x "${HOME}/.cargo/bin/cargo-make" || cargo install --debug cargo-make - name: Check Lints run: cargo make clippy -D clippy::all diff --git a/.gitignore b/.gitignore index 54137021..433d02d7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ .vscode .vim .DS_Store -/assets/man/zellij.1 \ No newline at end of file +/assets/man/zellij.1 +**/target \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c925429..c6c83044 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] +* Remove unused imports (https://github.com/zellij-org/zellij/pull/504) +* More Infrastructure changes for the upcoming session detach feature: run server and client in separate processes (https://github.com/zellij-org/zellij/pull/499) +* Restructuring cargo workspace: Separate client, server and utils into separate crates (https://github.com/zellij-org/zellij/pull/515) +* Terminal compatibility: handle most OSC sequences (https://github.com/zellij-org/zellij/pull/517) +* Split `layout` flag into `layout` and `layout-path` (https://github.com/zellij-org/zellij/pull/514) +* Fix behaviour of the `clean` flag (https://github.com/zellij-org/zellij/pull/519) +* Make distinction clearer between certain configuration flags (https://github.com/zellij-org/zellij/pull/529) +* Resource usage and performance improvements (https://github.com/zellij-org/zellij/pull/523) +* Feature: Detachable/Persistent sessions (https://github.com/zellij-org/zellij/pull/531) + +## [0.11.0] - 2021-05-15 + +This version is mostly an installation hotfix. + * Add `check` flag to `setup` subcommand, move `generate-completions` subcommand to `setup` flag (https://github.com/zellij-org/zellij/pull/503) +* Change the asset installation from an opt-in to an opt-out (https://github.com/zellij-org/zellij/pull/512) ## [0.10.0] - 2021-05-14 * Change Switch default config loading order of `HOME` and system (https://github.com/zellij-org/zellij/pull/488) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b8efe437..9841e3fc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,9 +18,6 @@ To build Zellij, we're using cargo-make – you can install it by running `cargo install --force cargo-make`. To edit a manpage mandown crate (`cargo install mandown`) is used and the work is done on a markdown file in docs/MANPAGE.md. -Zellij has a hard dependency on a package from `x11` most likely called -`libX11`, or similarly on your system. - Here are some of the commands currently supported by the build system: ```sh @@ -44,8 +41,8 @@ cargo make publish cargo make manpage ``` -To run `install` or `publish`, you'll need `binaryen --version` > 97, for it's -command `wasm-opt`. +To run `install` or `publish`, you'll need the package `binaryen` in the +version `wasm-opt --version` > 97, for it's command `wasm-opt`. ## Looking for something to work on? diff --git a/Cargo.lock b/Cargo.lock index 39c1dad2..596062e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,7 +132,7 @@ dependencies = [ "event-listener", "futures-lite", "once_cell", - "signal-hook", + "signal-hook 0.3.8", "winapi", ] @@ -170,6 +170,17 @@ version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" +[[package]] +name = "async-trait" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic-waker" version = "1.0.0" @@ -222,18 +233,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -[[package]] -name = "bitvec" -version = "0.19.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "blocking" version = "1.0.2" @@ -248,6 +247,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "boxfnonce" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5988cb1d626264ac94100be357308f29ff7cbdd3b36bda27f450a4ee3f713426" + [[package]] name = "bumpalo" version = "3.6.1" @@ -457,6 +462,31 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crossterm" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c36c10130df424b2f3552fcc2ddcd9b28a27b1e54b358b45874f88d1ca6888c" +dependencies = [ + "bitflags", + "crossterm_winapi", + "lazy_static", + "libc", + "mio", + "parking_lot", + "signal-hook 0.1.17", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da8964ace4d3e4a044fd027919b2237000b24315a37c916f61809f1ff2140b9" +dependencies = [ + "winapi", +] + [[package]] name = "ctor" version = "0.1.20" @@ -467,6 +497,16 @@ dependencies = [ "syn", ] +[[package]] +name = "daemonize" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70c24513e34f53b640819f0ac9f705b673fcf4006d7aab8778bee72ebfc89815" +dependencies = [ + "boxfnonce", + "libc", +] + [[package]] name = "darling" version = "0.12.3" @@ -604,12 +644,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -[[package]] -name = "funty" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" - [[package]] name = "futures" version = "0.3.14" @@ -934,19 +968,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" -[[package]] -name = "lexical-core" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21f866863575d0e1d654fbeeabdc927292fdf862873dc3c96c6f753357e13374" -dependencies = [ - "arrayvec", - "bitflags", - "cfg-if 1.0.0", - "ryu", - "static_assertions", -] - [[package]] name = "libc" version = "0.2.93" @@ -971,9 +992,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "lock_api" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176" +checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" dependencies = [ "scopeguard", ] @@ -1031,6 +1052,28 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mio" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + [[package]] name = "more-asserts" version = "0.2.1" @@ -1059,16 +1102,12 @@ dependencies = [ ] [[package]] -name = "nom" -version = "6.1.2" +name = "ntapi" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" dependencies = [ - "bitvec", - "funty", - "lexical-core", - "memchr", - "version_check", + "winapi", ] [[package]] @@ -1115,6 +1154,31 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + [[package]] name = "pin-project-lite" version = "0.2.6" @@ -1210,12 +1274,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "radium" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" - [[package]] name = "rand" version = "0.3.23" @@ -1464,6 +1522,17 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "signal-hook" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729" +dependencies = [ + "libc", + "mio", + "signal-hook-registry", +] + [[package]] name = "signal-hook" version = "0.3.8" @@ -1526,12 +1595,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "status-bar" version = "0.1.0" @@ -1635,12 +1698,6 @@ dependencies = [ "zellij-tile-utils", ] -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "target-lexicon" version = "0.11.2" @@ -1661,6 +1718,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "termbg" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7fa67e879d2b5c902517367809bfab4e69ee6cabc03909141003721bd64282" +dependencies = [ + "crossterm", + "thiserror", + "winapi", +] + [[package]] name = "terminal_size" version = "0.1.16" @@ -1683,15 +1751,6 @@ dependencies = [ "redox_termios", ] -[[package]] -name = "termios" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" -dependencies = [ - "libc", -] - [[package]] name = "textwrap" version = "0.11.0" @@ -1794,15 +1853,6 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" -[[package]] -name = "unicode-truncate" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a04be5ca5f7a4a7270ffea82bc41c59b87c611ed04f20e77c338e8d3c2348e42" -dependencies = [ - "unicode-width", -] - [[package]] name = "unicode-width" version = "0.1.8" @@ -2237,12 +2287,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "wyz" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" - [[package]] name = "yaml-rust" version = "0.4.5" @@ -2254,43 +2298,41 @@ dependencies = [ [[package]] name = "zellij" -version = "0.11.0" +version = "0.12.0" +dependencies = [ + "insta", + "names", + "zellij-client", + "zellij-server", + "zellij-utils", +] + +[[package]] +name = "zellij-client" +version = "0.12.0" +dependencies = [ + "termbg", + "zellij-utils", +] + +[[package]] +name = "zellij-server" +version = "0.12.0" dependencies = [ "ansi_term 0.12.1", - "async-std", - "backtrace", - "bincode", - "colors-transform", - "directories-next", - "futures", + "async-trait", + "daemonize", "insta", - "interprocess", - "lazy_static", - "libc", - "names", - "nix", - "nom", - "serde", "serde_json", - "serde_yaml", - "signal-hook", - "strip-ansi-escapes", - "structopt", - "strum", - "tempfile", - "termion", - "termios", - "unicode-truncate", "unicode-width", - "vte 0.10.1", "wasmer", "wasmer-wasi", - "zellij-tile", + "zellij-utils", ] [[package]] name = "zellij-tile" -version = "0.11.0" +version = "0.12.0" dependencies = [ "serde", "serde_json", @@ -2300,7 +2342,33 @@ dependencies = [ [[package]] name = "zellij-tile-utils" -version = "0.11.0" +version = "0.12.0" dependencies = [ "ansi_term 0.12.1", ] + +[[package]] +name = "zellij-utils" +version = "0.12.0" +dependencies = [ + "async-std", + "backtrace", + "bincode", + "colors-transform", + "directories-next", + "interprocess", + "lazy_static", + "libc", + "nix", + "once_cell", + "serde", + "serde_yaml", + "signal-hook 0.3.8", + "strip-ansi-escapes", + "structopt", + "strum", + "tempfile", + "termion", + "vte 0.10.1", + "zellij-tile", +] diff --git a/Cargo.toml b/Cargo.toml index b2faa935..5f7afaaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zellij" -version = "0.11.0" +version = "0.12.0" authors = ["Aram Drevekenin "] edition = "2018" description = "A terminal workspace with batteries included" @@ -8,51 +8,27 @@ license = "MIT" repository = "https://github.com/zellij-org/zellij" homepage = "https://zellij.dev" include = ["src/**/*", "assets/plugins/*", "assets/layouts/*", "assets/config/*", "LICENSE.md", "README.md", "!**/*_test.*", "!**/tests/**/*"] +resolver = "2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ansi_term = "0.12.1" -backtrace = "0.3.55" -bincode = "1.3.1" -directories-next = "2.0" -futures = "0.3.5" -libc = "0.2" -nix = "0.19.1" -nom = "6.0.1" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -serde_yaml = "0.8" -signal-hook = "0.3" -strip-ansi-escapes = "0.1.0" -structopt = "0.3" -termion = "1.5.0" -termios = "0.3" -unicode-truncate = "0.2.0" -unicode-width = "0.1.8" -vte = "0.10.1" -strum = "0.20.0" -lazy_static = "1.4.0" -wasmer = "1.0.0" -wasmer-wasi = "1.0.0" -interprocess = "1.1.1" names = "0.11.0" -colors-transform = "0.2.5" -zellij-tile = { path = "zellij-tile/", version = "0.11.0" } - -[dependencies.async-std] -version = "1.3.0" -features = ["unstable"] +zellij-client = { path = "zellij-client/", version = "0.12.0" } +zellij-server = { path = "zellij-server/", version = "0.12.0" } +zellij-utils = { path = "zellij-utils/", version = "0.12.0" } [dev-dependencies] insta = "1.6.0" -tempfile = "3.2.0" - -[build-dependencies] -structopt = "0.3" +zellij-utils = { path = "zellij-utils/", version = "*", features = ["test"] } +zellij-client = { path = "zellij-client/", version = "*", features = ["test"] } +zellij-server = { path = "zellij-server/", version = "*", features = ["test"] } [workspace] members = [ + "zellij-client", + "zellij-server", + "zellij-utils", "zellij-tile", "zellij-tile-utils", "default-plugins/status-bar", @@ -80,5 +56,4 @@ assets = [ ] [features] -default = [ "enable_automatic_asset_installation", ] -enable_automatic_asset_installation = [] +disable_automatic_asset_installation = [] diff --git a/Makefile.toml b/Makefile.toml index 75f78b25..995a4e0d 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -24,6 +24,7 @@ env = { "SKIP_TEST" = true } [tasks.test] condition = { env_false = ["SKIP_TEST"] } dependencies = ["pre-test"] +args = ["test", "--", "@@split(CARGO_MAKE_TASK_ARGS,;)"] [tasks.post-test] env = { "SKIP_TEST" = false } @@ -37,6 +38,12 @@ run_task = "launch" [tasks.build-workspace] run_task = { name = "build", fork = true } +[tasks.build] +args = ["build"] + +[tasks.build-release] +args = ["build", "--release"] + [tasks.build-dev-data-dir] script_runner = "@duckscript" script = ''' diff --git a/README.md b/README.md index a1a41fe6..916f6720 100644 --- a/README.md +++ b/README.md @@ -36,15 +36,14 @@ cargo install zellij Or you can download a prebuilt binary from our [Releases](https://github.com/zellij-org/zellij/releases). -As the default plugins make use of characters that are mostly only found in [nerdfonts](https://www.nerdfonts.com/), -you get the best experience either with installing nerdfonts, or telling the plugins that you request a ui, that -does not rely on such characters with `zellij options --simplified-ui`, or putting `simplified_ui: true` in the -config file. +The default plugins make use of characters that are mostly found in [nerdfonts](https://www.nerdfonts.com/). +To get the best experience either install nerdfonts, or use the simplified ui by starting Zellij with `zellij options --simplified-ui`, or putting `simplified_ui: true` in the config file. ## How do I hack on it? (Contributing) * Clone the project * Install cargo-make with `cargo install --force cargo-make` * In the project folder, for debug builds run: `cargo make run` +* To run all tests: `cargo make test` For more build commands, see [`Contributing.md`](CONTRIBUTING.md). diff --git a/assets/config/default.yaml b/assets/config/default.yaml index 4582e79f..42bc1688 100644 --- a/assets/config/default.yaml +++ b/assets/config/default.yaml @@ -12,6 +12,8 @@ keybinds: key: [Ctrl: 't',] - action: [SwitchToMode: Scroll,] key: [Ctrl: 's',] + - action: [SwitchToMode: Session,] + key: [Ctrl: 'o',] - action: [Quit,] key: [Ctrl: 'q',] - action: [NewPane: ] @@ -42,6 +44,8 @@ keybinds: key: [Ctrl: 'r', Char: "\n", Char: ' ',] - action: [SwitchToMode: Scroll,] key: [Ctrl: 's'] + - action: [SwitchToMode: Session,] + key: [Ctrl: 'o',] - action: [Quit] key: [Ctrl: 'q'] - action: [Resize: Left,] @@ -77,6 +81,8 @@ keybinds: key: [Ctrl: 'p', Char: "\n", Char: ' ',] - action: [SwitchToMode: Scroll,] key: [Ctrl: 's'] + - action: [SwitchToMode: Session,] + key: [Ctrl: 'o',] - action: [Quit,] key: [Ctrl: 'q',] - action: [MoveFocus: Left,] @@ -114,6 +120,8 @@ keybinds: key: [Ctrl: 't', Char: "\n", Char: ' ',] - action: [SwitchToMode: Scroll,] key: [Ctrl: 's'] + - action: [SwitchToMode: Session,] + key: [Ctrl: 'o',] - action: [SwitchToMode: RenameTab, TabNameInput: [0],] key: [Char: 'r'] - action: [Quit,] @@ -168,6 +176,8 @@ keybinds: key: [Ctrl: 'g',] - action: [SwitchToMode: Pane,] key: [Ctrl: 'p',] + - action: [SwitchToMode: Session,] + key: [Ctrl: 'o',] - action: [Quit,] key: [Ctrl: 'q',] - action: [ScrollDown,] @@ -213,3 +223,20 @@ keybinds: key: [ Alt: '[',] - action: [FocusNextPane,] key: [ Alt: ']',] + session: + - action: [SwitchToMode: Locked,] + key: [Ctrl: 'g'] + - action: [SwitchToMode: Resize,] + key: [Ctrl: 'r',] + - action: [SwitchToMode: Pane,] + key: [Ctrl: 'p',] + - action: [SwitchToMode: Tab,] + key: [Ctrl: 't',] + - action: [SwitchToMode: Normal,] + key: [Ctrl: 'o', Char: "\n", Char: ' ',] + - action: [SwitchToMode: Scroll,] + key: [Ctrl: 's'] + - action: [Quit,] + key: [Ctrl: 'q',] + - action: [Detach,] + key: [Char: 'd',] diff --git a/assets/plugins/status-bar.wasm b/assets/plugins/status-bar.wasm index 47c2b6f2..8ca71ac6 100644 Binary files a/assets/plugins/status-bar.wasm and b/assets/plugins/status-bar.wasm differ diff --git a/assets/plugins/strider.wasm b/assets/plugins/strider.wasm index 80421c0d..f687db8d 100644 Binary files a/assets/plugins/strider.wasm and b/assets/plugins/strider.wasm differ diff --git a/assets/plugins/tab-bar.wasm b/assets/plugins/tab-bar.wasm index 3487df71..bb981a5b 100644 Binary files a/assets/plugins/tab-bar.wasm and b/assets/plugins/tab-bar.wasm differ diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 37175e3f..7a678cdc 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -22,6 +22,7 @@ enum CtrlKeyAction { Resize, Scroll, Quit, + Session, } enum CtrlKeyMode { @@ -39,16 +40,7 @@ impl CtrlKeyShortcut { CtrlKeyAction::Resize => String::from("RESIZE"), CtrlKeyAction::Scroll => String::from("SCROLL"), CtrlKeyAction::Quit => String::from("QUIT"), - } - } - pub fn shortened_text(&self) -> String { - match self.action { - CtrlKeyAction::Lock => String::from("LOCK"), - CtrlKeyAction::Pane => String::from("ane"), - CtrlKeyAction::Tab => String::from("ab"), - CtrlKeyAction::Resize => String::from("esize"), - CtrlKeyAction::Scroll => String::from("croll"), - CtrlKeyAction::Quit => String::from("uit"), + CtrlKeyAction::Session => String::from("SESSION"), } } pub fn letter_shortcut(&self) -> char { @@ -59,6 +51,7 @@ impl CtrlKeyShortcut { CtrlKeyAction::Resize => 'r', CtrlKeyAction::Scroll => 's', CtrlKeyAction::Quit => 'q', + CtrlKeyAction::Session => 'o', } } } @@ -193,32 +186,6 @@ fn full_ctrl_key(key: &CtrlKeyShortcut, palette: ColoredElements, separator: &st } } -fn shortened_ctrl_key( - key: &CtrlKeyShortcut, - palette: ColoredElements, - separator: &str, -) -> LinePart { - let shortened_text = key.shortened_text(); - let letter_shortcut = key.letter_shortcut(); - let shortened_text = match key.action { - CtrlKeyAction::Lock => format!(" {}", shortened_text), - _ => shortened_text, - }; - match key.mode { - CtrlKeyMode::Unselected => { - unselected_mode_shortcut(letter_shortcut, &shortened_text, palette, separator) - } - CtrlKeyMode::Selected => { - selected_mode_shortcut(letter_shortcut, &shortened_text, palette, separator) - } - CtrlKeyMode::Disabled => disabled_mode_shortcut( - &format!(" <{}>{}", letter_shortcut, shortened_text), - palette, - separator, - ), - } -} - fn single_letter_ctrl_key( key: &CtrlKeyShortcut, palette: ColoredElements, @@ -254,15 +221,6 @@ fn key_indicators( return line_part; } line_part = LinePart::default(); - for ctrl_key in keys { - let key = shortened_ctrl_key(ctrl_key, palette, separator); - line_part.part = format!("{}{}", line_part.part, key.part); - line_part.len += key.len; - } - if line_part.len < max_len { - return line_part; - } - line_part = LinePart::default(); for ctrl_key in keys { let key = single_letter_ctrl_key(ctrl_key, palette, separator); line_part.part = format!("{}{}", line_part.part, key.part); @@ -296,6 +254,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Quit), ], colored_elements, @@ -309,6 +268,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, @@ -322,6 +282,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, @@ -335,6 +296,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, @@ -348,6 +310,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, @@ -361,6 +324,21 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), + ], + colored_elements, + separator, + ), + InputMode::Session => key_indicators( + max_len, + &[ + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Pane), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index bb56c279..cf9445d6 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -72,55 +72,61 @@ pub struct ColoredElements { // that can be defined in the config perhaps fn color_elements(palette: Palette) -> ColoredElements { match palette.source { + // "cyan" here is used as a background as a dirty hack + // this is because the Palette struct doesn't have a "gray" section + // and we can't use its "bg" because that is now dynamically taken from the terminal + // and might often not actually fit the rest of the colorscheme + // + // to fix this, we need to restructure the Palette struct PaletteSource::Default => ColoredElements { - selected_prefix_separator: style!(palette.bg, palette.green), + selected_prefix_separator: style!(palette.cyan, palette.green), selected_char_left_separator: style!(palette.black, palette.green).bold(), selected_char_shortcut: style!(palette.red, palette.green).bold(), selected_char_right_separator: style!(palette.black, palette.green).bold(), selected_styled_text: style!(palette.black, palette.green).bold(), - selected_suffix_separator: style!(palette.green, palette.bg).bold(), - unselected_prefix_separator: style!(palette.bg, palette.fg), + selected_suffix_separator: style!(palette.green, palette.cyan).bold(), + unselected_prefix_separator: style!(palette.cyan, palette.fg), unselected_char_left_separator: style!(palette.black, palette.fg).bold(), unselected_char_shortcut: style!(palette.red, palette.fg).bold(), unselected_char_right_separator: style!(palette.black, palette.fg).bold(), unselected_styled_text: style!(palette.black, palette.fg).bold(), - unselected_suffix_separator: style!(palette.fg, palette.bg), - disabled_prefix_separator: style!(palette.bg, palette.fg), - disabled_styled_text: style!(palette.bg, palette.fg).dimmed(), - disabled_suffix_separator: style!(palette.fg, palette.bg), - selected_single_letter_prefix_separator: style!(palette.bg, palette.green), + unselected_suffix_separator: style!(palette.fg, palette.cyan), + disabled_prefix_separator: style!(palette.cyan, palette.fg), + disabled_styled_text: style!(palette.cyan, palette.fg).dimmed(), + disabled_suffix_separator: style!(palette.fg, palette.cyan), + selected_single_letter_prefix_separator: style!(palette.cyan, palette.green), selected_single_letter_char_shortcut: style!(palette.red, palette.green).bold(), - selected_single_letter_suffix_separator: style!(palette.green, palette.bg), - unselected_single_letter_prefix_separator: style!(palette.bg, palette.fg), + selected_single_letter_suffix_separator: style!(palette.green, palette.cyan), + unselected_single_letter_prefix_separator: style!(palette.cyan, palette.fg), unselected_single_letter_char_shortcut: style!(palette.red, palette.fg).bold(), - unselected_single_letter_suffix_separator: style!(palette.fg, palette.bg), - superkey_prefix: style!(palette.white, palette.bg).bold(), - superkey_suffix_separator: style!(palette.bg, palette.bg), + unselected_single_letter_suffix_separator: style!(palette.fg, palette.cyan), + superkey_prefix: style!(palette.white, palette.cyan).bold(), + superkey_suffix_separator: style!(palette.cyan, palette.cyan), }, PaletteSource::Xresources => ColoredElements { - selected_prefix_separator: style!(palette.bg, palette.green), + selected_prefix_separator: style!(palette.cyan, palette.green), selected_char_left_separator: style!(palette.fg, palette.green).bold(), selected_char_shortcut: style!(palette.red, palette.green).bold(), selected_char_right_separator: style!(palette.fg, palette.green).bold(), - selected_styled_text: style!(palette.bg, palette.green).bold(), - selected_suffix_separator: style!(palette.green, palette.bg).bold(), - unselected_prefix_separator: style!(palette.bg, palette.fg), - unselected_char_left_separator: style!(palette.bg, palette.fg).bold(), + selected_styled_text: style!(palette.cyan, palette.green).bold(), + selected_suffix_separator: style!(palette.green, palette.cyan).bold(), + unselected_prefix_separator: style!(palette.cyan, palette.fg), + unselected_char_left_separator: style!(palette.cyan, palette.fg).bold(), unselected_char_shortcut: style!(palette.red, palette.fg).bold(), - unselected_char_right_separator: style!(palette.bg, palette.fg).bold(), - unselected_styled_text: style!(palette.bg, palette.fg).bold(), - unselected_suffix_separator: style!(palette.fg, palette.bg), - disabled_prefix_separator: style!(palette.bg, palette.fg), - disabled_styled_text: style!(palette.bg, palette.fg).dimmed(), - disabled_suffix_separator: style!(palette.fg, palette.bg), + unselected_char_right_separator: style!(palette.cyan, palette.fg).bold(), + unselected_styled_text: style!(palette.cyan, palette.fg).bold(), + unselected_suffix_separator: style!(palette.fg, palette.cyan), + disabled_prefix_separator: style!(palette.cyan, palette.fg), + disabled_styled_text: style!(palette.cyan, palette.fg).dimmed(), + disabled_suffix_separator: style!(palette.fg, palette.cyan), selected_single_letter_prefix_separator: style!(palette.fg, palette.green), selected_single_letter_char_shortcut: style!(palette.red, palette.green).bold(), selected_single_letter_suffix_separator: style!(palette.green, palette.fg), - unselected_single_letter_prefix_separator: style!(palette.fg, palette.bg), + unselected_single_letter_prefix_separator: style!(palette.fg, palette.cyan), unselected_single_letter_char_shortcut: style!(palette.red, palette.fg).bold(), - unselected_single_letter_suffix_separator: style!(palette.fg, palette.bg), - superkey_prefix: style!(palette.bg, palette.fg).bold(), - superkey_suffix_separator: style!(palette.fg, palette.bg), + unselected_single_letter_suffix_separator: style!(palette.fg, palette.cyan), + superkey_prefix: style!(palette.cyan, palette.fg).bold(), + superkey_suffix_separator: style!(palette.fg, palette.cyan), }, } } @@ -155,7 +161,7 @@ impl ZellijPlugin for State { // [48;5;238m is gray background, [0K is so that it fills the rest of the line // [m is background reset, [0K is so that it clears the rest of the line - match self.mode_info.palette.bg { + match self.mode_info.palette.cyan { PaletteColor::Rgb((r, g, b)) => { println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", first_line, r, g, b); } diff --git a/default-plugins/tab-bar/src/line.rs b/default-plugins/tab-bar/src/line.rs index 574edb78..fce8982d 100644 --- a/default-plugins/tab-bar/src/line.rs +++ b/default-plugins/tab-bar/src/line.rs @@ -62,11 +62,11 @@ fn left_more_message(tab_count_to_the_left: usize, palette: Palette, separator: }; // 238 let more_text_len = more_text.chars().count() + 2; // 2 for the arrows - let left_separator = style!(palette.bg, palette.orange).paint(separator); + let left_separator = style!(palette.cyan, palette.orange).paint(separator); let more_styled_text = style!(palette.black, palette.orange) .bold() .paint(more_text); - let right_separator = style!(palette.orange, palette.bg).paint(separator); + let right_separator = style!(palette.orange, palette.cyan).paint(separator); let more_styled_text = format!( "{}", ANSIStrings(&[left_separator, more_styled_text, right_separator,]) @@ -94,11 +94,11 @@ fn right_more_message( " +many → ".to_string() }; let more_text_len = more_text.chars().count() + 1; // 2 for the arrow - let left_separator = style!(palette.bg, palette.orange).paint(separator); + let left_separator = style!(palette.cyan, palette.orange).paint(separator); let more_styled_text = style!(palette.black, palette.orange) .bold() .paint(more_text); - let right_separator = style!(palette.orange, palette.bg).paint(separator); + let right_separator = style!(palette.orange, palette.cyan).paint(separator); let more_styled_text = format!( "{}", ANSIStrings(&[left_separator, more_styled_text, right_separator,]) @@ -147,7 +147,9 @@ fn add_next_tabs_msg( fn tab_line_prefix(palette: Palette) -> LinePart { let prefix_text = " Zellij ".to_string(); let prefix_text_len = prefix_text.chars().count(); - let prefix_styled_text = style!(palette.white, palette.bg).bold().paint(prefix_text); + let prefix_styled_text = style!(palette.white, palette.cyan) + .bold() + .paint(prefix_text); LinePart { part: format!("{}", prefix_styled_text), len: prefix_text_len, diff --git a/default-plugins/tab-bar/src/main.rs b/default-plugins/tab-bar/src/main.rs index 00ff4bd9..46336be3 100644 --- a/default-plugins/tab-bar/src/main.rs +++ b/default-plugins/tab-bar/src/main.rs @@ -75,7 +75,7 @@ impl ZellijPlugin for State { for bar_part in tab_line { s = format!("{}{}", s, bar_part.part); } - match self.mode_info.palette.bg { + match self.mode_info.palette.cyan { PaletteColor::Rgb((r, g, b)) => { println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", s, r, g, b); } diff --git a/default-plugins/tab-bar/src/tab.rs b/default-plugins/tab-bar/src/tab.rs index 85e2c378..fb8e377d 100644 --- a/default-plugins/tab-bar/src/tab.rs +++ b/default-plugins/tab-bar/src/tab.rs @@ -4,12 +4,12 @@ use zellij_tile::prelude::*; use zellij_tile_utils::style; pub fn active_tab(text: String, palette: Palette, separator: &str) -> LinePart { - let left_separator = style!(palette.bg, palette.green).paint(separator); + let left_separator = style!(palette.cyan, palette.green).paint(separator); let tab_text_len = text.chars().count() + 4; // 2 for left and right separators, 2 for the text padding let tab_styled_text = style!(palette.black, palette.green) .bold() .paint(format!(" {} ", text)); - let right_separator = style!(palette.green, palette.bg).paint(separator); + let right_separator = style!(palette.green, palette.cyan).paint(separator); let tab_styled_text = format!( "{}", ANSIStrings(&[left_separator, tab_styled_text, right_separator,]) @@ -21,12 +21,12 @@ pub fn active_tab(text: String, palette: Palette, separator: &str) -> LinePart { } pub fn non_active_tab(text: String, palette: Palette, separator: &str) -> LinePart { - let left_separator = style!(palette.bg, palette.fg).paint(separator); + let left_separator = style!(palette.cyan, palette.fg).paint(separator); let tab_text_len = text.chars().count() + 4; // 2 for left and right separators, 2 for the padding let tab_styled_text = style!(palette.black, palette.fg) .bold() .paint(format!(" {} ", text)); - let right_separator = style!(palette.fg, palette.bg).paint(separator); + let right_separator = style!(palette.fg, palette.cyan).paint(separator); let tab_styled_text = format!( "{}", ANSIStrings(&[left_separator, tab_styled_text, right_separator,]) diff --git a/src/cli.rs b/src/cli.rs deleted file mode 100644 index 69496808..00000000 --- a/src/cli.rs +++ /dev/null @@ -1,47 +0,0 @@ -use super::common::utils::consts::{ZELLIJ_CONFIG_DIR_ENV, ZELLIJ_CONFIG_FILE_ENV}; -use crate::common::input::options::Options; -use crate::common::setup::Setup; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -use structopt::StructOpt; - -#[derive(StructOpt, Default, Debug, Clone, Serialize, Deserialize)] -#[structopt(name = "zellij")] -pub struct CliArgs { - /// Maximum panes on screen, caution: opening more panes will close old ones - #[structopt(long)] - pub max_panes: Option, - - /// Change where zellij looks for layouts and plugins - #[structopt(long)] - pub data_dir: Option, - - /// Path to a layout yaml file - #[structopt(short, long)] - pub layout: Option, - - /// Change where zellij looks for the configuration - #[structopt(short, long, env=ZELLIJ_CONFIG_FILE_ENV)] - pub config: Option, - - /// Change where zellij looks for the configuration - #[structopt(long, env=ZELLIJ_CONFIG_DIR_ENV)] - pub config_dir: Option, - - #[structopt(subcommand)] - pub option: Option, - - #[structopt(short, long)] - pub debug: bool, -} - -#[derive(Debug, StructOpt, Clone, Serialize, Deserialize)] -pub enum ConfigCli { - /// Change the behaviour of zellij - #[structopt(name = "options")] - Options(Options), - - /// Setup zellij and check its configuration - #[structopt(name = "setup")] - Setup(Setup), -} diff --git a/src/client/mod.rs b/src/client/mod.rs deleted file mode 100644 index 8d83b626..00000000 --- a/src/client/mod.rs +++ /dev/null @@ -1,195 +0,0 @@ -pub mod boundaries; -pub mod layout; -pub mod pane_resizer; -pub mod panes; -pub mod tab; - -use serde::{Deserialize, Serialize}; -use std::io::Write; -use std::sync::mpsc; -use std::thread; - -use crate::cli::CliArgs; -use crate::common::{ - command_is_executing::CommandIsExecuting, - errors::{ClientContext, ContextType}, - input::config::Config, - input::handler::input_loop, - input::options::Options, - os_input_output::ClientOsApi, - thread_bus::{SenderType, SenderWithContext, SyncChannelWithContext}, -}; -use crate::server::ServerInstruction; -use zellij_tile::data::InputMode; - -/// Instructions related to the client-side application and sent from server to client -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum ClientInstruction { - Error(String), - Render(Option), - UnblockInputThread, - Exit, -} - -pub fn start_client(mut os_input: Box, opts: CliArgs, config: Config) { - let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}12l\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"; - os_input.unset_raw_mode(0); - let _ = os_input - .get_stdout_writer() - .write(take_snapshot.as_bytes()) - .unwrap(); - let _ = os_input - .get_stdout_writer() - .write(clear_client_terminal_attributes.as_bytes()) - .unwrap(); - std::env::set_var(&"ZELLIJ", "0"); - - let mut command_is_executing = CommandIsExecuting::new(); - - let config_options = Options::from_cli(&config.options, opts.option.clone()); - - let full_screen_ws = os_input.get_terminal_size_using_fd(0); - os_input.connect_to_server(); - os_input.send_to_server(ServerInstruction::NewClient( - full_screen_ws, - opts, - config_options.clone(), - )); - os_input.set_raw_mode(0); - let _ = os_input - .get_stdout_writer() - .write(bracketed_paste.as_bytes()) - .unwrap(); - - let (send_client_instructions, receive_client_instructions): SyncChannelWithContext< - ClientInstruction, - > = mpsc::sync_channel(50); - let send_client_instructions = - SenderWithContext::new(SenderType::SyncSender(send_client_instructions)); - - #[cfg(not(test))] - std::panic::set_hook({ - use crate::errors::handle_panic; - let send_client_instructions = send_client_instructions.clone(); - Box::new(move |info| { - handle_panic(info, &send_client_instructions); - }) - }); - - let _stdin_thread = thread::Builder::new() - .name("stdin_handler".to_string()) - .spawn({ - let send_client_instructions = send_client_instructions.clone(); - let command_is_executing = command_is_executing.clone(); - let os_input = os_input.clone(); - let default_mode = config_options.default_mode.unwrap_or(InputMode::Normal); - move || { - input_loop( - os_input, - config, - command_is_executing, - send_client_instructions, - default_mode, - ) - } - }); - - let _signal_thread = thread::Builder::new() - .name("signal_listener".to_string()) - .spawn({ - let os_input = os_input.clone(); - move || { - os_input.receive_sigwinch(Box::new({ - let os_api = os_input.clone(); - move || { - os_api.send_to_server(ServerInstruction::TerminalResize( - os_api.get_terminal_size_using_fd(0), - )); - } - })); - } - }) - .unwrap(); - - let router_thread = thread::Builder::new() - .name("router".to_string()) - .spawn({ - let os_input = os_input.clone(); - move || { - loop { - let (instruction, mut err_ctx) = os_input.recv_from_server(); - err_ctx.add_call(ContextType::Client(ClientContext::from(&instruction))); - if let ClientInstruction::Exit = instruction { - break; - } - send_client_instructions.send(instruction).unwrap(); - } - send_client_instructions - .send(ClientInstruction::Exit) - .unwrap(); - } - }) - .unwrap(); - - #[warn(clippy::never_loop)] - loop { - let (client_instruction, mut err_ctx) = receive_client_instructions - .recv() - .expect("failed to receive app instruction on channel"); - - err_ctx.add_call(ContextType::Client(ClientContext::from( - &client_instruction, - ))); - match client_instruction { - ClientInstruction::Exit => break, - ClientInstruction::Error(backtrace) => { - let _ = os_input.send_to_server(ServerInstruction::ClientExit); - os_input.unset_raw_mode(0); - let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1); - let restore_snapshot = "\u{1b}[?1049l"; - let error = format!( - "{}\n{}{}", - goto_start_of_last_line, restore_snapshot, backtrace - ); - let _ = os_input - .get_stdout_writer() - .write(error.as_bytes()) - .unwrap(); - std::process::exit(1); - } - ClientInstruction::Render(output) => { - if output.is_none() { - break; - } - let mut stdout = os_input.get_stdout_writer(); - stdout - .write_all(&output.unwrap().as_bytes()) - .expect("cannot write to stdout"); - stdout.flush().expect("could not flush"); - } - ClientInstruction::UnblockInputThread => { - command_is_executing.unblock_input_thread(); - } - } - } - - let _ = os_input.send_to_server(ServerInstruction::ClientExit); - 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{}{}{}Bye from Zellij!\n", - goto_start_of_last_line, restore_snapshot, reset_style, show_cursor - ); - - os_input.unset_raw_mode(0); - let mut stdout = os_input.get_stdout_writer(); - let _ = stdout.write(goodbye_message.as_bytes()).unwrap(); - stdout.flush().unwrap(); -} diff --git a/src/common/input/mod.rs b/src/common/input/mod.rs deleted file mode 100644 index f30ccf23..00000000 --- a/src/common/input/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! The way terminal input is handled. - -pub mod actions; -pub mod config; -pub mod handler; -pub mod keybinds; -pub mod options; diff --git a/src/common/mod.rs b/src/common/mod.rs deleted file mode 100644 index c91e1e05..00000000 --- a/src/common/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub mod command_is_executing; -pub mod errors; -pub mod input; -pub mod ipc; -pub mod os_input_output; -pub mod pty; -pub mod screen; -pub mod setup; -pub mod thread_bus; -pub mod utils; -pub mod wasm_vm; - -use crate::panes::PaneId; -use crate::server::ServerInstruction; diff --git a/src/common/os_input_output.rs b/src/common/os_input_output.rs deleted file mode 100644 index 6f891e0d..00000000 --- a/src/common/os_input_output.rs +++ /dev/null @@ -1,411 +0,0 @@ -use crate::client::ClientInstruction; -use crate::common::{ - ipc::{IpcReceiverWithContext, IpcSenderWithContext}, - utils::consts::ZELLIJ_IPC_PIPE, -}; -use crate::errors::ErrorContext; -use crate::panes::PositionAndSize; -use crate::server::ServerInstruction; -use crate::utils::shared::default_palette; -use interprocess::local_socket::LocalSocketStream; -use nix::fcntl::{fcntl, FcntlArg, OFlag}; -use nix::pty::{forkpty, Winsize}; -use nix::sys::signal::{kill, Signal}; -use nix::sys::termios; -use nix::sys::wait::waitpid; -use nix::unistd::{self, ForkResult, Pid}; -use signal_hook::{consts::signal::*, iterator::Signals}; -use std::io::prelude::*; -use std::os::unix::{fs::PermissionsExt, io::RawFd}; -use std::path::{Path, PathBuf}; -use std::process::{Child, Command}; -use std::sync::{Arc, Mutex}; -use std::{env, fs, io}; -use zellij_tile::data::Palette; - -const UNIX_PERMISSIONS: u32 = 0o700; - -pub fn set_permissions(path: &Path) -> io::Result<()> { - let mut permissions = fs::metadata(path)?.permissions(); - permissions.set_mode(UNIX_PERMISSIONS); - fs::set_permissions(path, permissions) -} - -fn into_raw_mode(pid: RawFd) { - let mut tio = termios::tcgetattr(pid).expect("could not get terminal attribute"); - termios::cfmakeraw(&mut tio); - match termios::tcsetattr(pid, termios::SetArg::TCSANOW, &tio) { - Ok(_) => {} - Err(e) => panic!("error {:?}", e), - }; -} - -fn unset_raw_mode(pid: RawFd, orig_termios: termios::Termios) { - match termios::tcsetattr(pid, termios::SetArg::TCSANOW, &orig_termios) { - Ok(_) => {} - Err(e) => panic!("error {:?}", e), - }; -} - -pub fn get_terminal_size_using_fd(fd: RawFd) -> PositionAndSize { - // TODO: do this with the nix ioctl - use libc::ioctl; - use libc::TIOCGWINSZ; - - let mut winsize = Winsize { - ws_row: 0, - ws_col: 0, - ws_xpixel: 0, - ws_ypixel: 0, - }; - - unsafe { ioctl(fd, TIOCGWINSZ, &mut winsize) }; - PositionAndSize::from(winsize) -} - -pub fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) { - // TODO: do this with the nix ioctl - use libc::ioctl; - use libc::TIOCSWINSZ; - - let winsize = Winsize { - ws_col: columns, - ws_row: rows, - ws_xpixel: 0, - ws_ypixel: 0, - }; - unsafe { ioctl(fd, TIOCSWINSZ, &winsize) }; -} - -/// Handle some signals for the child process. This will loop until the child -/// process exits. -fn handle_command_exit(mut child: Child) { - // register the SIGINT signal (TODO handle more signals) - let mut signals = ::signal_hook::iterator::Signals::new(&[SIGINT]).unwrap(); - 'handle_exit: loop { - // test whether the child process has exited - match child.try_wait() { - Ok(Some(_status)) => { - // if the child process has exited, break outside of the loop - // and exit this function - // TODO: handle errors? - break 'handle_exit; - } - Ok(None) => { - ::std::thread::sleep(::std::time::Duration::from_millis(100)); - } - Err(e) => panic!("error attempting to wait: {}", e), - } - - for signal in signals.pending() { - if let SIGINT = signal { - child.kill().unwrap(); - child.wait().unwrap(); - break 'handle_exit; - } - } - } -} - -/// Spawns a new terminal from the parent terminal with [`termios`](termios::Termios) -/// `orig_termios`. -/// -/// If a `file_to_open` is given, the text editor specified by environment variable `EDITOR` -/// (or `VISUAL`, if `EDITOR` is not set) will be started in the new terminal, with the given -/// file open. If no file is given, the shell specified by environment variable `SHELL` will -/// be started in the new terminal. -/// -/// # Panics -/// -/// This function will panic if both the `EDITOR` and `VISUAL` environment variables are not -/// set. -// FIXME this should probably be split into different functions, or at least have less levels -// of indentation in some way -fn spawn_terminal(file_to_open: Option, orig_termios: termios::Termios) -> (RawFd, RawFd) { - let (pid_primary, pid_secondary): (RawFd, RawFd) = { - match forkpty(None, Some(&orig_termios)) { - Ok(fork_pty_res) => { - let pid_primary = fork_pty_res.master; - let pid_secondary = match fork_pty_res.fork_result { - ForkResult::Parent { child } => { - // fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::empty())).expect("could not fcntl"); - fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)) - .expect("could not fcntl"); - child - } - ForkResult::Child => match file_to_open { - Some(file_to_open) => { - if env::var("EDITOR").is_err() && env::var("VISUAL").is_err() { - panic!("Can't edit files if an editor is not defined. To fix: define the EDITOR or VISUAL environment variables with the path to your editor (eg. /usr/bin/vim)"); - } - let editor = - env::var("EDITOR").unwrap_or_else(|_| env::var("VISUAL").unwrap()); - - let child = Command::new(editor) - .args(&[file_to_open]) - .spawn() - .expect("failed to spawn"); - handle_command_exit(child); - ::std::process::exit(0); - } - None => { - let child = Command::new(env::var("SHELL").unwrap()) - .spawn() - .expect("failed to spawn"); - handle_command_exit(child); - ::std::process::exit(0); - } - }, - }; - (pid_primary, pid_secondary.as_raw()) - } - Err(e) => { - panic!("failed to fork {:?}", e); - } - } - }; - (pid_primary, pid_secondary) -} - -#[derive(Clone)] -pub struct ServerOsInputOutput { - orig_termios: Arc>, - receive_instructions_from_client: Option>>>, - send_instructions_to_client: Arc>>>, -} - -/// The `ServerOsApi` trait represents an abstract interface to the features of an operating system that -/// Zellij server requires. -pub trait ServerOsApi: Send + Sync { - /// Sets the size of the terminal associated to file descriptor `fd`. - fn set_terminal_size_using_fd(&mut self, fd: RawFd, cols: u16, rows: u16); - /// Spawn a new terminal, with an optional file to open in a terminal program. - fn spawn_terminal(&mut self, file_to_open: Option) -> (RawFd, RawFd); - /// Read bytes from the standard output of the virtual terminal referred to by `fd`. - fn read_from_tty_stdout(&mut self, fd: RawFd, buf: &mut [u8]) -> Result; - /// Write bytes to the standard input of the virtual terminal referred to by `fd`. - fn write_to_tty_stdin(&mut self, fd: RawFd, buf: &mut [u8]) -> Result; - /// Wait until all output written to the object referred to by `fd` has been transmitted. - fn tcdrain(&mut self, fd: RawFd) -> Result<(), nix::Error>; - /// Terminate the process with process ID `pid`. - // FIXME `RawFd` is semantically the wrong type here. It should either be a raw libc::pid_t, - // or a nix::unistd::Pid. See `man kill.3`, nix::sys::signal::kill (both take an argument - // called `pid` and of type `pid_t`, and not `fd`) - fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error>; - /// Returns a [`Box`] pointer to this [`ServerOsApi`] struct. - fn box_clone(&self) -> Box; - /// Receives a message on server-side IPC channel - fn recv_from_client(&self) -> (ServerInstruction, ErrorContext); - /// Sends a message to client - fn send_to_client(&self, msg: ClientInstruction); - /// Adds a sender to client - fn add_client_sender(&mut self); - /// Update the receiver socket for the client - fn update_receiver(&mut self, stream: LocalSocketStream); - fn load_palette(&self) -> Palette; -} - -impl ServerOsApi for ServerOsInputOutput { - fn set_terminal_size_using_fd(&mut self, fd: RawFd, cols: u16, rows: u16) { - set_terminal_size_using_fd(fd, cols, rows); - } - fn spawn_terminal(&mut self, file_to_open: Option) -> (RawFd, RawFd) { - let orig_termios = self.orig_termios.lock().unwrap(); - spawn_terminal(file_to_open, orig_termios.clone()) - } - fn read_from_tty_stdout(&mut self, fd: RawFd, buf: &mut [u8]) -> Result { - unistd::read(fd, buf) - } - fn write_to_tty_stdin(&mut self, fd: RawFd, buf: &mut [u8]) -> Result { - unistd::write(fd, buf) - } - fn tcdrain(&mut self, fd: RawFd) -> Result<(), nix::Error> { - termios::tcdrain(fd) - } - fn box_clone(&self) -> Box { - Box::new((*self).clone()) - } - fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error> { - // TODO: - // Ideally, we should be using SIGINT rather than SIGKILL here, but there are cases in which - // the terminal we're trying to kill hangs on SIGINT and so all the app gets stuck - // that's why we're sending SIGKILL here - // A better solution would be to send SIGINT here and not wait for it, and then have - // a background thread do the waitpid stuff and send SIGKILL if the process is stuck - kill(Pid::from_raw(pid), Some(Signal::SIGKILL)).unwrap(); - waitpid(Pid::from_raw(pid), None).unwrap(); - Ok(()) - } - fn recv_from_client(&self) -> (ServerInstruction, ErrorContext) { - self.receive_instructions_from_client - .as_ref() - .unwrap() - .lock() - .unwrap() - .recv() - } - fn send_to_client(&self, msg: ClientInstruction) { - self.send_instructions_to_client - .lock() - .unwrap() - .as_mut() - .unwrap() - .send(msg); - } - fn add_client_sender(&mut self) { - assert!(self.send_instructions_to_client.lock().unwrap().is_none()); - let sender = self - .receive_instructions_from_client - .as_ref() - .unwrap() - .lock() - .unwrap() - .get_sender(); - *self.send_instructions_to_client.lock().unwrap() = Some(sender); - } - fn update_receiver(&mut self, stream: LocalSocketStream) { - self.receive_instructions_from_client = - Some(Arc::new(Mutex::new(IpcReceiverWithContext::new(stream)))); - } - fn load_palette(&self) -> Palette { - default_palette() - } -} - -impl Clone for Box { - fn clone(&self) -> Box { - self.box_clone() - } -} - -pub fn get_server_os_input() -> ServerOsInputOutput { - let current_termios = termios::tcgetattr(0).unwrap(); - let orig_termios = Arc::new(Mutex::new(current_termios)); - ServerOsInputOutput { - orig_termios, - receive_instructions_from_client: None, - send_instructions_to_client: Arc::new(Mutex::new(None)), - } -} - -#[derive(Clone)] -pub struct ClientOsInputOutput { - orig_termios: Arc>, - send_instructions_to_server: Arc>>>, - receive_instructions_from_server: Arc>>>, -} - -/// The `ClientOsApi` trait represents an abstract interface to the features of an operating system that -/// Zellij client requires. -pub trait ClientOsApi: Send + Sync { - /// Returns the size of the terminal associated to file descriptor `fd`. - fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize; - /// Set the terminal associated to file descriptor `fd` to - /// [raw mode](https://en.wikipedia.org/wiki/Terminal_mode). - fn set_raw_mode(&mut self, fd: RawFd); - /// Set the terminal associated to file descriptor `fd` to - /// [cooked mode](https://en.wikipedia.org/wiki/Terminal_mode). - fn unset_raw_mode(&mut self, fd: RawFd); - /// Returns the writer that allows writing to standard output. - fn get_stdout_writer(&self) -> Box; - /// Returns the raw contents of standard input. - fn read_from_stdin(&self) -> Vec; - /// Returns a [`Box`] pointer to this [`ClientOsApi`] struct. - fn box_clone(&self) -> Box; - /// Sends a message to the server. - fn send_to_server(&self, msg: ServerInstruction); - /// Receives a message on client-side IPC channel - // This should be called from the client-side router thread only. - fn recv_from_server(&self) -> (ClientInstruction, ErrorContext); - fn receive_sigwinch(&self, cb: Box); - /// Establish a connection with the server socket. - fn connect_to_server(&self); -} - -impl ClientOsApi for ClientOsInputOutput { - fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize { - get_terminal_size_using_fd(fd) - } - fn set_raw_mode(&mut self, fd: RawFd) { - into_raw_mode(fd); - } - fn unset_raw_mode(&mut self, fd: RawFd) { - let orig_termios = self.orig_termios.lock().unwrap(); - unset_raw_mode(fd, orig_termios.clone()); - } - fn box_clone(&self) -> Box { - Box::new((*self).clone()) - } - fn read_from_stdin(&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 get_stdout_writer(&self) -> Box { - let stdout = ::std::io::stdout(); - Box::new(stdout) - } - fn send_to_server(&self, msg: ServerInstruction) { - self.send_instructions_to_server - .lock() - .unwrap() - .as_mut() - .unwrap() - .send(msg); - } - fn recv_from_server(&self) -> (ClientInstruction, ErrorContext) { - self.receive_instructions_from_server - .lock() - .unwrap() - .as_mut() - .unwrap() - .recv() - } - fn receive_sigwinch(&self, cb: Box) { - let mut signals = Signals::new(&[SIGWINCH, SIGTERM, SIGINT, SIGQUIT]).unwrap(); - for signal in signals.forever() { - match signal { - SIGWINCH => { - cb(); - } - SIGTERM | SIGINT | SIGQUIT => { - break; - } - _ => unreachable!(), - } - } - } - fn connect_to_server(&self) { - let socket = match LocalSocketStream::connect(&**ZELLIJ_IPC_PIPE) { - Ok(sock) => sock, - Err(_) => { - std::thread::sleep(std::time::Duration::from_millis(20)); - LocalSocketStream::connect(&**ZELLIJ_IPC_PIPE).unwrap() - } - }; - let sender = IpcSenderWithContext::new(socket); - let receiver = sender.get_receiver(); - *self.send_instructions_to_server.lock().unwrap() = Some(sender); - *self.receive_instructions_from_server.lock().unwrap() = Some(receiver); - } -} - -impl Clone for Box { - fn clone(&self) -> Box { - self.box_clone() - } -} - -pub fn get_client_os_input() -> ClientOsInputOutput { - let current_termios = termios::tcgetattr(0).unwrap(); - let orig_termios = Arc::new(Mutex::new(current_termios)); - ClientOsInputOutput { - orig_termios, - send_instructions_to_server: Arc::new(Mutex::new(None)), - receive_instructions_from_server: Arc::new(Mutex::new(None)), - } -} diff --git a/src/common/thread_bus.rs b/src/common/thread_bus.rs deleted file mode 100644 index 8827d068..00000000 --- a/src/common/thread_bus.rs +++ /dev/null @@ -1,142 +0,0 @@ -//! Definitions and helpers for sending and receiving messages between threads. - -use async_std::task_local; -use std::cell::RefCell; -use std::sync::mpsc; - -use crate::common::pty::PtyInstruction; -use crate::common::ServerInstruction; -use crate::errors::{get_current_ctx, ErrorContext}; -use crate::os_input_output::ServerOsApi; -use crate::screen::ScreenInstruction; -use crate::wasm_vm::PluginInstruction; - -/// An [MPSC](mpsc) asynchronous channel with added error context. -pub type ChannelWithContext = ( - mpsc::Sender<(T, ErrorContext)>, - mpsc::Receiver<(T, ErrorContext)>, -); -/// An [MPSC](mpsc) synchronous channel with added error context. -pub type SyncChannelWithContext = ( - mpsc::SyncSender<(T, ErrorContext)>, - mpsc::Receiver<(T, ErrorContext)>, -); - -/// Wrappers around the two standard [MPSC](mpsc) sender types, [`mpsc::Sender`] and [`mpsc::SyncSender`], with an additional [`ErrorContext`]. -#[derive(Clone)] -pub enum SenderType { - /// A wrapper around an [`mpsc::Sender`], adding an [`ErrorContext`]. - Sender(mpsc::Sender<(T, ErrorContext)>), - /// A wrapper around an [`mpsc::SyncSender`], adding an [`ErrorContext`]. - SyncSender(mpsc::SyncSender<(T, ErrorContext)>), -} - -/// Sends messages on an [MPSC](std::sync::mpsc) channel, along with an [`ErrorContext`], -/// synchronously or asynchronously depending on the underlying [`SenderType`]. -#[derive(Clone)] -pub struct SenderWithContext { - sender: SenderType, -} - -impl SenderWithContext { - pub fn new(sender: SenderType) -> Self { - Self { sender } - } - - /// Sends an event, along with the current [`ErrorContext`], on this - /// [`SenderWithContext`]'s channel. - pub fn send(&self, event: T) -> Result<(), mpsc::SendError<(T, ErrorContext)>> { - let err_ctx = get_current_ctx(); - match self.sender { - SenderType::Sender(ref s) => s.send((event, err_ctx)), - SenderType::SyncSender(ref s) => s.send((event, err_ctx)), - } - } -} - -unsafe impl Send for SenderWithContext {} -unsafe impl Sync for SenderWithContext {} - -thread_local!( - /// A key to some thread local storage (TLS) that holds a representation of the thread's call - /// stack in the form of an [`ErrorContext`]. - pub static OPENCALLS: RefCell = RefCell::default() -); - -task_local! { - /// A key to some task local storage that holds a representation of the task's call - /// stack in the form of an [`ErrorContext`]. - pub static ASYNCOPENCALLS: RefCell = RefCell::default() -} - -/// A container for senders to the different threads in zellij on the server side -#[derive(Clone)] -pub struct ThreadSenders { - pub to_screen: Option>, - pub to_pty: Option>, - pub to_plugin: Option>, - pub to_server: Option>, -} - -impl ThreadSenders { - pub fn send_to_screen( - &self, - instruction: ScreenInstruction, - ) -> Result<(), mpsc::SendError<(ScreenInstruction, ErrorContext)>> { - self.to_screen.as_ref().unwrap().send(instruction) - } - - pub fn send_to_pty( - &self, - instruction: PtyInstruction, - ) -> Result<(), mpsc::SendError<(PtyInstruction, ErrorContext)>> { - self.to_pty.as_ref().unwrap().send(instruction) - } - - pub fn send_to_plugin( - &self, - instruction: PluginInstruction, - ) -> Result<(), mpsc::SendError<(PluginInstruction, ErrorContext)>> { - self.to_plugin.as_ref().unwrap().send(instruction) - } - - pub fn send_to_server( - &self, - instruction: ServerInstruction, - ) -> Result<(), mpsc::SendError<(ServerInstruction, ErrorContext)>> { - self.to_server.as_ref().unwrap().send(instruction) - } -} - -/// A container for a receiver, OS input and the senders to a given thread -pub struct Bus { - pub receiver: mpsc::Receiver<(T, ErrorContext)>, - pub senders: ThreadSenders, - pub os_input: Option>, -} - -impl Bus { - pub fn new( - receiver: mpsc::Receiver<(T, ErrorContext)>, - to_screen: Option<&SenderWithContext>, - to_pty: Option<&SenderWithContext>, - to_plugin: Option<&SenderWithContext>, - to_server: Option<&SenderWithContext>, - os_input: Option>, - ) -> Self { - Bus { - receiver, - senders: ThreadSenders { - to_screen: to_screen.cloned(), - to_pty: to_pty.cloned(), - to_plugin: to_plugin.cloned(), - to_server: to_server.cloned(), - }, - os_input: os_input.clone(), - } - } - - pub fn recv(&self) -> Result<(T, ErrorContext), mpsc::RecvError> { - self.receiver.recv() - } -} diff --git a/src/common/utils/mod.rs b/src/common/utils/mod.rs deleted file mode 100644 index 1cb4df27..00000000 --- a/src/common/utils/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Zellij utilities. - -pub mod consts; -pub mod logging; -pub mod shared; diff --git a/src/main.rs b/src/main.rs index c1f0f387..67b83398 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,63 +1,80 @@ -mod cli; -mod client; -mod common; -mod server; +mod sessions; #[cfg(test)] mod tests; -use client::{boundaries, layout, panes, start_client, tab}; -use common::{ - command_is_executing, errors, os_input_output, pty, screen, setup::Setup, utils, wasm_vm, -}; -use server::start_server; -use structopt::StructOpt; - -use crate::cli::CliArgs; -use crate::command_is_executing::CommandIsExecuting; -use crate::common::input::{config::Config, options::Options}; -use crate::os_input_output::{get_client_os_input, get_server_os_input, ClientOsApi, ServerOsApi}; -use crate::utils::{ - consts::{ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR}, - logging::*, -}; +use sessions::{assert_session, assert_session_ne, list_sessions}; use std::convert::TryFrom; +use std::process; +use zellij_client::{os_input_output::get_client_os_input, start_client, ClientInfo}; +use zellij_server::{os_input_output::get_server_os_input, start_server}; +use zellij_utils::{ + cli::{CliArgs, Command, Sessions}, + consts::{ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR}, + input::config::Config, + logging::*, + setup::Setup, + structopt::StructOpt, +}; pub fn main() { let opts = CliArgs::from_args(); - if let Some(crate::cli::ConfigCli::Setup(setup)) = opts.option.clone() { - Setup::from_cli(&setup, opts).expect("Failed to print to stdout"); - std::process::exit(0); - } else { - let config = match Config::try_from(&opts) { - Ok(config) => config, + if let Some(Command::Sessions(Sessions::ListSessions)) = opts.command { + list_sessions(); + } else if let Some(Command::Setup(ref setup)) = opts.command { + Setup::from_cli(setup, &opts).expect("Failed to print to stdout"); + } + + let config = match Config::try_from(&opts) { + Ok(config) => config, + Err(e) => { + eprintln!("There was an error in the config file:\n{}", e); + process::exit(1); + } + }; + atomic_create_dir(&*ZELLIJ_TMP_DIR).unwrap(); + atomic_create_dir(&*ZELLIJ_TMP_LOG_DIR).unwrap(); + if let Some(path) = opts.server { + let os_input = match get_server_os_input() { + Ok(server_os_input) => server_os_input, Err(e) => { - eprintln!("There was an error in the config file:\n{}", e); - std::process::exit(1); + eprintln!("failed to open terminal:\n{}", e); + process::exit(1); } }; - let config_options = Options::from_cli(&config.options, opts.option.clone()); - atomic_create_dir(&*ZELLIJ_TMP_DIR).unwrap(); - atomic_create_dir(&*ZELLIJ_TMP_LOG_DIR).unwrap(); - let server_os_input = get_server_os_input(); - let os_input = get_client_os_input(); - start( - Box::new(os_input), - opts, - Box::new(server_os_input), - config, - config_options, - ); + start_server(Box::new(os_input), path); + } else { + let os_input = match get_client_os_input() { + Ok(os_input) => os_input, + Err(e) => { + eprintln!("failed to open terminal:\n{}", e); + process::exit(1); + } + }; + if let Some(Command::Sessions(Sessions::Attach { + session_name, + force, + })) = opts.command.clone() + { + assert_session(&session_name); + start_client( + Box::new(os_input), + opts, + config, + ClientInfo::Attach(session_name, force), + ); + } else { + let session_name = opts + .session + .clone() + .unwrap_or_else(|| names::Generator::default().next().unwrap()); + assert_session_ne(&session_name); + start_client( + Box::new(os_input), + opts, + config, + ClientInfo::New(session_name), + ); + } } } -pub fn start( - client_os_input: Box, - opts: CliArgs, - server_os_input: Box, - config: Config, - config_options: Options, -) { - let ipc_thread = start_server(server_os_input, config_options); - start_client(client_os_input, opts, config); - drop(ipc_thread.join()); -} diff --git a/src/server/mod.rs b/src/server/mod.rs deleted file mode 100644 index a4a79ca0..00000000 --- a/src/server/mod.rs +++ /dev/null @@ -1,269 +0,0 @@ -pub mod route; - -use interprocess::local_socket::LocalSocketListener; -use serde::{Deserialize, Serialize}; -use std::sync::{Arc, RwLock}; -use std::thread; -use std::{path::PathBuf, sync::mpsc::channel}; -use wasmer::Store; -use zellij_tile::data::PluginCapabilities; - -use crate::cli::CliArgs; -use crate::client::ClientInstruction; -use crate::common::thread_bus::{Bus, ThreadSenders}; -use crate::common::{ - errors::{ContextType, ServerContext}, - input::{actions::Action, options::Options}, - os_input_output::{set_permissions, ServerOsApi}, - pty::{pty_thread_main, Pty, PtyInstruction}, - screen::{screen_thread_main, ScreenInstruction}, - setup::{get_default_data_dir, install::populate_data_dir}, - thread_bus::{ChannelWithContext, SenderType, SenderWithContext}, - utils::consts::{ZELLIJ_IPC_PIPE, ZELLIJ_PROJ_DIR}, - wasm_vm::{wasm_thread_main, PluginInstruction}, -}; -use crate::layout::Layout; -use crate::panes::PositionAndSize; -use route::route_thread_main; - -/// Instructions related to server-side application including the -/// ones sent by client to server -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum ServerInstruction { - TerminalResize(PositionAndSize), - NewClient(PositionAndSize, CliArgs, Options), - Action(Action), - Render(Option), - UnblockInputThread, - ClientExit, -} - -pub struct SessionMetaData { - pub senders: ThreadSenders, - screen_thread: Option>, - pty_thread: Option>, - wasm_thread: Option>, -} - -impl Drop for SessionMetaData { - fn drop(&mut self) { - let _ = self.senders.send_to_pty(PtyInstruction::Exit); - let _ = self.senders.send_to_screen(ScreenInstruction::Exit); - let _ = self.senders.send_to_plugin(PluginInstruction::Exit); - let _ = self.screen_thread.take().unwrap().join(); - let _ = self.pty_thread.take().unwrap().join(); - let _ = self.wasm_thread.take().unwrap().join(); - } -} - -pub fn start_server( - os_input: Box, - config_options: Options, -) -> thread::JoinHandle<()> { - let (to_server, server_receiver): ChannelWithContext = channel(); - let to_server = SenderWithContext::new(SenderType::Sender(to_server)); - let sessions: Arc>> = Arc::new(RwLock::new(None)); - - #[cfg(test)] - thread::Builder::new() - .name("server_router".to_string()) - .spawn({ - let sessions = sessions.clone(); - let os_input = os_input.clone(); - let to_server = to_server.clone(); - let capabilities = PluginCapabilities { - arrow_fonts: !config_options.simplified_ui, - }; - - move || route_thread_main(sessions, os_input, to_server, capabilities) - }) - .unwrap(); - #[cfg(not(test))] - let _ = thread::Builder::new() - .name("server_listener".to_string()) - .spawn({ - let os_input = os_input.clone(); - let sessions = sessions.clone(); - let to_server = to_server.clone(); - let capabilities = PluginCapabilities { - arrow_fonts: config_options.simplified_ui, - }; - move || { - drop(std::fs::remove_file(&*ZELLIJ_IPC_PIPE)); - let listener = LocalSocketListener::bind(&**ZELLIJ_IPC_PIPE).unwrap(); - set_permissions(&*ZELLIJ_IPC_PIPE).unwrap(); - for stream in listener.incoming() { - match stream { - Ok(stream) => { - let mut os_input = os_input.clone(); - os_input.update_receiver(stream); - let sessions = sessions.clone(); - let to_server = to_server.clone(); - thread::Builder::new() - .name("server_router".to_string()) - .spawn({ - let sessions = sessions.clone(); - let os_input = os_input.clone(); - let to_server = to_server.clone(); - - move || { - route_thread_main( - sessions, - os_input, - to_server, - capabilities, - ) - } - }) - .unwrap(); - } - Err(err) => { - panic!("err {:?}", err); - } - } - } - } - }); - - thread::Builder::new() - .name("server_thread".to_string()) - .spawn({ - move || loop { - let (instruction, mut err_ctx) = server_receiver.recv().unwrap(); - err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction))); - match instruction { - ServerInstruction::NewClient(full_screen_ws, opts, config_options) => { - let session_data = init_session( - os_input.clone(), - opts, - config_options, - to_server.clone(), - full_screen_ws, - ); - *sessions.write().unwrap() = Some(session_data); - sessions - .read() - .unwrap() - .as_ref() - .unwrap() - .senders - .send_to_pty(PtyInstruction::NewTab) - .unwrap(); - } - ServerInstruction::UnblockInputThread => { - os_input.send_to_client(ClientInstruction::UnblockInputThread); - } - ServerInstruction::ClientExit => { - *sessions.write().unwrap() = None; - os_input.send_to_client(ClientInstruction::Exit); - drop(std::fs::remove_file(&*ZELLIJ_IPC_PIPE)); - break; - } - ServerInstruction::Render(output) => { - os_input.send_to_client(ClientInstruction::Render(output)) - } - _ => panic!("Received unexpected instruction."), - } - } - }) - .unwrap() -} - -fn init_session( - os_input: Box, - opts: CliArgs, - config_options: Options, - to_server: SenderWithContext, - full_screen_ws: PositionAndSize, -) -> SessionMetaData { - let (to_screen, screen_receiver): ChannelWithContext = channel(); - let to_screen = SenderWithContext::new(SenderType::Sender(to_screen)); - - let (to_plugin, plugin_receiver): ChannelWithContext = channel(); - let to_plugin = SenderWithContext::new(SenderType::Sender(to_plugin)); - let (to_pty, pty_receiver): ChannelWithContext = channel(); - let to_pty = SenderWithContext::new(SenderType::Sender(to_pty)); - - // Determine and initialize the data directory - let data_dir = opts.data_dir.unwrap_or_else(get_default_data_dir); - - #[cfg(enable_automatic_assets_installation)] - populate_data_dir(&data_dir); - - // Don't use default layouts in tests, but do everywhere else - #[cfg(not(test))] - let default_layout = Some(PathBuf::from("default")); - #[cfg(test)] - let default_layout = None; - let maybe_layout = opts - .layout - .map(|p| Layout::new(&p, &data_dir)) - .or_else(|| default_layout.map(|p| Layout::from_defaults(&p, &data_dir))); - - let pty_thread = thread::Builder::new() - .name("pty".to_string()) - .spawn({ - let pty = Pty::new( - Bus::new( - pty_receiver, - Some(&to_screen), - None, - Some(&to_plugin), - Some(&to_server), - Some(os_input.clone()), - ), - opts.debug, - ); - - move || pty_thread_main(pty, maybe_layout) - }) - .unwrap(); - - let screen_thread = thread::Builder::new() - .name("screen".to_string()) - .spawn({ - let screen_bus = Bus::new( - screen_receiver, - None, - Some(&to_pty), - Some(&to_plugin), - Some(&to_server), - Some(os_input.clone()), - ); - let max_panes = opts.max_panes; - let config_options = config_options; - - move || { - screen_thread_main(screen_bus, max_panes, full_screen_ws, config_options); - } - }) - .unwrap(); - - let wasm_thread = thread::Builder::new() - .name("wasm".to_string()) - .spawn({ - let plugin_bus = Bus::new( - plugin_receiver, - Some(&to_screen), - Some(&to_pty), - None, - None, - None, - ); - let store = Store::default(); - - move || wasm_thread_main(plugin_bus, store, data_dir) - }) - .unwrap(); - SessionMetaData { - senders: ThreadSenders { - to_screen: Some(to_screen), - to_pty: Some(to_pty), - to_plugin: Some(to_plugin), - to_server: None, - }, - screen_thread: Some(screen_thread), - pty_thread: Some(pty_thread), - wasm_thread: Some(wasm_thread), - } -} diff --git a/src/sessions.rs b/src/sessions.rs new file mode 100644 index 00000000..fd834e40 --- /dev/null +++ b/src/sessions.rs @@ -0,0 +1,109 @@ +use std::os::unix::fs::FileTypeExt; +use std::{fs, io, process}; +use zellij_utils::{ + consts::ZELLIJ_SOCK_DIR, + interprocess::local_socket::LocalSocketStream, + ipc::{ClientToServerMsg, IpcSenderWithContext}, +}; + +fn get_sessions() -> Result, io::ErrorKind> { + match fs::read_dir(&*ZELLIJ_SOCK_DIR) { + Ok(files) => { + let mut sessions = Vec::new(); + files.for_each(|file| { + let file = file.unwrap(); + let file_name = file.file_name().into_string().unwrap(); + if file.file_type().unwrap().is_socket() && assert_socket(&file_name) { + sessions.push(file_name); + } + }); + Ok(sessions) + } + Err(err) => { + if let io::ErrorKind::NotFound = err.kind() { + Ok(Vec::with_capacity(0)) + } else { + Err(err.kind()) + } + } + } +} + +pub(crate) fn list_sessions() { + let exit_code = match get_sessions() { + Ok(sessions) => { + if sessions.is_empty() { + println!("No active zellij sessions found."); + } else { + let curr_session = + std::env::var("ZELLIJ_SESSION_NAME").unwrap_or_else(|_| "".into()); + sessions.iter().for_each(|session| { + let suffix = if curr_session == *session { + " (current)" + } else { + "" + }; + println!("{}{}", session, suffix); + }) + } + 0 + } + Err(e) => { + eprintln!("Error occured: {:?}", e); + 1 + } + }; + process::exit(exit_code); +} + +pub(crate) fn assert_session(name: &str) { + let exit_code = match get_sessions() { + Ok(sessions) => { + if sessions.iter().any(|s| s == name) { + return; + } + println!("No session named {:?} found.", name); + 0 + } + Err(e) => { + eprintln!("Error occured: {:?}", e); + 1 + } + }; + process::exit(exit_code); +} + +pub(crate) fn assert_session_ne(name: &str) { + let exit_code = match get_sessions() { + Ok(sessions) => { + if sessions.iter().all(|s| s != name) { + return; + } + println!("Session with name {:?} aleady exists. Use attach command to connect to it or specify a different name.", name); + 0 + } + Err(e) => { + eprintln!("Error occured: {:?}", e); + 1 + } + }; + process::exit(exit_code); +} + +fn assert_socket(name: &str) -> bool { + let path = &*ZELLIJ_SOCK_DIR.join(name); + match LocalSocketStream::connect(path) { + Ok(stream) => { + IpcSenderWithContext::new(stream).send(ClientToServerMsg::ClientExited); + true + } + Err(e) => { + if e.kind() == io::ErrorKind::ConnectionRefused { + drop(fs::remove_file(path)); + false + } else { + true + } + } + } +} diff --git a/src/tests/fakes.rs b/src/tests/fakes.rs index eb0d14e8..23f9f182 100644 --- a/src/tests/fakes.rs +++ b/src/tests/fakes.rs @@ -1,5 +1,3 @@ -use crate::panes::PositionAndSize; -use interprocess::local_socket::LocalSocketStream; use std::collections::{HashMap, VecDeque}; use std::io::Write; use std::os::unix::io::RawFd; @@ -7,21 +5,28 @@ use std::path::PathBuf; use std::sync::{mpsc, Arc, Condvar, Mutex}; use std::time::{Duration, Instant}; -use crate::client::ClientInstruction; -use crate::common::thread_bus::{ChannelWithContext, SenderType, SenderWithContext}; -use crate::errors::ErrorContext; -use crate::os_input_output::{ClientOsApi, ServerOsApi}; -use crate::server::ServerInstruction; +use zellij_utils::{nix, zellij_tile}; + use crate::tests::possible_tty_inputs::{get_possible_tty_inputs, Bytes}; use crate::tests::utils::commands::{QUIT, SLEEP}; -use crate::utils::shared::default_palette; +use zellij_client::os_input_output::ClientOsApi; +use zellij_server::os_input_output::{async_trait, AsyncReader, Pid, ServerOsApi}; use zellij_tile::data::Palette; +use zellij_utils::{ + async_std, + channels::{ChannelWithContext, SenderType, SenderWithContext}, + errors::ErrorContext, + interprocess::local_socket::LocalSocketStream, + ipc::{ClientToServerMsg, ServerToClientMsg}, + pane_size::PositionAndSize, + shared::default_palette, +}; const MIN_TIME_BETWEEN_SNAPSHOTS: Duration = Duration::from_millis(150); #[derive(Clone)] pub enum IoEvent { - Kill(RawFd), + Kill(Pid), SetTerminalSizeUsingFd(RawFd, u16, u16), IntoRawMode(RawFd), UnsetRawMode(RawFd), @@ -77,10 +82,10 @@ pub struct FakeInputOutput { win_sizes: Arc>>, possible_tty_inputs: HashMap, last_snapshot_time: Arc>, - send_instructions_to_client: SenderWithContext, - receive_instructions_from_server: Arc>>, - send_instructions_to_server: SenderWithContext, - receive_instructions_from_client: Arc>>, + send_instructions_to_client: SenderWithContext, + receive_instructions_from_server: Arc>>, + send_instructions_to_server: SenderWithContext, + receive_instructions_from_client: Arc>>, should_trigger_sigwinch: Arc<(Mutex, Condvar)>, sigwinch_event: Option, } @@ -90,10 +95,10 @@ impl FakeInputOutput { let mut win_sizes = HashMap::new(); let last_snapshot_time = Arc::new(Mutex::new(Instant::now())); let stdout_writer = FakeStdoutWriter::new(last_snapshot_time.clone()); - let (client_sender, client_receiver): ChannelWithContext = + let (client_sender, client_receiver): ChannelWithContext = mpsc::channel(); let send_instructions_to_client = SenderWithContext::new(SenderType::Sender(client_sender)); - let (server_sender, server_receiver): ChannelWithContext = + let (server_sender, server_receiver): ChannelWithContext = mpsc::channel(); let send_instructions_to_server = SenderWithContext::new(SenderType::Sender(server_sender)); win_sizes.insert(0, winsize); // 0 is the current terminal @@ -126,7 +131,7 @@ impl FakeInputOutput { } self.stdin_commands = Arc::new(Mutex::new(stdin_commands)); } - pub fn add_terminal(&mut self, fd: RawFd) { + pub fn add_terminal(&self, fd: RawFd) { self.stdin_writes.lock().unwrap().insert(fd, vec![]); } pub fn add_sigwinch_event(&mut self, new_position_and_size: PositionAndSize) { @@ -153,7 +158,7 @@ impl ClientOsApi for FakeInputOutput { .unwrap() .push(IoEvent::IntoRawMode(pid)); } - fn unset_raw_mode(&mut self, pid: RawFd) { + fn unset_raw_mode(&self, pid: RawFd) { self.io_events .lock() .unwrap() @@ -195,17 +200,17 @@ impl ClientOsApi for FakeInputOutput { fn get_stdout_writer(&self) -> Box { Box::new(self.stdout_writer.clone()) } - fn send_to_server(&self, msg: ServerInstruction) { + fn send_to_server(&self, msg: ClientToServerMsg) { self.send_instructions_to_server.send(msg).unwrap(); } - fn recv_from_server(&self) -> (ClientInstruction, ErrorContext) { + fn recv_from_server(&self) -> (ServerToClientMsg, ErrorContext) { self.receive_instructions_from_server .lock() .unwrap() .recv() .unwrap() } - fn receive_sigwinch(&self, cb: Box) { + fn handle_signals(&self, sigwinch_cb: Box, _quit_cb: Box) { if self.sigwinch_event.is_some() { let (lock, cvar) = &*self.should_trigger_sigwinch; { @@ -214,14 +219,44 @@ impl ClientOsApi for FakeInputOutput { should_trigger_sigwinch = cvar.wait(should_trigger_sigwinch).unwrap(); } } - cb(); + sigwinch_cb(); + } + } + fn connect_to_server(&self, _path: &std::path::Path) {} + fn load_palette(&self) -> Palette { + default_palette() + } +} + +struct FakeAsyncReader { + fd: RawFd, + os_api: Box, +} + +#[async_trait] +impl AsyncReader for FakeAsyncReader { + async fn read(&mut self, buf: &mut [u8]) -> Result { + // simulates async semantics: EAGAIN is not propagated to caller + loop { + let res = self.os_api.read_from_tty_stdout(self.fd, buf); + match res { + Err(nix::Error::Sys(nix::errno::Errno::EAGAIN)) => { + async_std::task::sleep(Duration::from_millis(10)).await; + continue; + } + Err(e) => { + break Err(std::io::Error::from_raw_os_error( + e.as_errno().unwrap() as i32 + )) + } + Ok(n_bytes) => break Ok(n_bytes), + } } } - fn connect_to_server(&self) {} } impl ServerOsApi for FakeInputOutput { - fn set_terminal_size_using_fd(&mut self, pid: RawFd, cols: u16, rows: u16) { + fn set_terminal_size_using_fd(&self, pid: RawFd, cols: u16, rows: u16) { let terminal_input = self .possible_tty_inputs .get(&cols) @@ -235,12 +270,15 @@ impl ServerOsApi for FakeInputOutput { .unwrap() .push(IoEvent::SetTerminalSizeUsingFd(pid, cols, rows)); } - fn spawn_terminal(&mut self, _file_to_open: Option) -> (RawFd, RawFd) { + fn spawn_terminal(&self, _file_to_open: Option) -> (RawFd, Pid) { let next_terminal_id = self.stdin_writes.lock().unwrap().keys().len() as RawFd + 1; self.add_terminal(next_terminal_id); - (next_terminal_id as i32, next_terminal_id + 1000) // secondary number is arbitrary here + ( + next_terminal_id as i32, + Pid::from_raw(next_terminal_id + 1000), + ) // secondary number is arbitrary here } - fn write_to_tty_stdin(&mut self, pid: RawFd, buf: &mut [u8]) -> Result { + fn write_to_tty_stdin(&self, pid: RawFd, buf: &[u8]) -> Result { let mut stdin_writes = self.stdin_writes.lock().unwrap(); let write_buffer = stdin_writes.get_mut(&pid).unwrap(); let mut bytes_written = 0; @@ -250,7 +288,7 @@ impl ServerOsApi for FakeInputOutput { } Ok(bytes_written) } - fn read_from_tty_stdout(&mut self, pid: RawFd, buf: &mut [u8]) -> Result { + fn read_from_tty_stdout(&self, pid: RawFd, buf: &mut [u8]) -> Result { let mut read_buffers = self.read_buffers.lock().unwrap(); let mut bytes_read = 0; match read_buffers.get_mut(&pid) { @@ -267,28 +305,36 @@ impl ServerOsApi for FakeInputOutput { None => Err(nix::Error::Sys(nix::errno::Errno::EAGAIN)), } } - fn tcdrain(&mut self, pid: RawFd) -> Result<(), nix::Error> { + fn async_file_reader(&self, fd: RawFd) -> Box { + Box::new(FakeAsyncReader { + fd, + os_api: ServerOsApi::box_clone(self), + }) + } + fn tcdrain(&self, pid: RawFd) -> Result<(), nix::Error> { self.io_events.lock().unwrap().push(IoEvent::TcDrain(pid)); Ok(()) } fn box_clone(&self) -> Box { Box::new((*self).clone()) } - fn kill(&mut self, fd: RawFd) -> Result<(), nix::Error> { - self.io_events.lock().unwrap().push(IoEvent::Kill(fd)); + fn kill(&self, pid: Pid) -> Result<(), nix::Error> { + self.io_events.lock().unwrap().push(IoEvent::Kill(pid)); Ok(()) } - fn recv_from_client(&self) -> (ServerInstruction, ErrorContext) { + fn recv_from_client(&self) -> (ClientToServerMsg, ErrorContext) { self.receive_instructions_from_client .lock() .unwrap() .recv() .unwrap() } - fn send_to_client(&self, msg: ClientInstruction) { + fn send_to_client(&self, msg: ServerToClientMsg) { self.send_instructions_to_client.send(msg).unwrap(); } - fn add_client_sender(&mut self) {} + fn add_client_sender(&self) {} + fn remove_client_sender(&self) {} + fn send_to_temp_client(&self, _msg: ServerToClientMsg) {} fn update_receiver(&mut self, _stream: LocalSocketStream) {} fn load_palette(&self) -> Palette { default_palette() diff --git a/src/tests/integration/basic.rs b/src/tests/integration/basic.rs index 1ebcb922..640b1c55 100644 --- a/src/tests/integration/basic.rs +++ b/src/tests/integration/basic.rs @@ -1,8 +1,8 @@ -use crate::panes::PositionAndSize; use ::insta::assert_snapshot; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::{config::Config, options::Options}; use crate::tests::fakes::FakeInputOutput; +use crate::tests::start; use crate::tests::utils::commands::{ BRACKETED_PASTE_END, BRACKETED_PASTE_START, PANE_MODE, QUIT, SCROLL_DOWN_IN_SCROLL_MODE, SCROLL_MODE, SCROLL_PAGE_DOWN_IN_SCROLL_MODE, SCROLL_PAGE_UP_IN_SCROLL_MODE, @@ -10,7 +10,8 @@ use crate::tests::utils::commands::{ SPLIT_RIGHT_IN_PANE_MODE, TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE, }; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; -use crate::{start, CliArgs}; +use crate::CliArgs; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(fake_win_size.clone()) @@ -32,7 +33,6 @@ pub fn starts_with_one_terminal() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -61,7 +61,6 @@ pub fn split_terminals_vertically() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -90,7 +89,6 @@ pub fn split_terminals_horizontally() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -126,7 +124,6 @@ pub fn split_largest_terminal() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -155,7 +152,6 @@ pub fn cannot_split_terminals_vertically_when_active_terminal_is_too_small() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -184,7 +180,6 @@ pub fn cannot_split_terminals_horizontally_when_active_terminal_is_too_small() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -213,7 +208,6 @@ pub fn cannot_split_largest_terminal_when_there_is_no_room() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -250,7 +244,6 @@ pub fn scrolling_up_inside_a_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -289,7 +282,6 @@ pub fn scrolling_down_inside_a_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -325,7 +317,6 @@ pub fn scrolling_page_up_inside_a_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -364,7 +355,6 @@ pub fn scrolling_page_down_inside_a_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -404,7 +394,6 @@ pub fn max_panes() { opts, Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -442,7 +431,6 @@ pub fn toggle_focused_pane_fullscreen() { opts, Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -483,7 +471,6 @@ pub fn bracketed_paste() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer diff --git a/src/tests/integration/close_pane.rs b/src/tests/integration/close_pane.rs index d151f63d..b5ba7f19 100644 --- a/src/tests/integration/close_pane.rs +++ b/src/tests/integration/close_pane.rs @@ -1,16 +1,17 @@ -use crate::panes::PositionAndSize; use ::insta::assert_snapshot; +use zellij_utils::pane_size::PositionAndSize; use crate::tests::fakes::FakeInputOutput; +use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; -use crate::{start, CliArgs}; +use crate::CliArgs; -use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ CLOSE_PANE_IN_PANE_MODE, ESC, MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_DOWN_IN_RESIZE_MODE, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(fake_win_size.clone()) @@ -45,7 +46,6 @@ pub fn close_pane_with_another_pane_above_it() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -89,7 +89,6 @@ pub fn close_pane_with_another_pane_below_it() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -130,7 +129,6 @@ pub fn close_pane_with_another_pane_to_the_left() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -172,7 +170,6 @@ pub fn close_pane_with_another_pane_to_the_right() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -219,7 +216,6 @@ pub fn close_pane_with_multiple_panes_above_it() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -264,7 +260,6 @@ pub fn close_pane_with_multiple_panes_below_it() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -311,7 +306,6 @@ pub fn close_pane_with_multiple_panes_to_the_left() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -356,7 +350,6 @@ pub fn close_pane_with_multiple_panes_to_the_right() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -423,7 +416,6 @@ pub fn close_pane_with_multiple_panes_above_it_away_from_screen_edges() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -486,7 +478,6 @@ pub fn close_pane_with_multiple_panes_below_it_away_from_screen_edges() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -551,7 +542,6 @@ pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -616,7 +606,6 @@ pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -654,7 +643,6 @@ pub fn closing_last_pane_exits_app() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/compatibility.rs b/src/tests/integration/compatibility.rs index b986b1de..3b28a970 100644 --- a/src/tests/integration/compatibility.rs +++ b/src/tests/integration/compatibility.rs @@ -1,14 +1,15 @@ use ::insta::assert_snapshot; use ::std::collections::HashMap; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; use crate::tests::possible_tty_inputs::Bytes; +use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; -use crate::{start, CliArgs}; +use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::QUIT; +use zellij_utils::input::config::Config; /* * These tests are general compatibility tests for non-trivial scenarios running in the terminal. @@ -48,7 +49,6 @@ pub fn run_bandwhich_from_fish_shell() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -78,7 +78,6 @@ pub fn fish_tab_completion_options() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -113,7 +112,6 @@ pub fn fish_select_tab_completion_options() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -152,7 +150,6 @@ pub fn vim_scroll_region_down() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -188,7 +185,6 @@ pub fn vim_ctrl_d() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -223,7 +219,6 @@ pub fn vim_ctrl_u() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -253,7 +248,6 @@ pub fn htop() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -283,7 +277,6 @@ pub fn htop_scrolling() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -313,7 +306,6 @@ pub fn htop_right_scrolling() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -351,7 +343,6 @@ pub fn vim_overwrite() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -384,7 +375,6 @@ pub fn clear_scroll_region() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -414,7 +404,6 @@ pub fn display_tab_characters_properly() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -444,7 +433,6 @@ pub fn neovim_insert_mode() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -476,7 +464,6 @@ pub fn bash_cursor_linewrap() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -508,7 +495,6 @@ pub fn fish_paste_multiline() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -538,7 +524,6 @@ pub fn git_log() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -570,7 +555,6 @@ pub fn git_diff_scrollup() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -600,7 +584,6 @@ pub fn emacs_longbuf() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -630,7 +613,6 @@ pub fn top_and_quit() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -666,7 +648,6 @@ pub fn exa_plus_omf_theme() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer diff --git a/src/tests/integration/layouts.rs b/src/tests/integration/layouts.rs index bd7ceb3f..e6986924 100644 --- a/src/tests/integration/layouts.rs +++ b/src/tests/integration/layouts.rs @@ -1,12 +1,13 @@ use insta::assert_snapshot; use std::path::PathBuf; -use crate::common::input::{config::Config, options::Options}; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; +use crate::tests::start; use crate::tests::utils::commands::QUIT; use crate::tests::utils::get_output_frame_snapshots; -use crate::{start, CliArgs}; +use crate::CliArgs; +use zellij_utils::input::config::Config; +use zellij_utils::pane_size::PositionAndSize; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(fake_win_size.clone()) @@ -24,7 +25,7 @@ pub fn accepts_basic_layout() { let mut fake_input_output = get_fake_os_input(&fake_win_size); fake_input_output.add_terminal_input(&[&QUIT]); let mut opts = CliArgs::default(); - opts.layout = Some(PathBuf::from( + opts.layout_path = Some(PathBuf::from( "src/tests/fixtures/layouts/three-panes-with-nesting.yaml", )); @@ -33,7 +34,6 @@ pub fn accepts_basic_layout() { opts, Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer diff --git a/src/tests/integration/move_focus_down.rs b/src/tests/integration/move_focus_down.rs index e182acf9..2f18ab62 100644 --- a/src/tests/integration/move_focus_down.rs +++ b/src/tests/integration/move_focus_down.rs @@ -1,15 +1,16 @@ use ::insta::assert_snapshot; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; +use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; -use crate::{start, CliArgs}; +use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ MOVE_FOCUS_DOWN_IN_PANE_MODE, MOVE_FOCUS_UP_IN_PANE_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) @@ -37,7 +38,6 @@ pub fn move_focus_down() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -75,7 +75,6 @@ pub fn move_focus_down_to_the_most_recently_used_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/move_focus_left.rs b/src/tests/integration/move_focus_left.rs index c991d1e8..a9fca49e 100644 --- a/src/tests/integration/move_focus_left.rs +++ b/src/tests/integration/move_focus_left.rs @@ -1,16 +1,17 @@ use ::insta::assert_snapshot; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; +use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; -use crate::{start, CliArgs}; +use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ ENTER, MOVE_FOCUS_LEFT_IN_NORMAL_MODE, MOVE_FOCUS_LEFT_IN_PANE_MODE, MOVE_FOCUS_RIGHT_IN_PANE_MODE, NEW_TAB_IN_TAB_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, TAB_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) @@ -37,7 +38,6 @@ pub fn move_focus_left() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -76,7 +76,6 @@ pub fn move_focus_left_to_the_most_recently_used_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -115,7 +114,6 @@ pub fn move_focus_left_changes_tab() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/move_focus_right.rs b/src/tests/integration/move_focus_right.rs index ffc8cacf..c85d5b96 100644 --- a/src/tests/integration/move_focus_right.rs +++ b/src/tests/integration/move_focus_right.rs @@ -1,16 +1,17 @@ use ::insta::assert_snapshot; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; +use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; -use crate::{start, CliArgs}; +use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ ENTER, MOVE_FOCUS_LEFT_IN_PANE_MODE, MOVE_FOCUS_RIGHT_IN_NORMAL_MODE, MOVE_FOCUS_RIGHT_IN_PANE_MODE, NEW_TAB_IN_TAB_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, TAB_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) @@ -38,7 +39,6 @@ pub fn move_focus_right() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -76,7 +76,6 @@ pub fn move_focus_right_to_the_most_recently_used_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -115,7 +114,6 @@ pub fn move_focus_right_changes_tab() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/move_focus_up.rs b/src/tests/integration/move_focus_up.rs index 54bfef5e..d161ff8a 100644 --- a/src/tests/integration/move_focus_up.rs +++ b/src/tests/integration/move_focus_up.rs @@ -1,15 +1,16 @@ use ::insta::assert_snapshot; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; +use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; -use crate::{start, CliArgs}; +use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ MOVE_FOCUS_DOWN_IN_PANE_MODE, MOVE_FOCUS_UP_IN_PANE_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) @@ -36,7 +37,6 @@ pub fn move_focus_up() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -75,7 +75,6 @@ pub fn move_focus_up_to_the_most_recently_used_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/resize_down.rs b/src/tests/integration/resize_down.rs index 0e710b4e..4ef45a06 100644 --- a/src/tests/integration/resize_down.rs +++ b/src/tests/integration/resize_down.rs @@ -1,16 +1,17 @@ use insta::assert_snapshot; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; +use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; -use crate::{start, CliArgs}; +use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_DOWN_IN_RESIZE_MODE, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, SLEEP, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) @@ -48,7 +49,6 @@ pub fn resize_down_with_pane_above() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -94,7 +94,6 @@ pub fn resize_down_with_pane_below() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -146,7 +145,6 @@ pub fn resize_down_with_panes_above_and_below() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -197,7 +195,6 @@ pub fn resize_down_with_multiple_panes_above() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -250,7 +247,6 @@ pub fn resize_down_with_panes_above_aligned_left_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -302,7 +298,6 @@ pub fn resize_down_with_panes_below_aligned_left_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -352,7 +347,6 @@ pub fn resize_down_with_panes_above_aligned_right_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -403,7 +397,6 @@ pub fn resize_down_with_panes_below_aligned_right_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -457,7 +450,6 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -513,7 +505,6 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -586,7 +577,6 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -661,7 +651,6 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -704,7 +693,6 @@ pub fn cannot_resize_down_when_pane_below_is_at_minimum_height() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/resize_left.rs b/src/tests/integration/resize_left.rs index 5d498e96..1b820501 100644 --- a/src/tests/integration/resize_left.rs +++ b/src/tests/integration/resize_left.rs @@ -1,15 +1,16 @@ use ::insta::assert_snapshot; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; +use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; -use crate::{start, CliArgs}; +use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE, SLEEP, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) @@ -44,7 +45,6 @@ pub fn resize_left_with_pane_to_the_left() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -88,7 +88,6 @@ pub fn resize_left_with_pane_to_the_right() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -134,7 +133,6 @@ pub fn resize_left_with_panes_to_the_left_and_right() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -183,7 +181,6 @@ pub fn resize_left_with_multiple_panes_to_the_left() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -234,7 +231,6 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -282,7 +278,6 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -332,7 +327,6 @@ pub fn resize_left_with_panes_to_the_left_aligned_bottom_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -381,7 +375,6 @@ pub fn resize_left_with_panes_to_the_right_aligned_bottom_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -435,7 +428,6 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pa CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -491,7 +483,6 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_p CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -564,7 +555,6 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -640,7 +630,6 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -683,7 +672,6 @@ pub fn cannot_resize_left_when_pane_to_the_left_is_at_minimum_width() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/resize_right.rs b/src/tests/integration/resize_right.rs index 8be15315..44601f2c 100644 --- a/src/tests/integration/resize_right.rs +++ b/src/tests/integration/resize_right.rs @@ -1,15 +1,16 @@ use ::insta::assert_snapshot; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; +use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; -use crate::{start, CliArgs}; +use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_MODE, RESIZE_RIGHT_IN_RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE, SLEEP, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) @@ -44,7 +45,6 @@ pub fn resize_right_with_pane_to_the_left() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -88,7 +88,6 @@ pub fn resize_right_with_pane_to_the_right() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -134,7 +133,6 @@ pub fn resize_right_with_panes_to_the_left_and_right() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -183,7 +181,6 @@ pub fn resize_right_with_multiple_panes_to_the_left() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -234,7 +231,6 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -282,7 +278,6 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -332,7 +327,6 @@ pub fn resize_right_with_panes_to_the_left_aligned_bottom_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -381,7 +375,6 @@ pub fn resize_right_with_panes_to_the_right_aligned_bottom_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -435,7 +428,6 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_p CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -491,7 +483,6 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_ CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -564,7 +555,6 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -639,7 +629,6 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -682,7 +671,6 @@ pub fn cannot_resize_right_when_pane_to_the_left_is_at_minimum_width() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/resize_up.rs b/src/tests/integration/resize_up.rs index ba810a9f..9aca8d5a 100644 --- a/src/tests/integration/resize_up.rs +++ b/src/tests/integration/resize_up.rs @@ -1,15 +1,16 @@ use ::insta::assert_snapshot; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; +use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; -use crate::{start, CliArgs}; +use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE, SLEEP, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) @@ -46,7 +47,6 @@ pub fn resize_up_with_pane_above() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -92,7 +92,6 @@ pub fn resize_up_with_pane_below() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -143,7 +142,6 @@ pub fn resize_up_with_panes_above_and_below() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -193,7 +191,6 @@ pub fn resize_up_with_multiple_panes_above() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -244,7 +241,6 @@ pub fn resize_up_with_panes_above_aligned_left_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -296,7 +292,6 @@ pub fn resize_up_with_panes_below_aligned_left_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -346,7 +341,6 @@ pub fn resize_up_with_panes_above_aligned_right_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -397,7 +391,6 @@ pub fn resize_up_with_panes_below_aligned_right_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -451,7 +444,6 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -507,7 +499,6 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -580,7 +571,6 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -655,7 +645,6 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -698,7 +687,6 @@ pub fn cannot_resize_up_when_pane_above_is_at_minimum_height() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/tabs.rs b/src/tests/integration/tabs.rs index 1d8a2c9d..90f82c6d 100644 --- a/src/tests/integration/tabs.rs +++ b/src/tests/integration/tabs.rs @@ -1,16 +1,18 @@ use insta::assert_snapshot; use crate::tests::fakes::FakeInputOutput; +use crate::tests::start; +use crate::tests::utils::commands::CLOSE_PANE_IN_PANE_MODE; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; -use crate::{panes::PositionAndSize, tests::utils::commands::CLOSE_PANE_IN_PANE_MODE}; -use crate::{start, CliArgs}; +use crate::CliArgs; -use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ CLOSE_TAB_IN_TAB_MODE, NEW_TAB_IN_TAB_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, SWITCH_NEXT_TAB_IN_TAB_MODE, SWITCH_PREV_TAB_IN_TAB_MODE, TAB_MODE, TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE, }; +use zellij_utils::input::config::Config; +use zellij_utils::pane_size::PositionAndSize; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) @@ -38,7 +40,6 @@ pub fn open_new_tab() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -75,7 +76,6 @@ pub fn switch_to_prev_tab() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -112,7 +112,6 @@ pub fn switch_to_next_tab() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -149,7 +148,6 @@ pub fn close_tab() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -187,7 +185,6 @@ pub fn close_last_pane_in_a_tab() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -227,7 +224,6 @@ pub fn close_the_middle_tab() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -272,7 +268,6 @@ pub fn close_the_tab_that_has_a_pane_in_fullscreen() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -309,7 +304,6 @@ pub fn closing_last_tab_exits_the_app() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/terminal_window_resize.rs b/src/tests/integration/terminal_window_resize.rs index 7c06609a..2be41c3a 100644 --- a/src/tests/integration/terminal_window_resize.rs +++ b/src/tests/integration/terminal_window_resize.rs @@ -1,11 +1,12 @@ -use crate::panes::PositionAndSize; use ::insta::assert_snapshot; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::{config::Config, options::Options}; use crate::tests::fakes::FakeInputOutput; +use crate::tests::start; use crate::tests::utils::commands::QUIT; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; -use crate::{start, CliArgs}; +use crate::CliArgs; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(fake_win_size.clone()) @@ -35,7 +36,6 @@ pub fn window_width_decrease_with_one_pane() { opts, Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -72,7 +72,6 @@ pub fn window_width_increase_with_one_pane() { opts, Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -109,7 +108,6 @@ pub fn window_height_increase_with_one_pane() { opts, Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -146,7 +144,6 @@ pub fn window_width_and_height_decrease_with_one_pane() { opts, Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output .stdout_writer diff --git a/src/tests/integration/toggle_fullscreen.rs b/src/tests/integration/toggle_fullscreen.rs index 8a63bbea..80257982 100644 --- a/src/tests/integration/toggle_fullscreen.rs +++ b/src/tests/integration/toggle_fullscreen.rs @@ -1,15 +1,16 @@ use insta::assert_snapshot; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; +use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; -use crate::{start, CliArgs}; +use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) @@ -37,7 +38,6 @@ pub fn adding_new_terminal_in_fullscreen() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output @@ -73,7 +73,6 @@ pub fn move_focus_is_disabled_in_fullscreen() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), - Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 6e07c96d..e075ed49 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -3,3 +3,24 @@ pub mod integration; pub mod possible_tty_inputs; pub mod tty_inputs; pub mod utils; + +use std::path::PathBuf; +use zellij_client::{os_input_output::ClientOsApi, start_client, ClientInfo}; +use zellij_server::{os_input_output::ServerOsApi, start_server}; +use zellij_utils::{cli::CliArgs, input::config::Config}; + +pub fn start( + client_os_input: Box, + opts: CliArgs, + server_os_input: Box, + config: Config, +) { + let server_thread = std::thread::Builder::new() + .name("server_thread".into()) + .spawn(move || { + start_server(server_os_input, PathBuf::from("")); + }) + .unwrap(); + start_client(client_os_input, opts, config, ClientInfo::New("".into())); + let _ = server_thread.join(); +} diff --git a/src/tests/utils.rs b/src/tests/utils.rs index f01c1ac5..6267c883 100644 --- a/src/tests/utils.rs +++ b/src/tests/utils.rs @@ -1,5 +1,8 @@ -use crate::panes::PositionAndSize; -use crate::panes::TerminalPane; +use zellij_utils::{vte, zellij_tile}; + +use zellij_server::{panes::TerminalPane, tab::Pane}; +use zellij_tile::data::Palette; +use zellij_utils::pane_size::PositionAndSize; pub fn get_output_frame_snapshots( output_frames: &[Vec], @@ -7,7 +10,7 @@ pub fn get_output_frame_snapshots( ) -> Vec { let mut vte_parser = vte::Parser::new(); let main_pid = 0; - let mut terminal_output = TerminalPane::new(main_pid, *win_size); + let mut terminal_output = TerminalPane::new(main_pid, *win_size, Palette::default()); let mut snapshots = vec![]; for frame in output_frames.iter() { diff --git a/zellij-client/Cargo.toml b/zellij-client/Cargo.toml new file mode 100644 index 00000000..f9d0db08 --- /dev/null +++ b/zellij-client/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "zellij-client" +version = "0.12.0" +authors = ["Kunal Mohan "] +edition = "2018" +description = "The client-side library for Zellij" +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +termbg = "0.2.3" +zellij-utils = { path = "../zellij-utils/", version = "0.12.0" } + +[features] +test = ["zellij-utils/test"] diff --git a/src/common/command_is_executing.rs b/zellij-client/src/command_is_executing.rs similarity index 96% rename from src/common/command_is_executing.rs rename to zellij-client/src/command_is_executing.rs index ad032557..3d672d9a 100644 --- a/src/common/command_is_executing.rs +++ b/zellij-client/src/command_is_executing.rs @@ -2,7 +2,7 @@ use std::sync::{Arc, Condvar, Mutex}; #[derive(Clone)] -pub struct CommandIsExecuting { +pub(crate) struct CommandIsExecuting { input_thread: Arc<(Mutex, Condvar)>, } diff --git a/src/common/input/handler.rs b/zellij-client/src/input_handler.rs similarity index 62% rename from src/common/input/handler.rs rename to zellij-client/src/input_handler.rs index 7dabc71e..65181e51 100644 --- a/src/common/input/handler.rs +++ b/zellij-client/src/input_handler.rs @@ -1,17 +1,17 @@ //! Main input logic. -use super::actions::Action; -use super::keybinds::Keybinds; -use crate::client::ClientInstruction; -use crate::common::input::config::Config; -use crate::common::thread_bus::{SenderWithContext, OPENCALLS}; -use crate::errors::ContextType; -use crate::os_input_output::ClientOsApi; -use crate::server::ServerInstruction; -use crate::CommandIsExecuting; +use zellij_utils::{termion, zellij_tile}; -use termion::input::{TermRead, TermReadEventsAndRaw}; -use zellij_tile::data::{InputMode, Key, ModeInfo, Palette, PluginCapabilities}; +use crate::{os_input_output::ClientOsApi, ClientInstruction, CommandIsExecuting}; +use zellij_utils::{ + channels::{SenderWithContext, OPENCALLS}, + errors::ContextType, + input::{actions::Action, cast_termion_key, config::Config, keybinds::Keybinds}, + ipc::{ClientToServerMsg, ExitReason}, +}; + +use termion::input::TermReadEventsAndRaw; +use zellij_tile::data::{InputMode, Key}; /// Handles the dispatching of [`Action`]s according to the current /// [`InputMode`], and keep tracks of the current [`InputMode`]. @@ -133,14 +133,16 @@ impl InputHandler { let mut should_break = false; match action { - Action::Quit => { + Action::Quit | Action::Detach => { + self.os_input + .send_to_server(ClientToServerMsg::Action(action)); self.exit(); should_break = true; } Action::SwitchToMode(mode) => { self.mode = mode; self.os_input - .send_to_server(ServerInstruction::Action(action)); + .send_to_server(ClientToServerMsg::Action(action)); } Action::CloseFocus | Action::NewPane(_) @@ -152,13 +154,13 @@ impl InputHandler { | Action::MoveFocusOrTab(_) => { self.command_is_executing.blocking_input_thread(); self.os_input - .send_to_server(ServerInstruction::Action(action)); + .send_to_server(ClientToServerMsg::Action(action)); self.command_is_executing .wait_until_input_thread_is_unblocked(); } _ => self .os_input - .send_to_server(ServerInstruction::Action(action)), + .send_to_server(ClientToServerMsg::Action(action)), } should_break @@ -168,60 +170,14 @@ impl InputHandler { /// same as quitting Zellij). fn exit(&mut self) { self.send_client_instructions - .send(ClientInstruction::Exit) + .send(ClientInstruction::Exit(ExitReason::Normal)) .unwrap(); } } -/// Creates a [`Help`] struct indicating the current [`InputMode`] and its keybinds -/// (as pairs of [`String`]s). -// TODO this should probably be automatically generated in some way -pub fn get_mode_info( - mode: InputMode, - palette: Palette, - capabilities: PluginCapabilities, -) -> ModeInfo { - let mut keybinds: Vec<(String, String)> = vec![]; - match mode { - InputMode::Normal | InputMode::Locked => {} - InputMode::Resize => { - keybinds.push(("←↓↑→".to_string(), "Resize".to_string())); - } - InputMode::Pane => { - keybinds.push(("←↓↑→".to_string(), "Move focus".to_string())); - keybinds.push(("p".to_string(), "Next".to_string())); - keybinds.push(("n".to_string(), "New".to_string())); - keybinds.push(("d".to_string(), "Down split".to_string())); - keybinds.push(("r".to_string(), "Right split".to_string())); - keybinds.push(("x".to_string(), "Close".to_string())); - keybinds.push(("f".to_string(), "Fullscreen".to_string())); - } - InputMode::Tab => { - keybinds.push(("←↓↑→".to_string(), "Move focus".to_string())); - keybinds.push(("n".to_string(), "New".to_string())); - keybinds.push(("x".to_string(), "Close".to_string())); - keybinds.push(("r".to_string(), "Rename".to_string())); - keybinds.push(("s".to_string(), "Sync".to_string())); - } - InputMode::Scroll => { - keybinds.push(("↓↑".to_string(), "Scroll".to_string())); - keybinds.push(("PgUp/PgDn".to_string(), "Scroll Page".to_string())); - } - InputMode::RenameTab => { - keybinds.push(("Enter".to_string(), "when done".to_string())); - } - } - ModeInfo { - mode, - keybinds, - palette, - capabilities, - } -} - /// Entry point to the module. Instantiates an [`InputHandler`] and starts /// its [`InputHandler::handle_input()`] loop. -pub fn input_loop( +pub(crate) fn input_loop( os_input: Box, config: Config, command_is_executing: CommandIsExecuting, @@ -237,35 +193,3 @@ pub fn input_loop( ) .handle_input(); } - -pub fn parse_keys(input_bytes: &[u8]) -> Vec { - input_bytes.keys().flatten().map(cast_termion_key).collect() -} - -// FIXME: This is an absolutely cursed function that should be destroyed as soon -// as an alternative that doesn't touch zellij-tile can be developed... -fn cast_termion_key(event: termion::event::Key) -> Key { - match event { - termion::event::Key::Backspace => Key::Backspace, - termion::event::Key::Left => Key::Left, - termion::event::Key::Right => Key::Right, - termion::event::Key::Up => Key::Up, - termion::event::Key::Down => Key::Down, - termion::event::Key::Home => Key::Home, - termion::event::Key::End => Key::End, - termion::event::Key::PageUp => Key::PageUp, - termion::event::Key::PageDown => Key::PageDown, - termion::event::Key::BackTab => Key::BackTab, - termion::event::Key::Delete => Key::Delete, - termion::event::Key::Insert => Key::Insert, - termion::event::Key::F(n) => Key::F(n), - termion::event::Key::Char(c) => Key::Char(c), - termion::event::Key::Alt(c) => Key::Alt(c), - termion::event::Key::Ctrl(c) => Key::Ctrl(c), - termion::event::Key::Null => Key::Null, - termion::event::Key::Esc => Key::Esc, - _ => { - unimplemented!("Encountered an unknown key!") - } - } -} diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs new file mode 100644 index 00000000..d854b6d3 --- /dev/null +++ b/zellij-client/src/lib.rs @@ -0,0 +1,298 @@ +pub mod os_input_output; + +mod command_is_executing; +mod input_handler; + +use std::env::current_exe; +use std::io::{self, Write}; +use std::path::Path; +use std::process::Command; +use std::sync::mpsc; +use std::thread; + +use crate::{ + command_is_executing::CommandIsExecuting, input_handler::input_loop, + os_input_output::ClientOsApi, +}; +use zellij_utils::cli::CliArgs; +use zellij_utils::{ + channels::{SenderType, SenderWithContext, SyncChannelWithContext}, + consts::{SESSION_NAME, ZELLIJ_IPC_PIPE}, + errors::{ClientContext, ContextType, ErrorInstruction}, + input::{actions::Action, config::Config, options::Options}, + ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg}, +}; + +/// Instructions related to the client-side application +#[derive(Debug, Clone)] +pub(crate) enum ClientInstruction { + Error(String), + Render(String), + UnblockInputThread, + Exit(ExitReason), +} + +impl From for ClientInstruction { + fn from(instruction: ServerToClientMsg) -> Self { + match instruction { + ServerToClientMsg::Exit(e) => ClientInstruction::Exit(e), + ServerToClientMsg::Render(buffer) => ClientInstruction::Render(buffer), + ServerToClientMsg::UnblockInputThread => ClientInstruction::UnblockInputThread, + } + } +} + +impl From<&ClientInstruction> for ClientContext { + fn from(client_instruction: &ClientInstruction) -> Self { + match *client_instruction { + ClientInstruction::Exit(_) => ClientContext::Exit, + ClientInstruction::Error(_) => ClientContext::Error, + ClientInstruction::Render(_) => ClientContext::Render, + ClientInstruction::UnblockInputThread => ClientContext::UnblockInputThread, + } + } +} + +impl ErrorInstruction for ClientInstruction { + fn error(err: String) -> Self { + ClientInstruction::Error(err) + } +} + +fn spawn_server(socket_path: &Path) -> io::Result<()> { + let status = Command::new(current_exe()?) + .arg("--server") + .arg(socket_path) + .status()?; + if status.success() { + Ok(()) + } else { + let msg = "Process returned non-zero exit code"; + let err_msg = match status.code() { + Some(c) => format!("{}: {}", msg, c), + None => msg.to_string(), + }; + Err(io::Error::new(io::ErrorKind::Other, err_msg)) + } +} + +#[derive(Debug, Clone)] +pub enum ClientInfo { + Attach(String, bool), + New(String), +} + +pub fn start_client( + mut os_input: Box, + opts: CliArgs, + config: Config, + info: ClientInfo, +) { + let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}12l\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"; + os_input.unset_raw_mode(0); + let palette = os_input.load_palette(); + let _ = os_input + .get_stdout_writer() + .write(take_snapshot.as_bytes()) + .unwrap(); + let _ = os_input + .get_stdout_writer() + .write(clear_client_terminal_attributes.as_bytes()) + .unwrap(); + std::env::set_var(&"ZELLIJ", "0"); + + let config_options = Options::from_cli(&config.options, opts.command.clone()); + + let full_screen_ws = os_input.get_terminal_size_using_fd(0); + let client_attributes = ClientAttributes { + position_and_size: full_screen_ws, + palette, + }; + + #[cfg(not(any(feature = "test", test)))] + let first_msg = match info { + ClientInfo::Attach(name, force) => { + SESSION_NAME.set(name).unwrap(); + std::env::set_var(&"ZELLIJ_SESSION_NAME", SESSION_NAME.get().unwrap()); + + ClientToServerMsg::AttachClient(client_attributes, force) + } + ClientInfo::New(name) => { + SESSION_NAME.set(name).unwrap(); + std::env::set_var(&"ZELLIJ_SESSION_NAME", SESSION_NAME.get().unwrap()); + + spawn_server(&*ZELLIJ_IPC_PIPE).unwrap(); + + ClientToServerMsg::NewClient( + client_attributes, + Box::new(opts), + Box::new(config_options.clone()), + ) + } + }; + #[cfg(any(feature = "test", test))] + let first_msg = { + let _ = SESSION_NAME.set("".into()); + ClientToServerMsg::NewClient(client_attributes, Box::new(opts), Box::new(config_options.clone())) + }; + + os_input.connect_to_server(&*ZELLIJ_IPC_PIPE); + os_input.send_to_server(first_msg); + + let mut command_is_executing = CommandIsExecuting::new(); + + os_input.set_raw_mode(0); + let _ = os_input + .get_stdout_writer() + .write(bracketed_paste.as_bytes()) + .unwrap(); + + let (send_client_instructions, receive_client_instructions): SyncChannelWithContext< + ClientInstruction, + > = mpsc::sync_channel(50); + let send_client_instructions = + SenderWithContext::new(SenderType::SyncSender(send_client_instructions)); + + #[cfg(not(any(feature = "test", test)))] + std::panic::set_hook({ + use zellij_utils::errors::handle_panic; + let send_client_instructions = send_client_instructions.clone(); + Box::new(move |info| { + handle_panic(info, &send_client_instructions); + }) + }); + + let _stdin_thread = thread::Builder::new() + .name("stdin_handler".to_string()) + .spawn({ + let send_client_instructions = send_client_instructions.clone(); + let command_is_executing = command_is_executing.clone(); + let os_input = os_input.clone(); + let default_mode = config_options.default_mode.unwrap_or_default(); + move || { + input_loop( + os_input, + config, + command_is_executing, + send_client_instructions, + default_mode, + ) + } + }); + + let _signal_thread = thread::Builder::new() + .name("signal_listener".to_string()) + .spawn({ + let os_input = os_input.clone(); + let send_client_instructions = send_client_instructions.clone(); + move || { + os_input.handle_signals( + Box::new({ + let os_api = os_input.clone(); + move || { + os_api.send_to_server(ClientToServerMsg::TerminalResize( + os_api.get_terminal_size_using_fd(0), + )); + } + }), + Box::new({ + let send_client_instructions = send_client_instructions.clone(); + move || { + send_client_instructions + .send(ClientInstruction::Exit(ExitReason::Normal)) + .unwrap() + } + }), + ); + } + }) + .unwrap(); + + let router_thread = thread::Builder::new() + .name("router".to_string()) + .spawn({ + let os_input = os_input.clone(); + let mut should_break = false; + move || loop { + let (instruction, err_ctx) = os_input.recv_from_server(); + err_ctx.update_thread_ctx(); + if let ServerToClientMsg::Exit(_) = instruction { + should_break = true; + } + send_client_instructions.send(instruction.into()).unwrap(); + if should_break { + break; + } + } + }) + .unwrap(); + + let handle_error = |backtrace: String| { + os_input.unset_raw_mode(0); + let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1); + let restore_snapshot = "\u{1b}[?1049l"; + let error = format!( + "{}\n{}{}", + goto_start_of_last_line, restore_snapshot, backtrace + ); + let _ = os_input + .get_stdout_writer() + .write(error.as_bytes()) + .unwrap(); + std::process::exit(1); + }; + + let exit_msg: String; + + loop { + let (client_instruction, mut err_ctx) = receive_client_instructions + .recv() + .expect("failed to receive app instruction on channel"); + + err_ctx.add_call(ContextType::Client((&client_instruction).into())); + match client_instruction { + ClientInstruction::Exit(reason) => { + os_input.send_to_server(ClientToServerMsg::ClientExited); + + if let ExitReason::Error(_) = reason { + handle_error(format!("{}", reason)); + } + exit_msg = format!("{}", reason); + break; + } + ClientInstruction::Error(backtrace) => { + let _ = os_input.send_to_server(ClientToServerMsg::Action(Action::Quit)); + handle_error(backtrace); + } + ClientInstruction::Render(output) => { + let mut stdout = os_input.get_stdout_writer(); + stdout + .write_all(&output.as_bytes()) + .expect("cannot write to stdout"); + stdout.flush().expect("could not flush"); + } + ClientInstruction::UnblockInputThread => { + command_is_executing.unblock_input_thread(); + } + } + } + + 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.unset_raw_mode(0); + let mut stdout = os_input.get_stdout_writer(); + let _ = stdout.write(goodbye_message.as_bytes()).unwrap(); + stdout.flush().unwrap(); +} diff --git a/zellij-client/src/os_input_output.rs b/zellij-client/src/os_input_output.rs new file mode 100644 index 00000000..9d8617f5 --- /dev/null +++ b/zellij-client/src/os_input_output.rs @@ -0,0 +1,190 @@ +use zellij_utils::{interprocess, libc, nix, signal_hook, zellij_tile}; + +use interprocess::local_socket::LocalSocketStream; +use nix::pty::Winsize; +use nix::sys::termios; +use signal_hook::{consts::signal::*, iterator::Signals}; +use std::io; +use std::io::prelude::*; +use std::os::unix::io::RawFd; +use std::path::Path; +use std::sync::{Arc, Mutex}; +use zellij_tile::data::{Palette, PaletteColor}; +use zellij_utils::{ + errors::ErrorContext, + ipc::{ClientToServerMsg, IpcReceiverWithContext, IpcSenderWithContext, ServerToClientMsg}, + pane_size::PositionAndSize, + shared::default_palette, +}; + +fn into_raw_mode(pid: RawFd) { + let mut tio = termios::tcgetattr(pid).expect("could not get terminal attribute"); + termios::cfmakeraw(&mut tio); + match termios::tcsetattr(pid, termios::SetArg::TCSANOW, &tio) { + Ok(_) => {} + Err(e) => panic!("error {:?}", e), + }; +} + +fn unset_raw_mode(pid: RawFd, orig_termios: termios::Termios) { + match termios::tcsetattr(pid, termios::SetArg::TCSANOW, &orig_termios) { + Ok(_) => {} + Err(e) => panic!("error {:?}", e), + }; +} + +pub(crate) fn get_terminal_size_using_fd(fd: RawFd) -> PositionAndSize { + // TODO: do this with the nix ioctl + use libc::ioctl; + use libc::TIOCGWINSZ; + + let mut winsize = Winsize { + ws_row: 0, + ws_col: 0, + ws_xpixel: 0, + ws_ypixel: 0, + }; + + unsafe { ioctl(fd, TIOCGWINSZ, &mut winsize) }; + PositionAndSize::from(winsize) +} + +#[derive(Clone)] +pub struct ClientOsInputOutput { + orig_termios: Arc>, + send_instructions_to_server: Arc>>>, + receive_instructions_from_server: Arc>>>, +} + +/// The `ClientOsApi` trait represents an abstract interface to the features of an operating system that +/// Zellij client requires. +pub trait ClientOsApi: Send + Sync { + /// Returns the size of the terminal associated to file descriptor `fd`. + fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize; + /// Set the terminal associated to file descriptor `fd` to + /// [raw mode](https://en.wikipedia.org/wiki/Terminal_mode). + fn set_raw_mode(&mut self, fd: RawFd); + /// Set the terminal associated to file descriptor `fd` to + /// [cooked mode](https://en.wikipedia.org/wiki/Terminal_mode). + fn unset_raw_mode(&self, fd: RawFd); + /// Returns the writer that allows writing to standard output. + fn get_stdout_writer(&self) -> Box; + /// Returns the raw contents of standard input. + fn read_from_stdin(&self) -> Vec; + /// Returns a [`Box`] pointer to this [`ClientOsApi`] struct. + fn box_clone(&self) -> Box; + /// Sends a message to the server. + fn send_to_server(&self, msg: ClientToServerMsg); + /// Receives a message on client-side IPC channel + // This should be called from the client-side router thread only. + fn recv_from_server(&self) -> (ServerToClientMsg, ErrorContext); + fn handle_signals(&self, sigwinch_cb: Box, quit_cb: Box); + /// Establish a connection with the server socket. + fn connect_to_server(&self, path: &Path); + fn load_palette(&self) -> Palette; +} + +impl ClientOsApi for ClientOsInputOutput { + fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize { + get_terminal_size_using_fd(fd) + } + fn set_raw_mode(&mut self, fd: RawFd) { + into_raw_mode(fd); + } + fn unset_raw_mode(&self, fd: RawFd) { + let orig_termios = self.orig_termios.lock().unwrap(); + unset_raw_mode(fd, orig_termios.clone()); + } + fn box_clone(&self) -> Box { + Box::new((*self).clone()) + } + fn read_from_stdin(&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 get_stdout_writer(&self) -> Box { + let stdout = ::std::io::stdout(); + Box::new(stdout) + } + fn send_to_server(&self, msg: ClientToServerMsg) { + self.send_instructions_to_server + .lock() + .unwrap() + .as_mut() + .unwrap() + .send(msg); + } + fn recv_from_server(&self) -> (ServerToClientMsg, ErrorContext) { + self.receive_instructions_from_server + .lock() + .unwrap() + .as_mut() + .unwrap() + .recv() + } + fn handle_signals(&self, sigwinch_cb: Box, quit_cb: Box) { + let mut signals = Signals::new(&[SIGWINCH, SIGTERM, SIGINT, SIGQUIT, SIGHUP]).unwrap(); + for signal in signals.forever() { + match signal { + SIGWINCH => { + sigwinch_cb(); + } + SIGTERM | SIGINT | SIGQUIT | SIGHUP => { + quit_cb(); + break; + } + _ => unreachable!(), + } + } + } + fn connect_to_server(&self, path: &Path) { + let socket; + loop { + match LocalSocketStream::connect(path) { + Ok(sock) => { + socket = sock; + break; + } + Err(_) => { + std::thread::sleep(std::time::Duration::from_millis(50)); + } + } + } + let sender = IpcSenderWithContext::new(socket); + let receiver = sender.get_receiver(); + *self.send_instructions_to_server.lock().unwrap() = Some(sender); + *self.receive_instructions_from_server.lock().unwrap() = Some(receiver); + } + fn load_palette(&self) -> Palette { + let timeout = std::time::Duration::from_millis(100); + let mut palette = default_palette(); + if let Ok(rgb) = termbg::rgb(timeout) { + palette.bg = PaletteColor::Rgb((rgb.r as u8, rgb.g as u8, rgb.b as u8)); + // TODO: also dynamically get all other colors from the user's terminal + // this should be done in the same method (OSC ]11), but there might be other + // considerations here, hence using the library + }; + palette + } +} + +impl Clone for Box { + fn clone(&self) -> Box { + self.box_clone() + } +} + +pub fn get_client_os_input() -> Result { + let current_termios = termios::tcgetattr(0)?; + let orig_termios = Arc::new(Mutex::new(current_termios)); + Ok(ClientOsInputOutput { + orig_termios, + send_instructions_to_server: Arc::new(Mutex::new(None)), + receive_instructions_from_server: Arc::new(Mutex::new(None)), + }) +} diff --git a/zellij-server/Cargo.toml b/zellij-server/Cargo.toml new file mode 100644 index 00000000..388a3e5b --- /dev/null +++ b/zellij-server/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "zellij-server" +version = "0.12.0" +authors = ["Kunal Mohan "] +edition = "2018" +description = "The server-side library for Zellij" +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ansi_term = "0.12.1" +async-trait = "0.1.50" +daemonize = "0.4.1" +serde_json = "1.0" +unicode-width = "0.1.8" +wasmer = "1.0.0" +wasmer-wasi = "1.0.0" +zellij-utils = { path = "../zellij-utils/", version = "0.12.0" } + +[dev-dependencies] +insta = "1.6.0" + +[features] +test = ["zellij-utils/test"] diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs new file mode 100644 index 00000000..40fb87f7 --- /dev/null +++ b/zellij-server/src/lib.rs @@ -0,0 +1,407 @@ +pub mod os_input_output; +pub mod panes; +pub mod tab; + +mod pty; +mod route; +mod screen; +mod thread_bus; +mod ui; +mod wasm_vm; + +use zellij_utils::zellij_tile; + +use std::sync::{Arc, Mutex, RwLock}; +use std::thread; +use std::{path::PathBuf, sync::mpsc}; +use wasmer::Store; +use zellij_tile::data::{Event, InputMode, PluginCapabilities}; + +use crate::{ + os_input_output::ServerOsApi, + pty::{pty_thread_main, Pty, PtyInstruction}, + screen::{screen_thread_main, ScreenInstruction}, + thread_bus::{Bus, ThreadSenders}, + ui::layout::Layout, + wasm_vm::{wasm_thread_main, PluginInstruction}, +}; +use route::route_thread_main; +use zellij_utils::{ + channels::{ChannelWithContext, SenderType, SenderWithContext, SyncChannelWithContext}, + cli::CliArgs, + errors::{ContextType, ErrorInstruction, ServerContext}, + input::{get_mode_info, options::Options}, + ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg}, + setup::{get_default_data_dir, install::populate_data_dir}, +}; + +/// Instructions related to server-side application +#[derive(Debug, Clone)] +pub(crate) enum ServerInstruction { + NewClient(ClientAttributes, Box, Box), + Render(Option), + UnblockInputThread, + ClientExit, + Error(String), + DetachSession, + AttachClient(ClientAttributes, bool), +} + +impl From for ServerInstruction { + fn from(instruction: ClientToServerMsg) -> Self { + match instruction { + ClientToServerMsg::NewClient(attrs, opts, options) => { + ServerInstruction::NewClient(attrs, opts, options) + } + ClientToServerMsg::AttachClient(attrs, force) => { + ServerInstruction::AttachClient(attrs, force) + } + _ => unreachable!(), + } + } +} + +impl From<&ServerInstruction> for ServerContext { + fn from(server_instruction: &ServerInstruction) -> Self { + match *server_instruction { + ServerInstruction::NewClient(..) => ServerContext::NewClient, + ServerInstruction::Render(_) => ServerContext::Render, + ServerInstruction::UnblockInputThread => ServerContext::UnblockInputThread, + ServerInstruction::ClientExit => ServerContext::ClientExit, + ServerInstruction::Error(_) => ServerContext::Error, + ServerInstruction::DetachSession => ServerContext::DetachSession, + ServerInstruction::AttachClient(..) => ServerContext::AttachClient, + } + } +} + +impl ErrorInstruction for ServerInstruction { + fn error(err: String) -> Self { + ServerInstruction::Error(err) + } +} + +pub(crate) struct SessionMetaData { + pub senders: ThreadSenders, + pub capabilities: PluginCapabilities, + screen_thread: Option>, + pty_thread: Option>, + wasm_thread: Option>, +} + +impl Drop for SessionMetaData { + fn drop(&mut self) { + let _ = self.senders.send_to_pty(PtyInstruction::Exit); + let _ = self.senders.send_to_screen(ScreenInstruction::Exit); + let _ = self.senders.send_to_plugin(PluginInstruction::Exit); + let _ = self.screen_thread.take().unwrap().join(); + let _ = self.pty_thread.take().unwrap().join(); + let _ = self.wasm_thread.take().unwrap().join(); + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) enum SessionState { + Attached, + Detached, + Uninitialized, +} + +pub fn start_server(os_input: Box, socket_path: PathBuf) { + #[cfg(not(any(feature = "test", test)))] + daemonize::Daemonize::new() + .working_directory(std::env::current_dir().unwrap()) + .umask(0o077) + .start() + .expect("could not daemonize the server process"); + + std::env::set_var(&"ZELLIJ", "0"); + + let (to_server, server_receiver): SyncChannelWithContext = + mpsc::sync_channel(50); + let to_server = SenderWithContext::new(SenderType::SyncSender(to_server)); + let session_data: Arc>> = Arc::new(RwLock::new(None)); + let session_state = Arc::new(RwLock::new(SessionState::Uninitialized)); + + #[cfg(not(any(feature = "test", test)))] + std::panic::set_hook({ + use zellij_utils::errors::handle_panic; + let to_server = to_server.clone(); + Box::new(move |info| { + handle_panic(info, &to_server); + }) + }); + + let thread_handles = Arc::new(Mutex::new(Vec::new())); + + #[cfg(any(feature = "test", test))] + thread_handles.lock().unwrap().push( + thread::Builder::new() + .name("server_router".to_string()) + .spawn({ + let session_data = session_data.clone(); + let os_input = os_input.clone(); + let to_server = to_server.clone(); + let session_state = session_state.clone(); + + move || route_thread_main(session_data, session_state, os_input, to_server) + }) + .unwrap(), + ); + #[cfg(not(any(feature = "test", test)))] + let _ = thread::Builder::new() + .name("server_listener".to_string()) + .spawn({ + use zellij_utils::{ + interprocess::local_socket::LocalSocketListener, shared::set_permissions, + }; + + let os_input = os_input.clone(); + let session_data = session_data.clone(); + let session_state = session_state.clone(); + let to_server = to_server.clone(); + let socket_path = socket_path.clone(); + let thread_handles = thread_handles.clone(); + move || { + drop(std::fs::remove_file(&socket_path)); + let listener = LocalSocketListener::bind(&*socket_path).unwrap(); + set_permissions(&socket_path).unwrap(); + for stream in listener.incoming() { + match stream { + Ok(stream) => { + let mut os_input = os_input.clone(); + os_input.update_receiver(stream); + let session_data = session_data.clone(); + let session_state = session_state.clone(); + let to_server = to_server.clone(); + thread_handles.lock().unwrap().push( + thread::Builder::new() + .name("server_router".to_string()) + .spawn({ + let session_data = session_data.clone(); + let os_input = os_input.clone(); + let to_server = to_server.clone(); + + move || { + route_thread_main( + session_data, + session_state, + os_input, + to_server, + ) + } + }) + .unwrap(), + ); + } + Err(err) => { + panic!("err {:?}", err); + } + } + } + } + }); + + loop { + let (instruction, mut err_ctx) = server_receiver.recv().unwrap(); + err_ctx.add_call(ContextType::IPCServer((&instruction).into())); + match instruction { + ServerInstruction::NewClient(client_attributes, opts, config_options) => { + let session = init_session( + os_input.clone(), + opts, + config_options, + to_server.clone(), + client_attributes, + session_state.clone(), + ); + *session_data.write().unwrap() = Some(session); + *session_state.write().unwrap() = SessionState::Attached; + session_data + .read() + .unwrap() + .as_ref() + .unwrap() + .senders + .send_to_pty(PtyInstruction::NewTab) + .unwrap(); + } + ServerInstruction::AttachClient(attrs, _) => { + *session_state.write().unwrap() = SessionState::Attached; + let rlock = session_data.read().unwrap(); + let session_data = rlock.as_ref().unwrap(); + session_data + .senders + .send_to_screen(ScreenInstruction::TerminalResize(attrs.position_and_size)) + .unwrap(); + let mode_info = + get_mode_info(InputMode::Normal, attrs.palette, session_data.capabilities); + session_data + .senders + .send_to_screen(ScreenInstruction::ChangeMode(mode_info.clone())) + .unwrap(); + session_data + .senders + .send_to_plugin(PluginInstruction::Update( + None, + Event::ModeUpdate(mode_info), + )) + .unwrap(); + } + ServerInstruction::UnblockInputThread => { + if *session_state.read().unwrap() == SessionState::Attached { + os_input.send_to_client(ServerToClientMsg::UnblockInputThread); + } + } + ServerInstruction::ClientExit => { + *session_data.write().unwrap() = None; + os_input.send_to_client(ServerToClientMsg::Exit(ExitReason::Normal)); + break; + } + ServerInstruction::DetachSession => { + *session_state.write().unwrap() = SessionState::Detached; + os_input.send_to_client(ServerToClientMsg::Exit(ExitReason::Normal)); + os_input.remove_client_sender(); + } + ServerInstruction::Render(output) => { + if *session_state.read().unwrap() == SessionState::Attached { + // Here output is of the type Option sent by screen thread. + // If `Some(_)`- unwrap it and forward it to the client to render. + // If `None`- Send an exit instruction. This is the case when the user closes last Tab/Pane. + if let Some(op) = output { + os_input.send_to_client(ServerToClientMsg::Render(op)); + } else { + os_input.send_to_client(ServerToClientMsg::Exit(ExitReason::Normal)); + break; + } + } + } + ServerInstruction::Error(backtrace) => { + if *session_state.read().unwrap() == SessionState::Attached { + os_input.send_to_client(ServerToClientMsg::Exit(ExitReason::Error(backtrace))); + } + break; + } + } + } + thread_handles + .lock() + .unwrap() + .drain(..) + .for_each(|h| drop(h.join())); + #[cfg(not(any(feature = "test", test)))] + drop(std::fs::remove_file(&socket_path)); +} + +fn init_session( + os_input: Box, + opts: Box, + config_options: Box, + to_server: SenderWithContext, + client_attributes: ClientAttributes, + session_state: Arc>, +) -> SessionMetaData { + let (to_screen, screen_receiver): ChannelWithContext = mpsc::channel(); + let to_screen = SenderWithContext::new(SenderType::Sender(to_screen)); + + let (to_plugin, plugin_receiver): ChannelWithContext = mpsc::channel(); + let to_plugin = SenderWithContext::new(SenderType::Sender(to_plugin)); + let (to_pty, pty_receiver): ChannelWithContext = mpsc::channel(); + let to_pty = SenderWithContext::new(SenderType::Sender(to_pty)); + + // Determine and initialize the data directory + let data_dir = opts.data_dir.unwrap_or_else(get_default_data_dir); + + #[cfg(not(disable_automatic_asset_installation))] + populate_data_dir(&data_dir); + + let capabilities = PluginCapabilities { + arrow_fonts: config_options.simplified_ui, + }; + + // Don't use default layouts in tests, but do everywhere else + #[cfg(not(any(feature = "test", test)))] + let default_layout = Some(PathBuf::from("default")); + #[cfg(any(feature = "test", test))] + let default_layout = None; + let layout_path = opts.layout_path; + let maybe_layout = opts + .layout + .as_ref() + .map(|p| Layout::from_dir(&p, &data_dir)) + .or_else(|| layout_path.map(|p| Layout::new(&p))) + .or_else(|| default_layout.map(|p| Layout::from_dir(&p, &data_dir))); + + let pty_thread = thread::Builder::new() + .name("pty".to_string()) + .spawn({ + let pty = Pty::new( + Bus::new( + pty_receiver, + Some(&to_screen), + None, + Some(&to_plugin), + Some(&to_server), + Some(os_input.clone()), + ), + opts.debug, + ); + + move || pty_thread_main(pty, maybe_layout) + }) + .unwrap(); + + let screen_thread = thread::Builder::new() + .name("screen".to_string()) + .spawn({ + let screen_bus = Bus::new( + screen_receiver, + None, + Some(&to_pty), + Some(&to_plugin), + Some(&to_server), + Some(os_input.clone()), + ); + let max_panes = opts.max_panes; + + move || { + screen_thread_main( + screen_bus, + max_panes, + client_attributes, + config_options, + session_state, + ); + } + }) + .unwrap(); + + let wasm_thread = thread::Builder::new() + .name("wasm".to_string()) + .spawn({ + let plugin_bus = Bus::new( + plugin_receiver, + Some(&to_screen), + Some(&to_pty), + None, + None, + None, + ); + let store = Store::default(); + + move || wasm_thread_main(plugin_bus, store, data_dir) + }) + .unwrap(); + SessionMetaData { + senders: ThreadSenders { + to_screen: Some(to_screen), + to_pty: Some(to_pty), + to_plugin: Some(to_plugin), + to_server: None, + }, + capabilities, + screen_thread: Some(screen_thread), + pty_thread: Some(pty_thread), + wasm_thread: Some(wasm_thread), + } +} diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs new file mode 100644 index 00000000..4cda73ed --- /dev/null +++ b/zellij-server/src/os_input_output.rs @@ -0,0 +1,311 @@ +use std::env; +use std::os::unix::io::RawFd; +use std::path::PathBuf; +use std::process::{Child, Command}; +use std::sync::{Arc, Mutex}; + +use zellij_utils::{async_std, interprocess, libc, nix, signal_hook, zellij_tile}; + +use async_std::fs::File as AsyncFile; +use async_std::os::unix::io::FromRawFd; +use interprocess::local_socket::LocalSocketStream; +use nix::pty::{forkpty, Winsize}; +use nix::sys::signal::{kill, Signal}; +use nix::sys::termios; +use nix::sys::wait::waitpid; +use nix::unistd::{self, ForkResult}; +use signal_hook::consts::*; +use zellij_tile::data::Palette; +use zellij_utils::{ + errors::ErrorContext, + ipc::{ + ClientToServerMsg, ExitReason, IpcReceiverWithContext, IpcSenderWithContext, + ServerToClientMsg, + }, + shared::default_palette, +}; + +use async_std::io::ReadExt; +pub use async_trait::async_trait; + +pub use nix::unistd::Pid; + +pub(crate) fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) { + // TODO: do this with the nix ioctl + use libc::ioctl; + use libc::TIOCSWINSZ; + + let winsize = Winsize { + ws_col: columns, + ws_row: rows, + ws_xpixel: 0, + ws_ypixel: 0, + }; + unsafe { ioctl(fd, TIOCSWINSZ, &winsize) }; +} + +/// Handle some signals for the child process. This will loop until the child +/// process exits. +fn handle_command_exit(mut child: Child) { + // register the SIGINT signal (TODO handle more signals) + let mut signals = signal_hook::iterator::Signals::new(&[SIGINT]).unwrap(); + 'handle_exit: loop { + // test whether the child process has exited + match child.try_wait() { + Ok(Some(_status)) => { + // if the child process has exited, break outside of the loop + // and exit this function + // TODO: handle errors? + break 'handle_exit; + } + Ok(None) => { + ::std::thread::sleep(::std::time::Duration::from_millis(100)); + } + Err(e) => panic!("error attempting to wait: {}", e), + } + + for signal in signals.pending() { + if let SIGINT = signal { + child.kill().unwrap(); + child.wait().unwrap(); + break 'handle_exit; + } + } + } +} + +/// Spawns a new terminal from the parent terminal with [`termios`](termios::Termios) +/// `orig_termios`. +/// +/// If a `file_to_open` is given, the text editor specified by environment variable `EDITOR` +/// (or `VISUAL`, if `EDITOR` is not set) will be started in the new terminal, with the given +/// file open. If no file is given, the shell specified by environment variable `SHELL` will +/// be started in the new terminal. +/// +/// # Panics +/// +/// This function will panic if both the `EDITOR` and `VISUAL` environment variables are not +/// set. +// FIXME this should probably be split into different functions, or at least have less levels +// of indentation in some way +fn spawn_terminal(file_to_open: Option, orig_termios: termios::Termios) -> (RawFd, Pid) { + let (pid_primary, pid_secondary): (RawFd, Pid) = { + match forkpty(None, Some(&orig_termios)) { + Ok(fork_pty_res) => { + let pid_primary = fork_pty_res.master; + let pid_secondary = match fork_pty_res.fork_result { + ForkResult::Parent { child } => { + // fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::empty())).expect("could not fcntl"); + child + } + ForkResult::Child => match file_to_open { + Some(file_to_open) => { + if env::var("EDITOR").is_err() && env::var("VISUAL").is_err() { + panic!("Can't edit files if an editor is not defined. To fix: define the EDITOR or VISUAL environment variables with the path to your editor (eg. /usr/bin/vim)"); + } + let editor = + env::var("EDITOR").unwrap_or_else(|_| env::var("VISUAL").unwrap()); + + let child = Command::new(editor) + .args(&[file_to_open]) + .spawn() + .expect("failed to spawn"); + handle_command_exit(child); + ::std::process::exit(0); + } + None => { + let child = Command::new(env::var("SHELL").unwrap()) + .spawn() + .expect("failed to spawn"); + handle_command_exit(child); + ::std::process::exit(0); + } + }, + }; + (pid_primary, pid_secondary) + } + Err(e) => { + panic!("failed to fork {:?}", e); + } + } + }; + (pid_primary, pid_secondary) +} + +#[derive(Clone)] +pub struct ServerOsInputOutput { + orig_termios: Arc>, + receive_instructions_from_client: Option>>>, + send_instructions_to_client: Arc>>>, +} + +// async fn in traits is not supported by rust, so dtolnay's excellent async_trait macro is being +// used. See https://smallcultfollowing.com/babysteps/blog/2019/10/26/async-fn-in-traits-are-hard/ +#[async_trait] +pub trait AsyncReader: Send + Sync { + async fn read(&mut self, buf: &mut [u8]) -> Result; +} + +/// An `AsyncReader` that wraps a `RawFd` +struct RawFdAsyncReader { + fd: async_std::fs::File, +} + +impl RawFdAsyncReader { + fn new(fd: RawFd) -> RawFdAsyncReader { + RawFdAsyncReader { + /// The supplied `RawFd` is consumed by the created `RawFdAsyncReader`, closing it when dropped + fd: unsafe { AsyncFile::from_raw_fd(fd) }, + } + } +} + +#[async_trait] +impl AsyncReader for RawFdAsyncReader { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.fd.read(buf).await + } +} + +/// The `ServerOsApi` trait represents an abstract interface to the features of an operating system that +/// Zellij server requires. +pub trait ServerOsApi: Send + Sync { + /// Sets the size of the terminal associated to file descriptor `fd`. + fn set_terminal_size_using_fd(&self, fd: RawFd, cols: u16, rows: u16); + /// Spawn a new terminal, with an optional file to open in a terminal program. + fn spawn_terminal(&self, file_to_open: Option) -> (RawFd, Pid); + /// Read bytes from the standard output of the virtual terminal referred to by `fd`. + fn read_from_tty_stdout(&self, fd: RawFd, buf: &mut [u8]) -> Result; + /// Creates an `AsyncReader` that can be used to read from `fd` in an async context + fn async_file_reader(&self, fd: RawFd) -> Box; + /// Write bytes to the standard input of the virtual terminal referred to by `fd`. + fn write_to_tty_stdin(&self, fd: RawFd, buf: &[u8]) -> Result; + /// Wait until all output written to the object referred to by `fd` has been transmitted. + fn tcdrain(&self, fd: RawFd) -> Result<(), nix::Error>; + /// Terminate the process with process ID `pid`. + fn kill(&self, pid: Pid) -> Result<(), nix::Error>; + /// Returns a [`Box`] pointer to this [`ServerOsApi`] struct. + fn box_clone(&self) -> Box; + /// Receives a message on server-side IPC channel + fn recv_from_client(&self) -> (ClientToServerMsg, ErrorContext); + /// Sends a message to client + fn send_to_client(&self, msg: ServerToClientMsg); + /// Adds a sender to client + fn add_client_sender(&self); + /// Send to the temporary client + // A temporary client is the one that hasn't been registered as a client yet. + // Only the corresponding router thread has access to send messages to it. + // This can be the case when the client cannot attach to the session, + // so it tries to connect and then exits, hence temporary. + fn send_to_temp_client(&self, msg: ServerToClientMsg); + /// Removes the sender to client + fn remove_client_sender(&self); + /// Update the receiver socket for the client + fn update_receiver(&mut self, stream: LocalSocketStream); + fn load_palette(&self) -> Palette; +} + +impl ServerOsApi for ServerOsInputOutput { + fn set_terminal_size_using_fd(&self, fd: RawFd, cols: u16, rows: u16) { + set_terminal_size_using_fd(fd, cols, rows); + } + fn spawn_terminal(&self, file_to_open: Option) -> (RawFd, Pid) { + let orig_termios = self.orig_termios.lock().unwrap(); + spawn_terminal(file_to_open, orig_termios.clone()) + } + fn read_from_tty_stdout(&self, fd: RawFd, buf: &mut [u8]) -> Result { + unistd::read(fd, buf) + } + fn async_file_reader(&self, fd: RawFd) -> Box { + Box::new(RawFdAsyncReader::new(fd)) + } + fn write_to_tty_stdin(&self, fd: RawFd, buf: &[u8]) -> Result { + unistd::write(fd, buf) + } + fn tcdrain(&self, fd: RawFd) -> Result<(), nix::Error> { + termios::tcdrain(fd) + } + fn box_clone(&self) -> Box { + Box::new((*self).clone()) + } + fn kill(&self, pid: Pid) -> Result<(), nix::Error> { + // TODO: + // Ideally, we should be using SIGINT rather than SIGKILL here, but there are cases in which + // the terminal we're trying to kill hangs on SIGINT and so all the app gets stuck + // that's why we're sending SIGKILL here + // A better solution would be to send SIGINT here and not wait for it, and then have + // a background thread do the waitpid stuff and send SIGKILL if the process is stuck + kill(pid, Some(Signal::SIGKILL)).unwrap(); + waitpid(pid, None).unwrap(); + Ok(()) + } + fn recv_from_client(&self) -> (ClientToServerMsg, ErrorContext) { + self.receive_instructions_from_client + .as_ref() + .unwrap() + .lock() + .unwrap() + .recv() + } + fn send_to_client(&self, msg: ServerToClientMsg) { + self.send_instructions_to_client + .lock() + .unwrap() + .as_mut() + .unwrap() + .send(msg); + } + fn add_client_sender(&self) { + let sender = self + .receive_instructions_from_client + .as_ref() + .unwrap() + .lock() + .unwrap() + .get_sender(); + let old_sender = self + .send_instructions_to_client + .lock() + .unwrap() + .replace(sender); + if let Some(mut sender) = old_sender { + sender.send(ServerToClientMsg::Exit(ExitReason::ForceDetached)); + } + } + fn send_to_temp_client(&self, msg: ServerToClientMsg) { + self.receive_instructions_from_client + .as_ref() + .unwrap() + .lock() + .unwrap() + .get_sender() + .send(msg); + } + fn remove_client_sender(&self) { + assert!(self.send_instructions_to_client.lock().unwrap().is_some()); + *self.send_instructions_to_client.lock().unwrap() = None; + } + fn update_receiver(&mut self, stream: LocalSocketStream) { + self.receive_instructions_from_client = + Some(Arc::new(Mutex::new(IpcReceiverWithContext::new(stream)))); + } + fn load_palette(&self) -> Palette { + default_palette() + } +} + +impl Clone for Box { + fn clone(&self) -> Box { + self.box_clone() + } +} + +pub fn get_server_os_input() -> Result { + let current_termios = termios::tcgetattr(0)?; + let orig_termios = Arc::new(Mutex::new(current_termios)); + Ok(ServerOsInputOutput { + orig_termios, + receive_instructions_from_client: None, + send_instructions_to_client: Arc::new(Mutex::new(None)), + }) +} diff --git a/src/client/panes/grid.rs b/zellij-server/src/panes/grid.rs similarity index 90% rename from src/client/panes/grid.rs rename to zellij-server/src/panes/grid.rs index e51b1ed5..ac347db0 100644 --- a/src/client/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -2,22 +2,43 @@ use std::{ cmp::Ordering, collections::{BTreeSet, VecDeque}, fmt::{self, Debug, Formatter}, + str, }; -use vte::{Params, Perform}; +use zellij_utils::{vte, zellij_tile}; const TABSTOP_WIDTH: usize = 8; // TODO: is this always right? const SCROLL_BACK: usize = 10_000; -use crate::utils::consts::VERSION; -use crate::utils::logging::debug_log_to_file; -use crate::utils::shared::version_number; +use vte::{Params, Perform}; +use zellij_tile::data::{Palette, PaletteColor}; +use zellij_utils::{consts::VERSION, logging::debug_log_to_file, shared::version_number}; use crate::panes::terminal_character::{ CharacterStyles, CharsetIndex, Cursor, CursorShape, StandardCharset, TerminalCharacter, EMPTY_TERMINAL_CHARACTER, }; +// this was copied verbatim from alacritty +fn parse_number(input: &[u8]) -> Option { + if input.is_empty() { + return None; + } + let mut num: u8 = 0; + for c in input { + let c = *c as char; + if let Some(digit) = c.to_digit(10) { + num = match num.checked_mul(10).and_then(|v| v.checked_add(digit as u8)) { + Some(v) => v, + None => return None, + } + } else { + return None; + } + } + Some(num) +} + fn get_top_non_canonical_rows(rows: &mut Vec) -> Vec { let mut index_of_last_non_canonical_row = None; for (i, row) in rows.iter().enumerate() { @@ -180,6 +201,7 @@ pub struct Grid { scroll_region: Option<(usize, usize)>, active_charset: CharsetIndex, preceding_char: Option, + colors: Palette, pub should_render: bool, pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "") pub erasure_mode: bool, // ERM @@ -205,7 +227,7 @@ impl Debug for Grid { } impl Grid { - pub fn new(rows: usize, columns: usize) -> Self { + pub fn new(rows: usize, columns: usize, colors: Palette) -> Self { Grid { lines_above: VecDeque::with_capacity(SCROLL_BACK), viewport: vec![Row::new().canonical()], @@ -226,6 +248,7 @@ impl Grid { clear_viewport_before_rendering: false, active_charset: Default::default(), pending_messages_to_pty: vec![], + colors, } } pub fn contains_widechar(&self) -> bool { @@ -1013,8 +1036,139 @@ impl Perform for Grid { // TBD } - fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) { - // TBD + fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { + let terminator = if bell_terminated { "\x07" } else { "\x1b\\" }; + + if params.is_empty() || params[0].is_empty() { + return; + } + + match params[0] { + // Set window title. + b"0" | b"2" => { + if params.len() >= 2 { + let _title = params[1..] + .iter() + .flat_map(|x| str::from_utf8(x)) + .collect::>() + .join(";") + .trim() + .to_owned(); + // TBD: do something with title? + } + } + + // Set color index. + b"4" => { + // TBD: set color index - currently unsupported + // + // this changes a terminal color index to something else + // meaning anything set to that index will be changed + // during rendering + } + + // Get/set Foreground, Background, Cursor colors. + b"10" | b"11" | b"12" => { + if params.len() >= 2 { + if let Some(mut dynamic_code) = parse_number(params[0]) { + for param in ¶ms[1..] { + // currently only getting the color sequence is supported, + // setting still isn't + if param == b"?" { + let color_response_message = match self.colors.bg { + PaletteColor::Rgb((r, g, b)) => { + format!( + "\u{1b}]{};rgb:{1:02x}{1:02x}/{2:02x}{2:02x}/{3:02x}{3:02x}{4}", + // dynamic_code, color.r, color.g, color.b, terminator + dynamic_code, r, g, b, terminator + ) + } + _ => { + format!( + "\u{1b}]{};rgb:{1:02x}{1:02x}/{2:02x}{2:02x}/{3:02x}{3:02x}{4}", + // dynamic_code, color.r, color.g, color.b, terminator + dynamic_code, 0, 0, 0, terminator + ) + } + }; + self.pending_messages_to_pty + .push(color_response_message.as_bytes().to_vec()); + } + dynamic_code += 1; + } + return; + } + } + } + + // Set cursor style. + b"50" => { + if params.len() >= 2 + && params[1].len() >= 13 + && params[1][0..12] == *b"CursorShape=" + { + let shape = match params[1][12] as char { + '0' => Some(CursorShape::Block), + '1' => Some(CursorShape::Beam), + '2' => Some(CursorShape::Underline), + _ => None, + }; + if let Some(cursor_shape) = shape { + self.cursor.change_shape(cursor_shape); + } + } + } + + // Set clipboard. + b"52" => { + if params.len() < 3 { + return; + } + + let _clipboard = params[1].get(0).unwrap_or(&b'c'); + match params[2] { + b"?" => { + // TBD: paste from own clipboard - currently unsupported + } + _base64 => { + // TBD: copy to own clipboard - currently unsupported + } + } + } + + // Reset color index. + b"104" => { + // Reset all color indexes when no parameters are given. + if params.len() == 1 { + // TBD - reset all color changes - currently unsupported + return; + } + + // Reset color indexes given as parameters. + for param in ¶ms[1..] { + if let Some(_index) = parse_number(param) { + // TBD - reset color index - currently unimplemented + } + } + } + + // Reset foreground color. + b"110" => { + // TBD - reset foreground color - currently unimplemented + } + + // Reset background color. + b"111" => { + // TBD - reset background color - currently unimplemented + } + + // Reset text cursor color. + b"112" => { + // TBD - reset text cursor color - currently unimplemented + } + + _ => {} + } } fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], _ignore: bool, c: char) { @@ -1347,7 +1501,7 @@ impl Perform for Grid { } } else { let result = debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params)); - #[cfg(not(test))] + #[cfg(not(any(feature = "test", test)))] result.unwrap(); } } @@ -1436,13 +1590,19 @@ impl Debug for Row { } } -impl Row { - pub fn new() -> Self { +impl Default for Row { + fn default() -> Self { Row { columns: vec![], is_canonical: false, } } +} + +impl Row { + pub fn new() -> Self { + Self::default() + } pub fn from_columns(columns: Vec) -> Self { Row { columns, @@ -1528,6 +1688,9 @@ impl Row { pub fn len(&self) -> usize { self.columns.len() } + pub fn is_empty(&self) -> bool { + self.columns.is_empty() + } pub fn delete_character(&mut self, x: usize) { if x < self.columns.len() { self.columns.remove(x); diff --git a/src/client/panes/mod.rs b/zellij-server/src/panes/mod.rs similarity index 82% rename from src/client/panes/mod.rs rename to zellij-server/src/panes/mod.rs index 4e7d6e80..187b364d 100644 --- a/src/client/panes/mod.rs +++ b/zellij-server/src/panes/mod.rs @@ -4,6 +4,6 @@ mod terminal_character; mod terminal_pane; pub use grid::*; -pub use plugin_pane::*; +pub(crate) use plugin_pane::*; pub use terminal_character::*; pub use terminal_pane::*; diff --git a/src/client/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs similarity index 98% rename from src/client/panes/plugin_pane.rs rename to zellij-server/src/panes/plugin_pane.rs index dde3cd9b..5549a388 100644 --- a/src/client/panes/plugin_pane.rs +++ b/zellij-server/src/panes/plugin_pane.rs @@ -2,13 +2,13 @@ use std::sync::mpsc::channel; use std::time::Instant; use std::unimplemented; -use crate::common::thread_bus::SenderWithContext; -use crate::panes::{PaneId, PositionAndSize}; +use crate::panes::PaneId; use crate::pty::VteBytes; use crate::tab::Pane; use crate::wasm_vm::PluginInstruction; +use zellij_utils::{channels::SenderWithContext, pane_size::PositionAndSize}; -pub struct PluginPane { +pub(crate) struct PluginPane { pub pid: u32, pub should_render: bool, pub selectable: bool, diff --git a/src/client/panes/terminal_character.rs b/zellij-server/src/panes/terminal_character.rs similarity index 99% rename from src/client/panes/terminal_character.rs rename to zellij-server/src/panes/terminal_character.rs index d2340e2e..e6489eb4 100644 --- a/src/client/panes/terminal_character.rs +++ b/zellij-server/src/panes/terminal_character.rs @@ -1,10 +1,10 @@ use unicode_width::UnicodeWidthChar; -use crate::utils::logging::debug_log_to_file; use std::convert::TryFrom; use std::fmt::{self, Debug, Display, Formatter}; use std::ops::{Index, IndexMut}; -use vte::ParamsIter; +use zellij_utils::logging::debug_log_to_file; +use zellij_utils::vte::ParamsIter; pub const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter { character: ' ', @@ -110,9 +110,9 @@ pub struct CharacterStyles { pub italic: Option, } -impl CharacterStyles { - pub fn new() -> Self { - CharacterStyles { +impl Default for CharacterStyles { + fn default() -> Self { + Self { foreground: None, background: None, strike: None, @@ -126,6 +126,12 @@ impl CharacterStyles { italic: None, } } +} + +impl CharacterStyles { + pub fn new() -> Self { + Self::default() + } pub fn foreground(mut self, foreground_code: Option) -> Self { self.foreground = foreground_code; self diff --git a/src/client/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs similarity index 92% rename from src/client/panes/terminal_pane.rs rename to zellij-server/src/panes/terminal_pane.rs index 5c4f55c8..7e45c77b 100644 --- a/src/client/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -1,45 +1,26 @@ +use zellij_utils::{vte, zellij_tile}; + use std::fmt::Debug; use std::os::unix::io::RawFd; use std::time::Instant; +use zellij_tile::data::Palette; +use zellij_utils::pane_size::PositionAndSize; -use nix::pty::Winsize; -use serde::{Deserialize, Serialize}; - -use crate::panes::grid::Grid; -use crate::panes::terminal_character::{ - CharacterStyles, CursorShape, TerminalCharacter, EMPTY_TERMINAL_CHARACTER, +use crate::panes::{ + grid::Grid, + terminal_character::{ + CharacterStyles, CursorShape, TerminalCharacter, EMPTY_TERMINAL_CHARACTER, + }, }; use crate::pty::VteBytes; use crate::tab::Pane; -#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)] pub enum PaneId { Terminal(RawFd), Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct? } -/// Contains the position and size of a [`Pane`], or more generally of any terminal, measured -/// in character rows and columns. -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] -pub struct PositionAndSize { - pub x: usize, - pub y: usize, - pub rows: usize, - pub columns: usize, - pub max_rows: Option, - pub max_columns: Option, -} - -impl From for PositionAndSize { - fn from(winsize: Winsize) -> PositionAndSize { - PositionAndSize { - columns: winsize.ws_col as usize, - rows: winsize.ws_row as usize, - ..Default::default() - } - } -} - pub struct TerminalPane { pub grid: Grid, pub pid: RawFd, @@ -49,6 +30,7 @@ pub struct TerminalPane { pub max_height: Option, pub max_width: Option, pub active_at: Instant, + pub colors: Palette, vte_parser: vte::Parser, } @@ -307,8 +289,8 @@ impl Pane for TerminalPane { } impl TerminalPane { - pub fn new(pid: RawFd, position_and_size: PositionAndSize) -> TerminalPane { - let grid = Grid::new(position_and_size.rows, position_and_size.columns); + pub fn new(pid: RawFd, position_and_size: PositionAndSize, palette: Palette) -> TerminalPane { + let grid = Grid::new(position_and_size.rows, position_and_size.columns, palette); TerminalPane { pid, grid, @@ -319,6 +301,7 @@ impl TerminalPane { max_width: None, vte_parser: vte::Parser::new(), active_at: Instant::now(), + colors: palette, } } pub fn get_x(&self) -> usize { @@ -354,7 +337,7 @@ impl TerminalPane { pub fn read_buffer_as_lines(&self) -> Vec> { self.grid.as_character_lines() } - #[cfg(test)] + #[cfg(any(feature = "test", test))] pub fn cursor_coordinates(&self) -> Option<(usize, usize)> { // (x, y) self.grid.cursor_coordinates() diff --git a/src/client/panes/unit/grid_tests.rs b/zellij-server/src/panes/unit/grid_tests.rs similarity index 82% rename from src/client/panes/unit/grid_tests.rs rename to zellij-server/src/panes/unit/grid_tests.rs index 8517a975..1a1d7f5c 100644 --- a/src/client/panes/unit/grid_tests.rs +++ b/zellij-server/src/panes/unit/grid_tests.rs @@ -1,9 +1,10 @@ use super::super::Grid; use ::insta::assert_snapshot; +use zellij_utils::{vte, zellij_tile::data::Palette}; fn read_fixture(fixture_name: &str) -> Vec { let mut path_to_file = std::path::PathBuf::new(); - path_to_file.push("src"); + path_to_file.push("../src"); path_to_file.push("tests"); path_to_file.push("fixtures"); path_to_file.push(fixture_name); @@ -15,7 +16,7 @@ fn read_fixture(fixture_name: &str) -> Vec { #[test] fn vttest1_0() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest1-0"; let content = read_fixture(fixture_name); for byte in content { @@ -27,7 +28,7 @@ fn vttest1_0() { #[test] fn vttest1_1() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest1-1"; let content = read_fixture(fixture_name); for byte in content { @@ -39,7 +40,7 @@ fn vttest1_1() { #[test] fn vttest1_2() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest1-2"; let content = read_fixture(fixture_name); for byte in content { @@ -51,7 +52,7 @@ fn vttest1_2() { #[test] fn vttest1_3() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest1-3"; let content = read_fixture(fixture_name); for byte in content { @@ -63,7 +64,7 @@ fn vttest1_3() { #[test] fn vttest1_4() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest1-4"; let content = read_fixture(fixture_name); for byte in content { @@ -75,7 +76,7 @@ fn vttest1_4() { #[test] fn vttest1_5() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest1-5"; let content = read_fixture(fixture_name); for byte in content { @@ -87,7 +88,7 @@ fn vttest1_5() { #[test] fn vttest2_0() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest2-0"; let content = read_fixture(fixture_name); for byte in content { @@ -99,7 +100,7 @@ fn vttest2_0() { #[test] fn vttest2_1() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest2-1"; let content = read_fixture(fixture_name); for byte in content { @@ -111,7 +112,7 @@ fn vttest2_1() { #[test] fn vttest2_2() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest2-2"; let content = read_fixture(fixture_name); for byte in content { @@ -123,7 +124,7 @@ fn vttest2_2() { #[test] fn vttest2_3() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest2-3"; let content = read_fixture(fixture_name); for byte in content { @@ -135,7 +136,7 @@ fn vttest2_3() { #[test] fn vttest2_4() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest2-4"; let content = read_fixture(fixture_name); for byte in content { @@ -147,7 +148,7 @@ fn vttest2_4() { #[test] fn vttest2_5() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest2-5"; let content = read_fixture(fixture_name); for byte in content { @@ -159,7 +160,7 @@ fn vttest2_5() { #[test] fn vttest2_6() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest2-6"; let content = read_fixture(fixture_name); for byte in content { @@ -171,7 +172,7 @@ fn vttest2_6() { #[test] fn vttest2_7() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest2-7"; let content = read_fixture(fixture_name); for byte in content { @@ -183,7 +184,7 @@ fn vttest2_7() { #[test] fn vttest2_8() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest2-8"; let content = read_fixture(fixture_name); for byte in content { @@ -195,7 +196,7 @@ fn vttest2_8() { #[test] fn vttest2_9() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest2-9"; let content = read_fixture(fixture_name); for byte in content { @@ -207,7 +208,7 @@ fn vttest2_9() { #[test] fn vttest2_10() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest2-10"; let content = read_fixture(fixture_name); for byte in content { @@ -219,7 +220,7 @@ fn vttest2_10() { #[test] fn vttest2_11() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest2-11"; let content = read_fixture(fixture_name); for byte in content { @@ -231,7 +232,7 @@ fn vttest2_11() { #[test] fn vttest2_12() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest2-12"; let content = read_fixture(fixture_name); for byte in content { @@ -243,7 +244,7 @@ fn vttest2_12() { #[test] fn vttest2_13() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest2-13"; let content = read_fixture(fixture_name); for byte in content { @@ -255,7 +256,7 @@ fn vttest2_13() { #[test] fn vttest2_14() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest2-14"; let content = read_fixture(fixture_name); for byte in content { @@ -267,7 +268,7 @@ fn vttest2_14() { #[test] fn vttest3_0() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); + let mut grid = Grid::new(41, 110, Palette::default()); let fixture_name = "vttest3-0"; let content = read_fixture(fixture_name); for byte in content { @@ -279,7 +280,7 @@ fn vttest3_0() { #[test] fn vttest8_0() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); + let mut grid = Grid::new(51, 97, Palette::default()); let fixture_name = "vttest8-0"; let content = read_fixture(fixture_name); for byte in content { @@ -291,7 +292,7 @@ fn vttest8_0() { #[test] fn vttest8_1() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); + let mut grid = Grid::new(51, 97, Palette::default()); let fixture_name = "vttest8-1"; let content = read_fixture(fixture_name); for byte in content { @@ -303,7 +304,7 @@ fn vttest8_1() { #[test] fn vttest8_2() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); + let mut grid = Grid::new(51, 97, Palette::default()); let fixture_name = "vttest8-2"; let content = read_fixture(fixture_name); for byte in content { @@ -315,7 +316,7 @@ fn vttest8_2() { #[test] fn vttest8_3() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); + let mut grid = Grid::new(51, 97, Palette::default()); let fixture_name = "vttest8-3"; let content = read_fixture(fixture_name); for byte in content { @@ -327,7 +328,7 @@ fn vttest8_3() { #[test] fn vttest8_4() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); + let mut grid = Grid::new(51, 97, Palette::default()); let fixture_name = "vttest8-4"; let content = read_fixture(fixture_name); for byte in content { @@ -339,7 +340,7 @@ fn vttest8_4() { #[test] fn vttest8_5() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); + let mut grid = Grid::new(51, 97, Palette::default()); let fixture_name = "vttest8-5"; let content = read_fixture(fixture_name); for byte in content { @@ -351,7 +352,7 @@ fn vttest8_5() { #[test] fn csi_b() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); + let mut grid = Grid::new(51, 97, Palette::default()); let fixture_name = "csi-b"; let content = read_fixture(fixture_name); for byte in content { @@ -363,7 +364,7 @@ fn csi_b() { #[test] fn csi_capital_i() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); + let mut grid = Grid::new(51, 97, Palette::default()); let fixture_name = "csi-capital-i"; let content = read_fixture(fixture_name); for byte in content { @@ -375,7 +376,7 @@ fn csi_capital_i() { #[test] fn csi_capital_z() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); + let mut grid = Grid::new(51, 97, Palette::default()); let fixture_name = "csi-capital-z"; let content = read_fixture(fixture_name); for byte in content { @@ -387,7 +388,7 @@ fn csi_capital_z() { #[test] fn terminal_reports() { let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); + let mut grid = Grid::new(51, 97, Palette::default()); let fixture_name = "terminal_reports"; let content = read_fixture(fixture_name); for byte in content { diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_b.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_b.snap similarity index 57% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_b.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_b.snap index 1d0c34e5..2be051c5 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_b.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_b.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_i.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_capital_i.snap similarity index 67% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_i.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_capital_i.snap index 11f7b8f8..44026d04 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_i.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_capital_i.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_z.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_capital_z.snap similarity index 61% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_z.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_capital_z.snap index 04f492b8..2411b25f 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_z.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_capital_z.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__terminal_reports.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__terminal_reports.snap similarity index 79% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__terminal_reports.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__terminal_reports.snap index b2dca273..d289d93f 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__terminal_reports.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__terminal_reports.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid . pending_messages_to_pty)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_0.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_0.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_0.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_0.snap index 8641098c..f469f2ef 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_0.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_0.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/grid.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_1.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_1.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_1.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_1.snap index 09663484..6a43da94 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_1.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_1.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/grid.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_2.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_2.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_2.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_2.snap index 05d80b1d..5250338a 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_2.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_2.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/grid.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_3.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_3.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_3.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_3.snap index 64c3e7b9..f33ce414 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_3.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_3.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/grid.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_4.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_4.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_4.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_4.snap index dcb417ae..411bcec5 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_4.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_4.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/grid.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_5.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_5.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_5.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_5.snap index d044691c..5d16f7d6 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_5.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_5.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/grid.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_0.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_0.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_0.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_0.snap index f5bd11a5..a8250e29 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_0.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_0.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_1.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_1.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_1.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_1.snap index 148b8732..8dbcd8fe 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_1.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_1.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_10.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_10.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_10.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_10.snap index 7cbb8545..8fddefa0 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_10.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_10.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_11.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_11.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_11.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_11.snap index 1201290b..df92663d 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_11.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_11.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_12.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_12.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_12.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_12.snap index fc15da63..6373f0d5 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_12.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_12.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_13.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_13.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_13.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_13.snap index ee8e30fb..d250858f 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_13.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_13.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_14.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_14.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_14.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_14.snap index 2bdf1956..7d86b997 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_14.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_14.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_2.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_2.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_2.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_2.snap index 91a9a2e6..d751b7b5 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_2.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_2.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_3.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_3.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_3.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_3.snap index c3881efb..0ee924ac 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_3.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_3.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_4.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_4.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_4.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_4.snap index 089f3d7d..7c819279 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_4.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_4.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_5.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_5.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_5.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_5.snap index 6cf729c1..3d14e042 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_5.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_5.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_6.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_6.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_6.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_6.snap index 3c6a1c77..20a8aecd 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_6.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_6.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_7.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_7.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_7.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_7.snap index 0c4c5cac..4a980ffc 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_7.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_7.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_8.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_8.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_8.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_8.snap index 9e9ac891..413f7c8e 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_8.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_8.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_9.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_9.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_9.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_9.snap index 852203c8..6759d124 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_9.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_9.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest3_0.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest3_0.snap similarity index 98% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest3_0.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest3_0.snap index 6902cf25..0f9c5ce6 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest3_0.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest3_0.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_0.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_0.snap similarity index 99% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_0.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_0.snap index 18cdd01b..86e447a9 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_0.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_0.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_1.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_1.snap similarity index 99% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_1.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_1.snap index 7618e82b..4381306b 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_1.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_1.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_2.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_2.snap similarity index 99% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_2.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_2.snap index 0873ec23..b65ed724 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_2.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_2.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_3.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_3.snap similarity index 99% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_3.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_3.snap index 049f5083..d18554a8 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_3.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_3.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_4.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_4.snap similarity index 99% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_4.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_4.snap index e7eb978f..c45da961 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_4.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_4.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_5.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_5.snap similarity index 99% rename from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_5.snap rename to zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_5.snap index 9b54cbc8..00b8b310 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_5.snap +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_5.snap @@ -1,5 +1,5 @@ --- -source: src/client/panes/./unit/grid_tests.rs +source: zellij-server/src/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- diff --git a/src/common/pty.rs b/zellij-server/src/pty.rs similarity index 59% rename from src/common/pty.rs rename to zellij-server/src/pty.rs index 3c471d6a..803c0424 100644 --- a/src/common/pty.rs +++ b/zellij-server/src/pty.rs @@ -1,73 +1,31 @@ -use async_std::stream::*; -use async_std::task; -use async_std::task::*; +use zellij_utils::async_std; + +use async_std::future::timeout as async_timeout; +use async_std::task::{self, JoinHandle}; use std::collections::HashMap; use std::os::unix::io::RawFd; use std::path::PathBuf; -use std::pin::*; use std::time::{Duration, Instant}; -use crate::client::panes::PaneId; -use crate::common::errors::{get_current_ctx, ContextType, PtyContext}; -use crate::common::screen::ScreenInstruction; -use crate::common::thread_bus::{Bus, ThreadSenders}; -use crate::layout::Layout; -use crate::os_input_output::ServerOsApi; -use crate::server::ServerInstruction; -use crate::utils::logging::debug_to_file; -use crate::wasm_vm::PluginInstruction; - -pub struct ReadFromPid { - pid: RawFd, - os_input: Box, -} - -impl ReadFromPid { - pub fn new(pid: &RawFd, os_input: Box) -> ReadFromPid { - ReadFromPid { - pid: *pid, - os_input, - } - } -} - -impl Stream for ReadFromPid { - type Item = Vec; - fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - let mut read_buffer = [0; 65535]; - let pid = self.pid; - let read_result = &self.os_input.read_from_tty_stdout(pid, &mut read_buffer); - match read_result { - Ok(res) => { - if *res == 0 { - // indicates end of file - Poll::Ready(None) - } else { - let res = Some(read_buffer[..*res].to_vec()); - Poll::Ready(res) - } - } - Err(e) => { - match e { - nix::Error::Sys(errno) => { - if *errno == nix::errno::Errno::EAGAIN { - Poll::Ready(Some(vec![])) // TODO: better with timeout waker somehow - } else { - Poll::Ready(None) - } - } - _ => Poll::Ready(None), - } - } - } - } -} +use crate::{ + os_input_output::{AsyncReader, Pid, ServerOsApi}, + panes::PaneId, + screen::ScreenInstruction, + thread_bus::{Bus, ThreadSenders}, + ui::layout::Layout, + wasm_vm::PluginInstruction, + ServerInstruction, +}; +use zellij_utils::{ + errors::{get_current_ctx, ContextType, PtyContext}, + logging::debug_to_file, +}; pub type VteBytes = Vec; /// Instructions related to PTYs (pseudoterminals). #[derive(Clone, Debug)] -pub enum PtyInstruction { +pub(crate) enum PtyInstruction { SpawnTerminal(Option), SpawnTerminalVertically(Option), SpawnTerminalHorizontally(Option), @@ -77,17 +35,31 @@ pub enum PtyInstruction { Exit, } -pub struct Pty { +impl From<&PtyInstruction> for PtyContext { + fn from(pty_instruction: &PtyInstruction) -> Self { + match *pty_instruction { + PtyInstruction::SpawnTerminal(_) => PtyContext::SpawnTerminal, + PtyInstruction::SpawnTerminalVertically(_) => PtyContext::SpawnTerminalVertically, + PtyInstruction::SpawnTerminalHorizontally(_) => PtyContext::SpawnTerminalHorizontally, + PtyInstruction::ClosePane(_) => PtyContext::ClosePane, + PtyInstruction::CloseTab(_) => PtyContext::CloseTab, + PtyInstruction::NewTab => PtyContext::NewTab, + PtyInstruction::Exit => PtyContext::Exit, + } + } +} + +pub(crate) struct Pty { pub bus: Bus, - pub id_to_child_pid: HashMap, + pub id_to_child_pid: HashMap, debug_to_file: bool, task_handles: HashMap>, } -pub fn pty_thread_main(mut pty: Pty, maybe_layout: Option) { +pub(crate) fn pty_thread_main(mut pty: Pty, maybe_layout: Option) { loop { let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel"); - err_ctx.add_call(ContextType::Pty(PtyContext::from(&event))); + err_ctx.add_call(ContextType::Pty((&event).into())); match event { PtyInstruction::SpawnTerminal(file_to_open) => { let pid = pty.spawn_terminal(file_to_open); @@ -140,6 +112,42 @@ pub fn pty_thread_main(mut pty: Pty, maybe_layout: Option) { } } +enum ReadResult { + Ok(usize), + Timeout, + Err(std::io::Error), +} + +impl From> for ReadResult { + fn from(e: std::io::Result) -> ReadResult { + match e { + Err(e) => ReadResult::Err(e), + Ok(n) => ReadResult::Ok(n), + } + } +} + +async fn deadline_read( + reader: &mut dyn AsyncReader, + deadline: Option, + buf: &mut [u8], +) -> ReadResult { + if let Some(deadline) = deadline { + let timeout = deadline.checked_duration_since(Instant::now()); + if let Some(timeout) = timeout { + match async_timeout(timeout, reader.read(buf)).await { + Ok(res) => res.into(), + _ => ReadResult::Timeout, + } + } else { + // deadline has already elapsed + ReadResult::Timeout + } + } else { + reader.read(buf).await.into() + } +} + fn stream_terminal_bytes( pid: RawFd, senders: ThreadSenders, @@ -150,53 +158,43 @@ fn stream_terminal_bytes( task::spawn({ async move { err_ctx.add_call(ContextType::AsyncTask); - let mut terminal_bytes = ReadFromPid::new(&pid, os_input); - let mut last_byte_receive_time: Option = None; - let mut pending_render = false; - let max_render_pause = Duration::from_millis(30); + // After a successful read, we keep on reading additional data up to a duration of + // `render_pause`. This is in order to batch up PtyBytes before rendering them. + // Once `render_deadline` has elapsed, we send Render. + let render_pause = Duration::from_millis(30); + let mut render_deadline = None; - while let Some(bytes) = terminal_bytes.next().await { - let bytes_is_empty = bytes.is_empty(); - if debug { - for byte in bytes.iter() { - debug_to_file(*byte, pid).unwrap(); - } - } - if !bytes_is_empty { - let _ = senders.send_to_screen(ScreenInstruction::PtyBytes(pid, bytes)); - // for UX reasons, if we got something on the wire, we only send the render notice if: - // 1. there aren't any more bytes on the wire afterwards - // 2. a certain period (currently 30ms) has elapsed since the last render - // (otherwise if we get a large amount of data, the display would hang - // until it's done) - // 3. the stream has ended, and so we render 1 last time - match last_byte_receive_time.as_mut() { - Some(receive_time) => { - if receive_time.elapsed() > max_render_pause { - pending_render = false; - let _ = senders.send_to_screen(ScreenInstruction::Render); - last_byte_receive_time = Some(Instant::now()); - } else { - pending_render = true; - } - } - None => { - last_byte_receive_time = Some(Instant::now()); - pending_render = true; - } - }; - } else { - if pending_render { - pending_render = false; + let mut buf = [0u8; 65536]; + let mut async_reader = os_input.async_file_reader(pid); + loop { + match deadline_read(async_reader.as_mut(), render_deadline, &mut buf).await { + ReadResult::Ok(0) | ReadResult::Err(_) => break, // EOF or error + ReadResult::Timeout => { let _ = senders.send_to_screen(ScreenInstruction::Render); + // next read does not need a deadline as we just rendered everything + render_deadline = None; + + // yield so Screen thread has some time to render before send additional + // PtyBytes. + task::sleep(Duration::from_millis(10)).await; + } + ReadResult::Ok(n_bytes) => { + let bytes = &buf[..n_bytes]; + if debug { + let _ = debug_to_file(bytes, pid); + } + let _ = senders + .send_to_screen(ScreenInstruction::PtyBytes(pid, bytes.to_vec())); + // if we already have a render_deadline we keep it, otherwise we set it + // to the duration of `render_pause`. + render_deadline.get_or_insert(Instant::now() + render_pause); } - last_byte_receive_time = None; - task::sleep(::std::time::Duration::from_millis(10)).await; } } - senders.send_to_screen(ScreenInstruction::Render).unwrap(); - #[cfg(not(test))] + let _ = senders.send_to_screen(ScreenInstruction::Render); + + #[cfg(not(any(feature = "test", test)))] // this is a little hacky, and is because the tests end the file as soon as // we read everything, rather than hanging until there is new data // a better solution would be to fix the test fakes, but this will do for now @@ -217,7 +215,7 @@ impl Pty { } } pub fn spawn_terminal(&mut self, file_to_open: Option) -> RawFd { - let (pid_primary, pid_secondary): (RawFd, RawFd) = self + let (pid_primary, pid_secondary): (RawFd, Pid) = self .bus .os_input .as_mut() @@ -237,7 +235,7 @@ impl Pty { let total_panes = layout.total_terminal_panes(); let mut new_pane_pids = vec![]; for _ in 0..total_panes { - let (pid_primary, pid_secondary): (RawFd, RawFd) = + let (pid_primary, pid_secondary): (RawFd, Pid) = self.bus.os_input.as_mut().unwrap().spawn_terminal(None); self.id_to_child_pid.insert(pid_primary, pid_secondary); new_pane_pids.push(pid_primary); diff --git a/src/server/route.rs b/zellij-server/src/route.rs similarity index 70% rename from src/server/route.rs rename to zellij-server/src/route.rs index bd4b1b60..8dcd98ff 100644 --- a/src/server/route.rs +++ b/zellij-server/src/route.rs @@ -1,23 +1,27 @@ use std::sync::{Arc, RwLock}; -use zellij_tile::data::{Event, PluginCapabilities}; +use zellij_utils::zellij_tile::data::Event; -use crate::common::errors::{ContextType, ServerContext}; -use crate::common::input::actions::{Action, Direction}; -use crate::common::input::handler::get_mode_info; -use crate::common::os_input_output::ServerOsApi; -use crate::common::pty::PtyInstruction; -use crate::common::screen::ScreenInstruction; -use crate::common::thread_bus::SenderWithContext; -use crate::common::wasm_vm::PluginInstruction; -use crate::server::{ServerInstruction, SessionMetaData}; +use crate::{ + os_input_output::ServerOsApi, pty::PtyInstruction, screen::ScreenInstruction, + wasm_vm::PluginInstruction, ServerInstruction, SessionMetaData, SessionState, +}; +use zellij_utils::{ + channels::SenderWithContext, + input::{ + actions::{Action, Direction}, + get_mode_info, + }, + ipc::{ClientToServerMsg, ExitReason, ServerToClientMsg}, +}; fn route_action( action: Action, session: &SessionMetaData, os_input: &dyn ServerOsApi, - capabilities: PluginCapabilities, -) { + to_server: &SenderWithContext, +) -> bool { + let mut should_break = false; match action { Action::Write(val) => { session @@ -31,11 +35,14 @@ fn route_action( } Action::SwitchToMode(mode) => { let palette = os_input.load_palette(); + // TODO: use the palette from the client and remove it from the server os api + // this is left here as a stop gap measure until we shift some code around + // to allow for this session .senders .send_to_plugin(PluginInstruction::Update( None, - Event::ModeUpdate(get_mode_info(mode, palette, capabilities)), + Event::ModeUpdate(get_mode_info(mode, palette, session.capabilities)), )) .unwrap(); session @@ -43,7 +50,7 @@ fn route_action( .send_to_screen(ScreenInstruction::ChangeMode(get_mode_info( mode, palette, - capabilities, + session.capabilities, ))) .unwrap(); session @@ -181,35 +188,39 @@ fn route_action( .send_to_screen(ScreenInstruction::UpdateTabName(c)) .unwrap(); } + Action::Quit => { + to_server.send(ServerInstruction::ClientExit).unwrap(); + should_break = true; + } + Action::Detach => { + to_server.send(ServerInstruction::DetachSession).unwrap(); + should_break = true; + } Action::NoOp => {} - Action::Quit => panic!("Received unexpected action"), } + should_break } -pub fn route_thread_main( - sessions: Arc>>, - mut os_input: Box, +pub(crate) fn route_thread_main( + session_data: Arc>>, + session_state: Arc>, + os_input: Box, to_server: SenderWithContext, - capabilities: PluginCapabilities, ) { loop { - let (instruction, mut err_ctx) = os_input.recv_from_client(); - err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction))); - let rlocked_sessions = sessions.read().unwrap(); + let (instruction, err_ctx) = os_input.recv_from_client(); + err_ctx.update_thread_ctx(); + let rlocked_sessions = session_data.read().unwrap(); + match instruction { - ServerInstruction::ClientExit => { - to_server.send(instruction).unwrap(); - break; + ClientToServerMsg::Action(action) => { + if let Some(rlocked_sessions) = rlocked_sessions.as_ref() { + if route_action(action, rlocked_sessions, &*os_input, &to_server) { + break; + } + } } - ServerInstruction::Action(action) => { - route_action( - action, - rlocked_sessions.as_ref().unwrap(), - &*os_input, - capabilities, - ); - } - ServerInstruction::TerminalResize(new_size) => { + ClientToServerMsg::TerminalResize(new_size) => { rlocked_sessions .as_ref() .unwrap() @@ -217,13 +228,25 @@ pub fn route_thread_main( .send_to_screen(ScreenInstruction::TerminalResize(new_size)) .unwrap(); } - ServerInstruction::NewClient(..) => { - os_input.add_client_sender(); - to_server.send(instruction).unwrap(); + ClientToServerMsg::NewClient(..) => { + if *session_state.read().unwrap() != SessionState::Uninitialized { + os_input.send_to_temp_client(ServerToClientMsg::Exit(ExitReason::Error( + "Cannot add new client".into(), + ))); + } else { + os_input.add_client_sender(); + to_server.send(instruction.into()).unwrap(); + } } - _ => { - to_server.send(instruction).unwrap(); + ClientToServerMsg::AttachClient(_, force) => { + if *session_state.read().unwrap() == SessionState::Attached && !force { + os_input.send_to_temp_client(ServerToClientMsg::Exit(ExitReason::CannotAttach)); + } else { + os_input.add_client_sender(); + to_server.send(instruction.into()).unwrap(); + } } + ClientToServerMsg::ClientExited => break, } } } diff --git a/src/common/screen.rs b/zellij-server/src/screen.rs similarity index 79% rename from src/common/screen.rs rename to zellij-server/src/screen.rs index dbdccb86..48be44bf 100644 --- a/src/common/screen.rs +++ b/zellij-server/src/screen.rs @@ -3,23 +3,30 @@ use std::collections::BTreeMap; use std::os::unix::io::RawFd; use std::str; +use std::sync::{Arc, RwLock}; -use crate::common::input::options::Options; -use crate::common::pty::{PtyInstruction, VteBytes}; -use crate::common::thread_bus::Bus; -use crate::errors::{ContextType, ScreenContext}; -use crate::layout::Layout; -use crate::panes::PaneId; -use crate::panes::PositionAndSize; -use crate::server::ServerInstruction; -use crate::tab::Tab; -use crate::wasm_vm::PluginInstruction; +use zellij_utils::zellij_tile; +use crate::{ + panes::PaneId, + pty::{PtyInstruction, VteBytes}, + tab::Tab, + thread_bus::Bus, + ui::layout::Layout, + wasm_vm::PluginInstruction, + ServerInstruction, SessionState, +}; use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, PluginCapabilities, TabInfo}; +use zellij_utils::{ + errors::{ContextType, ScreenContext}, + input::options::Options, + ipc::ClientAttributes, + pane_size::PositionAndSize, +}; /// Instructions that can be sent to the [`Screen`]. #[derive(Debug, Clone)] -pub enum ScreenInstruction { +pub(crate) enum ScreenInstruction { PtyBytes(RawFd, VteBytes), Render, NewPane(PaneId), @@ -63,9 +70,61 @@ pub enum ScreenInstruction { ChangeMode(ModeInfo), } +impl From<&ScreenInstruction> for ScreenContext { + fn from(screen_instruction: &ScreenInstruction) -> Self { + match *screen_instruction { + ScreenInstruction::PtyBytes(..) => ScreenContext::HandlePtyBytes, + ScreenInstruction::Render => ScreenContext::Render, + ScreenInstruction::NewPane(_) => ScreenContext::NewPane, + ScreenInstruction::HorizontalSplit(_) => ScreenContext::HorizontalSplit, + ScreenInstruction::VerticalSplit(_) => ScreenContext::VerticalSplit, + ScreenInstruction::WriteCharacter(_) => ScreenContext::WriteCharacter, + ScreenInstruction::ResizeLeft => ScreenContext::ResizeLeft, + ScreenInstruction::ResizeRight => ScreenContext::ResizeRight, + ScreenInstruction::ResizeDown => ScreenContext::ResizeDown, + ScreenInstruction::ResizeUp => ScreenContext::ResizeUp, + ScreenInstruction::SwitchFocus => ScreenContext::SwitchFocus, + ScreenInstruction::FocusNextPane => ScreenContext::FocusNextPane, + ScreenInstruction::FocusPreviousPane => ScreenContext::FocusPreviousPane, + ScreenInstruction::MoveFocusLeft => ScreenContext::MoveFocusLeft, + ScreenInstruction::MoveFocusLeftOrPreviousTab => { + ScreenContext::MoveFocusLeftOrPreviousTab + } + ScreenInstruction::MoveFocusDown => ScreenContext::MoveFocusDown, + ScreenInstruction::MoveFocusUp => ScreenContext::MoveFocusUp, + ScreenInstruction::MoveFocusRight => ScreenContext::MoveFocusRight, + ScreenInstruction::MoveFocusRightOrNextTab => ScreenContext::MoveFocusRightOrNextTab, + ScreenInstruction::Exit => ScreenContext::Exit, + ScreenInstruction::ScrollUp => ScreenContext::ScrollUp, + ScreenInstruction::ScrollDown => ScreenContext::ScrollDown, + ScreenInstruction::PageScrollUp => ScreenContext::PageScrollUp, + ScreenInstruction::PageScrollDown => ScreenContext::PageScrollDown, + ScreenInstruction::ClearScroll => ScreenContext::ClearScroll, + ScreenInstruction::CloseFocusedPane => ScreenContext::CloseFocusedPane, + ScreenInstruction::ToggleActiveTerminalFullscreen => { + ScreenContext::ToggleActiveTerminalFullscreen + } + ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable, + ScreenInstruction::SetInvisibleBorders(..) => ScreenContext::SetInvisibleBorders, + ScreenInstruction::SetMaxHeight(..) => ScreenContext::SetMaxHeight, + ScreenInstruction::ClosePane(_) => ScreenContext::ClosePane, + ScreenInstruction::ApplyLayout(..) => ScreenContext::ApplyLayout, + ScreenInstruction::NewTab(_) => ScreenContext::NewTab, + ScreenInstruction::SwitchTabNext => ScreenContext::SwitchTabNext, + ScreenInstruction::SwitchTabPrev => ScreenContext::SwitchTabPrev, + ScreenInstruction::CloseTab => ScreenContext::CloseTab, + ScreenInstruction::GoToTab(_) => ScreenContext::GoToTab, + ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName, + ScreenInstruction::TerminalResize(_) => ScreenContext::TerminalResize, + ScreenInstruction::ChangeMode(_) => ScreenContext::ChangeMode, + ScreenInstruction::ToggleActiveSyncTab => ScreenContext::ToggleActiveSyncTab, + } + } +} + /// A [`Screen`] holds multiple [`Tab`]s, each one holding multiple [`panes`](crate::client::panes). /// It only directly controls which tab is active, delegating the rest to the individual `Tab`. -pub struct Screen { +pub(crate) struct Screen { /// A Bus for sending and receiving messages with the other threads. pub bus: Bus, /// An optional maximal amount of panes allowed per [`Tab`] in this [`Screen`] instance. @@ -73,33 +132,35 @@ pub struct Screen { /// A map between this [`Screen`]'s tabs and their ID/key. tabs: BTreeMap, /// The full size of this [`Screen`]. - full_screen_ws: PositionAndSize, + position_and_size: PositionAndSize, /// The index of this [`Screen`]'s active [`Tab`]. active_tab_index: Option, mode_info: ModeInfo, input_mode: InputMode, colors: Palette, + session_state: Arc>, } impl Screen { /// Creates and returns a new [`Screen`]. pub fn new( bus: Bus, - full_screen_ws: &PositionAndSize, + client_attributes: &ClientAttributes, max_panes: Option, mode_info: ModeInfo, input_mode: InputMode, - colors: Palette, + session_state: Arc>, ) -> Self { Screen { bus, max_panes, - full_screen_ws: *full_screen_ws, + position_and_size: client_attributes.position_and_size, + colors: client_attributes.palette, active_tab_index: None, tabs: BTreeMap::new(), mode_info, input_mode, - colors, + session_state, } } @@ -112,7 +173,7 @@ impl Screen { tab_index, position, String::new(), - &self.full_screen_ws, + &self.position_and_size, self.bus.os_input.as_ref().unwrap().clone(), self.bus.senders.clone(), self.max_panes, @@ -120,6 +181,7 @@ impl Screen { self.mode_info.clone(), self.input_mode, self.colors, + self.session_state.clone(), ); self.active_tab_index = Some(tab_index); self.tabs.insert(tab_index, tab); @@ -204,10 +266,12 @@ impl Screen { .unwrap(); if self.tabs.is_empty() { self.active_tab_index = None; - self.bus - .senders - .send_to_server(ServerInstruction::Render(None)) - .unwrap(); + if *self.session_state.read().unwrap() == SessionState::Attached { + self.bus + .senders + .send_to_server(ServerInstruction::Render(None)) + .unwrap(); + } } else { for t in self.tabs.values_mut() { if t.position > active_tab.position { @@ -219,7 +283,7 @@ impl Screen { } pub fn resize_to_screen(&mut self, new_screen_size: PositionAndSize) { - self.full_screen_ws = new_screen_size; + self.position_and_size = new_screen_size; for (_, tab) in self.tabs.iter_mut() { tab.resize_whole_tab(new_screen_size); } @@ -229,6 +293,9 @@ impl Screen { /// Renders this [`Screen`], which amounts to rendering its active [`Tab`]. pub fn render(&mut self) { + if *self.session_state.read().unwrap() != SessionState::Attached { + return; + } if let Some(active_tab) = self.get_active_tab_mut() { if active_tab.get_active_pane().is_some() { active_tab.render(); @@ -268,7 +335,7 @@ impl Screen { tab_index, position, String::new(), - &self.full_screen_ws, + &self.position_and_size, self.bus.os_input.as_ref().unwrap().clone(), self.bus.senders.clone(), self.max_panes, @@ -276,6 +343,7 @@ impl Screen { self.mode_info.clone(), self.input_mode, self.colors, + self.session_state.clone(), ); tab.apply_layout(layout, new_pids); self.active_tab_index = Some(tab_index); @@ -318,6 +386,7 @@ impl Screen { self.update_tabs(); } pub fn change_mode(&mut self, mode_info: ModeInfo) { + self.colors = mode_info.palette; self.mode_info = mode_info; for tab in self.tabs.values_mut() { tab.mode_info = self.mode_info.clone(); @@ -325,22 +394,25 @@ impl Screen { } } -pub fn screen_thread_main( +// The box is here in order to make the +// NewClient enum smaller +#[allow(clippy::boxed_local)] +pub(crate) fn screen_thread_main( bus: Bus, max_panes: Option, - full_screen_ws: PositionAndSize, - config_options: Options, + client_attributes: ClientAttributes, + config_options: Box, + session_state: Arc>, ) { - let colors = bus.os_input.as_ref().unwrap().load_palette(); let capabilities = config_options.simplified_ui; let default_mode = config_options.default_mode.unwrap_or_default(); let mut screen = Screen::new( bus, - &full_screen_ws, + &client_attributes, max_panes, ModeInfo { - palette: colors, + palette: client_attributes.palette, capabilities: PluginCapabilities { arrow_fonts: capabilities, }, @@ -348,14 +420,14 @@ pub fn screen_thread_main( ..ModeInfo::default() }, default_mode, - colors, + session_state, ); loop { let (event, mut err_ctx) = screen .bus .recv() .expect("failed to receive event on channel"); - err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event))); + err_ctx.add_call(ContextType::Screen((&event).into())); match event { ScreenInstruction::PtyBytes(pid, vte_bytes) => { let active_tab = screen.get_active_tab_mut().unwrap(); diff --git a/src/client/tab.rs b/zellij-server/src/tab.rs similarity index 98% rename from src/client/tab.rs rename to zellij-server/src/tab.rs index 29a41530..4a0db153 100644 --- a/src/client/tab.rs +++ b/zellij-server/src/tab.rs @@ -1,26 +1,27 @@ //! `Tab`s holds multiple panes. It tracks their coordinates (x/y) and size, //! as well as how they should be resized -use crate::client::pane_resizer::PaneResizer; -use crate::common::input::handler::parse_keys; -use crate::common::thread_bus::ThreadSenders; -use crate::layout::Layout; -use crate::os_input_output::ServerOsApi; -use crate::panes::{PaneId, PositionAndSize, TerminalPane}; -use crate::pty::{PtyInstruction, VteBytes}; -use crate::server::ServerInstruction; -use crate::utils::shared::adjust_to_size; -use crate::wasm_vm::PluginInstruction; -use crate::{boundaries::Boundaries, panes::PluginPane}; +use zellij_utils::{serde, zellij_tile}; + +use crate::{ + os_input_output::ServerOsApi, + panes::{PaneId, PluginPane, TerminalPane}, + pty::{PtyInstruction, VteBytes}, + thread_bus::ThreadSenders, + ui::{boundaries::Boundaries, layout::Layout, pane_resizer::PaneResizer}, + wasm_vm::PluginInstruction, + ServerInstruction, SessionState, +}; use serde::{Deserialize, Serialize}; use std::os::unix::io::RawFd; -use std::sync::mpsc::channel; +use std::sync::{mpsc::channel, Arc, RwLock}; use std::time::Instant; use std::{ cmp::Reverse, collections::{BTreeMap, HashSet}, }; use zellij_tile::data::{Event, InputMode, ModeInfo, Palette}; +use zellij_utils::{input::parse_keys, pane_size::PositionAndSize, shared::adjust_to_size}; const CURSOR_HEIGHT_WIDTH_RATIO: usize = 4; // this is not accurate and kind of a magic number, TODO: look into this @@ -59,7 +60,7 @@ fn split_horizontally_with_gap(rect: &PositionAndSize) -> (PositionAndSize, Posi (first_rect, second_rect) } -pub struct Tab { +pub(crate) struct Tab { pub index: usize, pub position: usize, pub name: String, @@ -73,13 +74,15 @@ pub struct Tab { pub senders: ThreadSenders, synchronize_is_active: bool, should_clear_display_before_rendering: bool, + session_state: Arc>, pub mode_info: ModeInfo, pub input_mode: InputMode, pub colors: Palette, } #[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct TabData { +#[serde(crate = "self::serde")] +pub(crate) struct TabData { /* subset of fields to publish to plugins */ pub position: usize, pub name: String, @@ -233,16 +236,17 @@ impl Tab { position: usize, name: String, full_screen_ws: &PositionAndSize, - mut os_api: Box, + os_api: Box, senders: ThreadSenders, max_panes: Option, pane_id: Option, mode_info: ModeInfo, input_mode: InputMode, colors: Palette, + session_state: Arc>, ) -> Self { let panes = if let Some(PaneId::Terminal(pid)) = pane_id { - let new_terminal = TerminalPane::new(pid, *full_screen_ws); + let new_terminal = TerminalPane::new(pid, *full_screen_ws, colors); os_api.set_terminal_size_using_fd( new_terminal.pid, new_terminal.columns() as u16, @@ -271,6 +275,7 @@ impl Tab { mode_info, input_mode, colors, + session_state, } } @@ -344,7 +349,7 @@ impl Tab { } else { // there are still panes left to fill, use the pids we received in this method let pid = new_pids.next().unwrap(); // if this crashes it means we got less pids than there are panes in this layout - let new_terminal = TerminalPane::new(*pid, *position_and_size); + let new_terminal = TerminalPane::new(*pid, *position_and_size, self.colors); self.os_api.set_terminal_size_using_fd( new_terminal.pid, new_terminal.columns() as u16, @@ -372,7 +377,7 @@ impl Tab { } if !self.has_panes() { if let PaneId::Terminal(term_pid) = pid { - let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws); + let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws, self.colors); self.os_api.set_terminal_size_using_fd( new_terminal.pid, new_terminal.columns() as u16, @@ -422,7 +427,7 @@ impl Tab { { if let PaneId::Terminal(term_pid) = pid { let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&terminal_ws); - let new_terminal = TerminalPane::new(term_pid, bottom_winsize); + let new_terminal = TerminalPane::new(term_pid, bottom_winsize, self.colors); self.os_api.set_terminal_size_using_fd( new_terminal.pid, bottom_winsize.columns as u16, @@ -442,7 +447,7 @@ impl Tab { } else if terminal_to_split.columns() > terminal_to_split.min_width() * 2 { if let PaneId::Terminal(term_pid) = pid { let (left_winsize, right_winsize) = split_vertically_with_gap(&terminal_ws); - let new_terminal = TerminalPane::new(term_pid, right_winsize); + let new_terminal = TerminalPane::new(term_pid, right_winsize, self.colors); self.os_api.set_terminal_size_using_fd( new_terminal.pid, right_winsize.columns as u16, @@ -470,7 +475,7 @@ impl Tab { } if !self.has_panes() { if let PaneId::Terminal(term_pid) = pid { - let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws); + let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws, self.colors); self.os_api.set_terminal_size_using_fd( new_terminal.pid, new_terminal.columns() as u16, @@ -500,7 +505,7 @@ impl Tab { active_pane.change_pos_and_size(&top_winsize); - let new_terminal = TerminalPane::new(term_pid, bottom_winsize); + let new_terminal = TerminalPane::new(term_pid, bottom_winsize, self.colors); self.os_api.set_terminal_size_using_fd( new_terminal.pid, bottom_winsize.columns as u16, @@ -527,7 +532,7 @@ impl Tab { } if !self.has_panes() { if let PaneId::Terminal(term_pid) = pid { - let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws); + let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws, self.colors); self.os_api.set_terminal_size_using_fd( new_terminal.pid, new_terminal.columns() as u16, @@ -557,7 +562,7 @@ impl Tab { active_pane.change_pos_and_size(&left_winsize); - let new_terminal = TerminalPane::new(term_pid, right_winsize); + let new_terminal = TerminalPane::new(term_pid, right_winsize, self.colors); self.os_api.set_terminal_size_using_fd( new_terminal.pid, right_winsize.columns as u16, @@ -625,9 +630,9 @@ impl Tab { match pane_id { PaneId::Terminal(active_terminal_id) => { let active_terminal = self.panes.get(&pane_id).unwrap(); - let mut adjusted_input = active_terminal.adjust_input_to_terminal(input_bytes); + let adjusted_input = active_terminal.adjust_input_to_terminal(input_bytes); self.os_api - .write_to_tty_stdin(active_terminal_id, &mut adjusted_input) + .write_to_tty_stdin(active_terminal_id, &adjusted_input) .expect("failed to write to terminal"); self.os_api .tcdrain(active_terminal_id) @@ -720,9 +725,12 @@ impl Tab { self.panes.iter().any(|(_, p)| p.contains_widechar()) } pub fn render(&mut self) { - if self.active_terminal.is_none() { + if self.active_terminal.is_none() + || *self.session_state.read().unwrap() != SessionState::Attached + { // we might not have an active terminal if we closed the last pane // in that case, we should not render as the app is exiting + // or if this session is not attached to a client, we do not have to render return; } // if any pane contain widechar, all pane in the same row will messup. We should render them every time diff --git a/zellij-server/src/thread_bus.rs b/zellij-server/src/thread_bus.rs new file mode 100644 index 00000000..afc1d875 --- /dev/null +++ b/zellij-server/src/thread_bus.rs @@ -0,0 +1,80 @@ +//! Definitions and helpers for sending and receiving messages between threads. + +use crate::{ + os_input_output::ServerOsApi, pty::PtyInstruction, screen::ScreenInstruction, + wasm_vm::PluginInstruction, ServerInstruction, +}; +use std::sync::mpsc; +use zellij_utils::{channels::SenderWithContext, errors::ErrorContext}; + +/// A container for senders to the different threads in zellij on the server side +#[derive(Clone)] +pub(crate) struct ThreadSenders { + pub to_screen: Option>, + pub to_pty: Option>, + pub to_plugin: Option>, + pub to_server: Option>, +} + +impl ThreadSenders { + pub fn send_to_screen( + &self, + instruction: ScreenInstruction, + ) -> Result<(), mpsc::SendError<(ScreenInstruction, ErrorContext)>> { + self.to_screen.as_ref().unwrap().send(instruction) + } + + pub fn send_to_pty( + &self, + instruction: PtyInstruction, + ) -> Result<(), mpsc::SendError<(PtyInstruction, ErrorContext)>> { + self.to_pty.as_ref().unwrap().send(instruction) + } + + pub fn send_to_plugin( + &self, + instruction: PluginInstruction, + ) -> Result<(), mpsc::SendError<(PluginInstruction, ErrorContext)>> { + self.to_plugin.as_ref().unwrap().send(instruction) + } + + pub fn send_to_server( + &self, + instruction: ServerInstruction, + ) -> Result<(), mpsc::SendError<(ServerInstruction, ErrorContext)>> { + self.to_server.as_ref().unwrap().send(instruction) + } +} + +/// A container for a receiver, OS input and the senders to a given thread +pub(crate) struct Bus { + pub receiver: mpsc::Receiver<(T, ErrorContext)>, + pub senders: ThreadSenders, + pub os_input: Option>, +} + +impl Bus { + pub fn new( + receiver: mpsc::Receiver<(T, ErrorContext)>, + to_screen: Option<&SenderWithContext>, + to_pty: Option<&SenderWithContext>, + to_plugin: Option<&SenderWithContext>, + to_server: Option<&SenderWithContext>, + os_input: Option>, + ) -> Self { + Bus { + receiver, + senders: ThreadSenders { + to_screen: to_screen.cloned(), + to_pty: to_pty.cloned(), + to_plugin: to_plugin.cloned(), + to_server: to_server.cloned(), + }, + os_input: os_input.clone(), + } + } + + pub fn recv(&self) -> Result<(T, ErrorContext), mpsc::RecvError> { + self.receiver.recv() + } +} diff --git a/src/client/boundaries.rs b/zellij-server/src/ui/boundaries.rs similarity index 98% rename from src/client/boundaries.rs rename to zellij-server/src/ui/boundaries.rs index 86fb37fc..42a80ace 100644 --- a/src/client/boundaries.rs +++ b/zellij-server/src/ui/boundaries.rs @@ -1,8 +1,10 @@ +use zellij_utils::zellij_tile; + use crate::tab::Pane; -use crate::utils::shared::colors; use ansi_term::Colour::{Fixed, RGB}; use std::collections::HashMap; use zellij_tile::data::{InputMode, Palette, PaletteColor}; +use zellij_utils::shared::colors; use std::fmt::{Display, Error, Formatter}; pub mod boundary_type { @@ -19,10 +21,10 @@ pub mod boundary_type { pub const CROSS: &str = "┼"; } -pub type BoundaryType = &'static str; // easy way to refer to boundary_type above +pub(crate) type BoundaryType = &'static str; // easy way to refer to boundary_type above #[derive(Clone, Copy, Debug)] -pub struct BoundarySymbol { +pub(crate) struct BoundarySymbol { boundary_type: BoundaryType, invisible: bool, color: Option, @@ -392,7 +394,7 @@ fn combine_symbols( } #[derive(PartialEq, Eq, Hash, Debug)] -pub struct Coordinates { +pub(crate) struct Coordinates { x: usize, y: usize, } @@ -403,7 +405,7 @@ impl Coordinates { } } -pub trait Rect { +pub(crate) trait Rect { fn x(&self) -> usize; fn y(&self) -> usize; fn rows(&self) -> usize; diff --git a/src/client/layout.rs b/zellij-server/src/ui/layout.rs similarity index 93% rename from src/client/layout.rs rename to zellij-server/src/ui/layout.rs index 3648ece9..a9af665f 100644 --- a/src/client/layout.rs +++ b/zellij-server/src/ui/layout.rs @@ -1,8 +1,10 @@ +use zellij_utils::{serde, serde_yaml}; + use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; use std::{fs::File, io::prelude::*}; -use crate::panes::PositionAndSize; +use zellij_utils::pane_size::PositionAndSize; fn split_space_to_parts_vertically( space_to_split: &PositionAndSize, @@ -167,19 +169,22 @@ fn split_space( } #[derive(Debug, Serialize, Deserialize, Clone)] -pub enum Direction { +#[serde(crate = "self::serde")] +pub(crate) enum Direction { Horizontal, Vertical, } #[derive(Debug, Serialize, Deserialize, Clone, Copy)] -pub enum SplitSize { +#[serde(crate = "self::serde")] +pub(crate) enum SplitSize { Percent(u8), // 1 to 100 Fixed(u16), // An absolute number of columns or rows } #[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Layout { +#[serde(crate = "self::serde")] +pub(crate) struct Layout { pub direction: Direction, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub parts: Vec, @@ -190,10 +195,9 @@ pub struct Layout { } impl Layout { - pub fn new(layout_path: &Path, data_dir: &Path) -> Self { - let layout_dir = data_dir.join("layouts/"); + pub fn new(layout_path: &Path) -> Self { let mut layout_file = File::open(&layout_path) - .or_else(|_| File::open(&layout_dir.join(&layout_path).with_extension("yaml"))) + .or_else(|_| File::open(&layout_path.with_extension("yaml"))) .unwrap_or_else(|_| panic!("cannot find layout {}", &layout_path.display())); let mut layout = String::new(); @@ -207,14 +211,8 @@ impl Layout { // It wants to use Path here, but that doesn't compile. #[allow(clippy::ptr_arg)] - pub fn from_defaults(layout_path: &PathBuf, data_dir: &Path) -> Self { - Self::new( - &data_dir - .join("layouts/") - .join(layout_path) - .with_extension("yaml"), - &data_dir, - ) + pub fn from_dir(layout: &PathBuf, data_dir: &Path) -> Self { + Self::new(&data_dir.join("layouts/").join(layout)) } pub fn total_terminal_panes(&self) -> usize { diff --git a/zellij-server/src/ui/mod.rs b/zellij-server/src/ui/mod.rs new file mode 100644 index 00000000..1e4c85fd --- /dev/null +++ b/zellij-server/src/ui/mod.rs @@ -0,0 +1,3 @@ +pub mod boundaries; +pub mod layout; +pub mod pane_resizer; diff --git a/src/client/pane_resizer.rs b/zellij-server/src/ui/pane_resizer.rs similarity index 99% rename from src/client/pane_resizer.rs rename to zellij-server/src/ui/pane_resizer.rs index 73678499..b2001945 100644 --- a/src/client/pane_resizer.rs +++ b/zellij-server/src/ui/pane_resizer.rs @@ -1,12 +1,11 @@ -use crate::os_input_output::ServerOsApi; -use crate::panes::{PaneId, PositionAndSize}; -use crate::tab::Pane; +use crate::{os_input_output::ServerOsApi, panes::PaneId, tab::Pane}; use std::{ cmp::Ordering, collections::{BTreeMap, HashSet}, }; +use zellij_utils::pane_size::PositionAndSize; -pub struct PaneResizer<'a> { +pub(crate) struct PaneResizer<'a> { panes: &'a mut BTreeMap>, os_api: &'a mut Box, } diff --git a/src/common/wasm_vm.rs b/zellij-server/src/wasm_vm.rs similarity index 90% rename from src/common/wasm_vm.rs rename to zellij-server/src/wasm_vm.rs index e5567285..53dceaa7 100644 --- a/src/common/wasm_vm.rs +++ b/zellij-server/src/wasm_vm.rs @@ -7,6 +7,8 @@ use std::sync::{mpsc::Sender, Arc, Mutex}; use std::thread; use std::time::{Duration, Instant}; +use zellij_utils::{serde, zellij_tile}; + use serde::{de::DeserializeOwned, Serialize}; use wasmer::{ imports, ChainableNamedResolver, Function, ImportObject, Instance, Module, Store, Value, @@ -15,14 +17,16 @@ use wasmer::{ use wasmer_wasi::{Pipe, WasiEnv, WasiState}; use zellij_tile::data::{Event, EventType, PluginIds}; -use crate::common::errors::{ContextType, PluginContext}; -use crate::common::pty::PtyInstruction; -use crate::common::screen::ScreenInstruction; -use crate::common::thread_bus::{Bus, ThreadSenders}; -use crate::common::PaneId; +use crate::{ + panes::PaneId, + pty::PtyInstruction, + screen::ScreenInstruction, + thread_bus::{Bus, ThreadSenders}, +}; +use zellij_utils::errors::{ContextType, PluginContext}; #[derive(Clone, Debug)] -pub enum PluginInstruction { +pub(crate) enum PluginInstruction { Load(Sender, PathBuf), Update(Option, Event), // Focused plugin / broadcast, event data Render(Sender, u32, usize, usize), // String buffer, plugin id, rows, cols @@ -30,8 +34,20 @@ pub enum PluginInstruction { Exit, } +impl From<&PluginInstruction> for PluginContext { + fn from(plugin_instruction: &PluginInstruction) -> Self { + match *plugin_instruction { + PluginInstruction::Load(..) => PluginContext::Load, + PluginInstruction::Update(..) => PluginContext::Update, + PluginInstruction::Render(..) => PluginContext::Render, + PluginInstruction::Unload(_) => PluginContext::Unload, + PluginInstruction::Exit => PluginContext::Exit, + } + } +} + #[derive(WasmerEnv, Clone)] -pub struct PluginEnv { +pub(crate) struct PluginEnv { pub plugin_id: u32, pub senders: ThreadSenders, pub wasi_env: WasiEnv, @@ -39,12 +55,12 @@ pub struct PluginEnv { } // Thread main -------------------------------------------------------------------------------------------------------- -pub fn wasm_thread_main(bus: Bus, store: Store, data_dir: PathBuf) { +pub(crate) fn wasm_thread_main(bus: Bus, store: Store, data_dir: PathBuf) { let mut plugin_id = 0; let mut plugin_map = HashMap::new(); loop { let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel"); - err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event))); + err_ctx.add_call(ContextType::Plugin((&event).into())); match event { PluginInstruction::Load(pid_tx, path) => { let plugin_dir = data_dir.join("plugins/"); @@ -126,7 +142,7 @@ pub fn wasm_thread_main(bus: Bus, store: Store, data_dir: Pat // Plugin API --------------------------------------------------------------------------------------------------------- -pub fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObject { +pub(crate) fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObject { macro_rules! zellij_export { ($($host_function:ident),+ $(,)?) => { imports! { diff --git a/zellij-tile-utils/Cargo.toml b/zellij-tile-utils/Cargo.toml index cee6df30..a69f53be 100644 --- a/zellij-tile-utils/Cargo.toml +++ b/zellij-tile-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zellij-tile-utils" -version = "0.11.0" +version = "0.12.0" authors = ["denis "] edition = "2018" description = "A utility library for Zellij plugins" diff --git a/zellij-tile/Cargo.toml b/zellij-tile/Cargo.toml index 60d30021..4728ec1e 100644 --- a/zellij-tile/Cargo.toml +++ b/zellij-tile/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zellij-tile" -version = "0.11.0" +version = "0.12.0" authors = ["Brooks J Rady "] edition = "2018" description = "A small client-side library for writing Zellij plugins" diff --git a/zellij-tile/src/data.rs b/zellij-tile/src/data.rs index cf9c8265..3bd926ca 100644 --- a/zellij-tile/src/data.rs +++ b/zellij-tile/src/data.rs @@ -60,6 +60,9 @@ pub enum InputMode { Scroll, #[serde(alias = "renametab")] RenameTab, + /// `Session` mode allows detaching sessions + #[serde(alias = "session")] + Session, } impl Default for InputMode { diff --git a/zellij-utils/Cargo.toml b/zellij-utils/Cargo.toml new file mode 100644 index 00000000..2fcccd7d --- /dev/null +++ b/zellij-utils/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "zellij-utils" +version = "0.12.0" +authors = ["Kunal Mohan "] +edition = "2018" +description = "A utility library for Zellij client and server" +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +backtrace = "0.3.55" +bincode = "1.3.1" +colors-transform = "0.2.5" +directories-next = "2.0" +interprocess = "1.1.1" +lazy_static = "1.4.0" +libc = "0.2" +nix = "0.19.1" +once_cell = "1.7.2" +serde = { version = "1.0", features = ["derive"] } +serde_yaml = "0.8" +signal-hook = "0.3" +strip-ansi-escapes = "0.1.0" +structopt = "0.3" +strum = "0.20.0" +termion = "1.5.0" +vte = "0.10.1" +zellij-tile = { path = "../zellij-tile/", version = "0.12.0" } + +[dependencies.async-std] +version = "1.3.0" +features = ["unstable"] + +[dev-dependencies] +tempfile = "3.2.0" + +[features] +test = [] diff --git a/zellij-utils/src/channels.rs b/zellij-utils/src/channels.rs new file mode 100644 index 00000000..8a97fa7d --- /dev/null +++ b/zellij-utils/src/channels.rs @@ -0,0 +1,65 @@ +//! Definitions and helpers for sending and receiving messages between threads. + +use async_std::task_local; +use std::cell::RefCell; +use std::sync::mpsc; + +use crate::errors::{get_current_ctx, ErrorContext}; + +/// An [MPSC](mpsc) asynchronous channel with added error context. +pub type ChannelWithContext = ( + mpsc::Sender<(T, ErrorContext)>, + mpsc::Receiver<(T, ErrorContext)>, +); +/// An [MPSC](mpsc) synchronous channel with added error context. +pub type SyncChannelWithContext = ( + mpsc::SyncSender<(T, ErrorContext)>, + mpsc::Receiver<(T, ErrorContext)>, +); + +/// Wrappers around the two standard [MPSC](mpsc) sender types, [`mpsc::Sender`] and [`mpsc::SyncSender`], with an additional [`ErrorContext`]. +#[derive(Clone)] +pub enum SenderType { + /// A wrapper around an [`mpsc::Sender`], adding an [`ErrorContext`]. + Sender(mpsc::Sender<(T, ErrorContext)>), + /// A wrapper around an [`mpsc::SyncSender`], adding an [`ErrorContext`]. + SyncSender(mpsc::SyncSender<(T, ErrorContext)>), +} + +/// Sends messages on an [MPSC](std::sync::mpsc) channel, along with an [`ErrorContext`], +/// synchronously or asynchronously depending on the underlying [`SenderType`]. +#[derive(Clone)] +pub struct SenderWithContext { + sender: SenderType, +} + +impl SenderWithContext { + pub fn new(sender: SenderType) -> Self { + Self { sender } + } + + /// Sends an event, along with the current [`ErrorContext`], on this + /// [`SenderWithContext`]'s channel. + pub fn send(&self, event: T) -> Result<(), mpsc::SendError<(T, ErrorContext)>> { + let err_ctx = get_current_ctx(); + match self.sender { + SenderType::Sender(ref s) => s.send((event, err_ctx)), + SenderType::SyncSender(ref s) => s.send((event, err_ctx)), + } + } +} + +unsafe impl Send for SenderWithContext {} +unsafe impl Sync for SenderWithContext {} + +thread_local!( + /// A key to some thread local storage (TLS) that holds a representation of the thread's call + /// stack in the form of an [`ErrorContext`]. + pub static OPENCALLS: RefCell = RefCell::default() +); + +task_local! { + /// A key to some task local storage that holds a representation of the task's call + /// stack in the form of an [`ErrorContext`]. + pub static ASYNCOPENCALLS: RefCell = RefCell::default() +} diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs new file mode 100644 index 00000000..bf7851c9 --- /dev/null +++ b/zellij-utils/src/cli.rs @@ -0,0 +1,82 @@ +use crate::consts::{ZELLIJ_CONFIG_DIR_ENV, ZELLIJ_CONFIG_FILE_ENV}; +use crate::input::options::Options; +use crate::setup::Setup; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use structopt::StructOpt; + +#[derive(StructOpt, Default, Debug, Clone, Serialize, Deserialize)] +#[structopt(name = "zellij")] +pub struct CliArgs { + /// Maximum panes on screen, caution: opening more panes will close old ones + #[structopt(long)] + pub max_panes: Option, + + /// Change where zellij looks for layouts and plugins + #[structopt(long, parse(from_os_str))] + pub data_dir: Option, + + /// Run server listening at the specified socket path + #[structopt(long, parse(from_os_str), hidden = true)] + pub server: Option, + + /// Specify name of a new session + #[structopt(long, short)] + pub session: Option, + + /// Name of a layout file in the layout directory + #[structopt(short, long, parse(from_os_str))] + pub layout: Option, + + /// Path to a layout yaml file + #[structopt(long, parse(from_os_str))] + pub layout_path: Option, + + /// Change where zellij looks for the configuration file + #[structopt(short, long, env=ZELLIJ_CONFIG_FILE_ENV, parse(from_os_str))] + pub config: Option, + + /// Change where zellij looks for the configuration directory + #[structopt(long, env=ZELLIJ_CONFIG_DIR_ENV, parse(from_os_str))] + pub config_dir: Option, + + #[structopt(subcommand)] + pub command: Option, + + #[structopt(short, long)] + pub debug: bool, +} + +#[derive(Debug, StructOpt, Clone, Serialize, Deserialize)] +pub enum Command { + /// Change the behaviour of zellij + #[structopt(name = "options")] + Options(Options), + + /// Setup zellij and check its configuration + #[structopt(name = "setup")] + Setup(Setup), + + /// Explore existing zellij sessions + #[structopt(flatten)] + Sessions(Sessions), +} + +#[derive(Debug, StructOpt, Clone, Serialize, Deserialize)] +pub enum Sessions { + /// List active sessions + #[structopt(alias = "ls")] + ListSessions, + + /// Attach to session + #[structopt(alias = "a")] + Attach { + /// Name of the session to attach to. + session_name: String, + + /// Force attach- session will detach from the other + /// zellij client (if any) and attach to this. + #[structopt(long, short)] + force: bool, + }, +} diff --git a/src/common/utils/consts.rs b/zellij-utils/src/consts.rs similarity index 75% rename from src/common/utils/consts.rs rename to zellij-utils/src/consts.rs index 479405c3..f22dd489 100644 --- a/src/common/utils/consts.rs +++ b/zellij-utils/src/consts.rs @@ -1,9 +1,10 @@ //! Zellij program-wide constants. -use crate::os_input_output::set_permissions; +use crate::shared::set_permissions; use directories_next::ProjectDirs; use lazy_static::lazy_static; use nix::unistd::Uid; +use once_cell::sync::OnceCell; use std::path::PathBuf; use std::{env, fs}; @@ -24,10 +25,10 @@ const fn system_default_data_dir() -> &'static str { lazy_static! { static ref UID: Uid = Uid::current(); - pub static ref SESSION_NAME: String = names::Generator::default().next().unwrap(); + pub static ref SESSION_NAME: OnceCell = OnceCell::new(); pub static ref ZELLIJ_PROJ_DIR: ProjectDirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap(); - pub static ref ZELLIJ_IPC_PIPE: PathBuf = { + pub static ref ZELLIJ_SOCK_DIR: PathBuf = { let mut ipc_dir = env::var("ZELLIJ_SOCKET_DIR").map_or_else( |_| { ZELLIJ_PROJ_DIR @@ -37,11 +38,15 @@ lazy_static! { PathBuf::from, ); ipc_dir.push(VERSION); - fs::create_dir_all(&ipc_dir).unwrap(); - set_permissions(&ipc_dir).unwrap(); - ipc_dir.push(&*SESSION_NAME); ipc_dir }; + 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).unwrap(); + sock_dir.push(SESSION_NAME.get().unwrap()); + sock_dir + }; pub static ref ZELLIJ_TMP_DIR: PathBuf = PathBuf::from("/tmp/zellij-".to_string() + &format!("{}", *UID)); pub static ref ZELLIJ_TMP_LOG_DIR: PathBuf = ZELLIJ_TMP_DIR.join("zellij-log"); @@ -49,6 +54,6 @@ lazy_static! { } pub const FEATURES: &[&str] = &[ - #[cfg(feature = "enable_automatic_asset_installation")] - "enable_automatic_asset_installation", + #[cfg(feature = "disable_automatic_asset_installation")] + "disable_automatic_asset_installation", ]; diff --git a/src/common/errors.rs b/zellij-utils/src/errors.rs similarity index 54% rename from src/common/errors.rs rename to zellij-utils/src/errors.rs index c2caf19d..b47ddb9a 100644 --- a/src/common/errors.rs +++ b/zellij-utils/src/errors.rs @@ -1,30 +1,24 @@ //! Error context system based on a thread-local representation of the call stack, itself based on //! the instructions that are sent between threads. +use crate::channels::{SenderWithContext, ASYNCOPENCALLS, OPENCALLS}; use serde::{Deserialize, Serialize}; - use std::fmt::{Display, Error, Formatter}; - -use crate::client::ClientInstruction; -use crate::common::thread_bus::{ASYNCOPENCALLS, OPENCALLS}; -use crate::pty::PtyInstruction; -use crate::screen::ScreenInstruction; -use crate::server::ServerInstruction; +use std::panic::PanicInfo; /// The maximum amount of calls an [`ErrorContext`] will keep track /// of in its stack representation. This is a per-thread maximum. const MAX_THREAD_CALL_STACK: usize = 6; -#[cfg(not(test))] -use super::thread_bus::SenderWithContext; -#[cfg(not(test))] -use std::panic::PanicInfo; +pub trait ErrorInstruction { + fn error(err: String) -> Self; +} + /// Custom panic handler/hook. Prints the [`ErrorContext`]. -#[cfg(not(test))] -pub fn handle_panic( - info: &PanicInfo<'_>, - send_app_instructions: &SenderWithContext, -) { +pub fn handle_panic(info: &PanicInfo<'_>, sender: &SenderWithContext) +where + T: ErrorInstruction + Clone, +{ use backtrace::Backtrace; use std::{process, thread}; let backtrace = Backtrace::new(); @@ -70,7 +64,7 @@ pub fn handle_panic( println!("{}", backtrace); process::exit(1); } else { - let _ = send_app_instructions.send(ClientInstruction::Error(backtrace)); + let _ = sender.send(T::error(backtrace)); } } @@ -103,6 +97,11 @@ impl ErrorContext { break; } } + self.update_thread_ctx() + } + + /// Updates the thread local [`ErrorContext`]. + pub fn update_thread_ctx(&self) { ASYNCOPENCALLS .try_with(|ctx| *ctx.borrow_mut() = *self) .unwrap_or_else(|_| OPENCALLS.with(|ctx| *ctx.borrow_mut() = *self)); @@ -221,59 +220,6 @@ pub enum ScreenContext { ChangeMode, } -// FIXME: Just deriving EnumDiscriminants from strum will remove the need for any of this!!! -impl From<&ScreenInstruction> for ScreenContext { - fn from(screen_instruction: &ScreenInstruction) -> Self { - match *screen_instruction { - ScreenInstruction::PtyBytes(..) => ScreenContext::HandlePtyBytes, - ScreenInstruction::Render => ScreenContext::Render, - ScreenInstruction::NewPane(_) => ScreenContext::NewPane, - ScreenInstruction::HorizontalSplit(_) => ScreenContext::HorizontalSplit, - ScreenInstruction::VerticalSplit(_) => ScreenContext::VerticalSplit, - ScreenInstruction::WriteCharacter(_) => ScreenContext::WriteCharacter, - ScreenInstruction::ResizeLeft => ScreenContext::ResizeLeft, - ScreenInstruction::ResizeRight => ScreenContext::ResizeRight, - ScreenInstruction::ResizeDown => ScreenContext::ResizeDown, - ScreenInstruction::ResizeUp => ScreenContext::ResizeUp, - ScreenInstruction::SwitchFocus => ScreenContext::SwitchFocus, - ScreenInstruction::FocusNextPane => ScreenContext::FocusNextPane, - ScreenInstruction::FocusPreviousPane => ScreenContext::FocusPreviousPane, - ScreenInstruction::MoveFocusLeft => ScreenContext::MoveFocusLeft, - ScreenInstruction::MoveFocusLeftOrPreviousTab => { - ScreenContext::MoveFocusLeftOrPreviousTab - } - ScreenInstruction::MoveFocusDown => ScreenContext::MoveFocusDown, - ScreenInstruction::MoveFocusUp => ScreenContext::MoveFocusUp, - ScreenInstruction::MoveFocusRight => ScreenContext::MoveFocusRight, - ScreenInstruction::MoveFocusRightOrNextTab => ScreenContext::MoveFocusRightOrNextTab, - ScreenInstruction::Exit => ScreenContext::Exit, - ScreenInstruction::ScrollUp => ScreenContext::ScrollUp, - ScreenInstruction::ScrollDown => ScreenContext::ScrollDown, - ScreenInstruction::PageScrollUp => ScreenContext::PageScrollUp, - ScreenInstruction::PageScrollDown => ScreenContext::PageScrollDown, - ScreenInstruction::ClearScroll => ScreenContext::ClearScroll, - ScreenInstruction::CloseFocusedPane => ScreenContext::CloseFocusedPane, - ScreenInstruction::ToggleActiveTerminalFullscreen => { - ScreenContext::ToggleActiveTerminalFullscreen - } - ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable, - ScreenInstruction::SetInvisibleBorders(..) => ScreenContext::SetInvisibleBorders, - ScreenInstruction::SetMaxHeight(..) => ScreenContext::SetMaxHeight, - ScreenInstruction::ClosePane(_) => ScreenContext::ClosePane, - ScreenInstruction::ApplyLayout(..) => ScreenContext::ApplyLayout, - ScreenInstruction::NewTab(_) => ScreenContext::NewTab, - ScreenInstruction::SwitchTabNext => ScreenContext::SwitchTabNext, - ScreenInstruction::SwitchTabPrev => ScreenContext::SwitchTabPrev, - ScreenInstruction::CloseTab => ScreenContext::CloseTab, - ScreenInstruction::GoToTab(_) => ScreenContext::GoToTab, - ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName, - ScreenInstruction::TerminalResize(_) => ScreenContext::TerminalResize, - ScreenInstruction::ChangeMode(_) => ScreenContext::ChangeMode, - ScreenInstruction::ToggleActiveSyncTab => ScreenContext::ToggleActiveSyncTab, - } - } -} - /// Stack call representations corresponding to the different types of [`PtyInstruction`]s. #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum PtyContext { @@ -286,24 +232,6 @@ pub enum PtyContext { Exit, } -impl From<&PtyInstruction> for PtyContext { - fn from(pty_instruction: &PtyInstruction) -> Self { - match *pty_instruction { - PtyInstruction::SpawnTerminal(_) => PtyContext::SpawnTerminal, - PtyInstruction::SpawnTerminalVertically(_) => PtyContext::SpawnTerminalVertically, - PtyInstruction::SpawnTerminalHorizontally(_) => PtyContext::SpawnTerminalHorizontally, - PtyInstruction::ClosePane(_) => PtyContext::ClosePane, - PtyInstruction::CloseTab(_) => PtyContext::CloseTab, - PtyInstruction::NewTab => PtyContext::NewTab, - PtyInstruction::Exit => PtyContext::Exit, - } - } -} - -// FIXME: This whole pattern *needs* a macro eventually, it's soul-crushing to write - -use crate::wasm_vm::PluginInstruction; - /// Stack call representations corresponding to the different types of [`PluginInstruction`]s. #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum PluginContext { @@ -314,18 +242,6 @@ pub enum PluginContext { Exit, } -impl From<&PluginInstruction> for PluginContext { - fn from(plugin_instruction: &PluginInstruction) -> Self { - match *plugin_instruction { - PluginInstruction::Load(..) => PluginContext::Load, - PluginInstruction::Update(..) => PluginContext::Update, - PluginInstruction::Render(..) => PluginContext::Render, - PluginInstruction::Unload(_) => PluginContext::Unload, - PluginInstruction::Exit => PluginContext::Exit, - } - } -} - /// Stack call representations corresponding to the different types of [`ClientInstruction`]s. #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum ClientContext { @@ -333,39 +249,17 @@ pub enum ClientContext { Error, UnblockInputThread, Render, -} - -impl From<&ClientInstruction> for ClientContext { - fn from(client_instruction: &ClientInstruction) -> Self { - match *client_instruction { - ClientInstruction::Exit => ClientContext::Exit, - ClientInstruction::Error(_) => ClientContext::Error, - ClientInstruction::Render(_) => ClientContext::Render, - ClientInstruction::UnblockInputThread => ClientContext::UnblockInputThread, - } - } + ServerError, } /// Stack call representations corresponding to the different types of [`ServerInstruction`]s. #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum ServerContext { NewClient, - Action, Render, - TerminalResize, UnblockInputThread, ClientExit, -} - -impl From<&ServerInstruction> for ServerContext { - fn from(server_instruction: &ServerInstruction) -> Self { - match *server_instruction { - ServerInstruction::NewClient(..) => ServerContext::NewClient, - ServerInstruction::Action(_) => ServerContext::Action, - ServerInstruction::TerminalResize(_) => ServerContext::TerminalResize, - ServerInstruction::Render(_) => ServerContext::Render, - ServerInstruction::UnblockInputThread => ServerContext::UnblockInputThread, - ServerInstruction::ClientExit => ServerContext::ClientExit, - } - } + Error, + DetachSession, + AttachClient, } diff --git a/src/common/input/actions.rs b/zellij-utils/src/input/actions.rs similarity index 88% rename from src/common/input/actions.rs rename to zellij-utils/src/input/actions.rs index b806aa3f..4e0ce0cb 100644 --- a/src/common/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -12,6 +12,10 @@ pub enum Direction { Down, } +// As these actions are bound to the default config, please +// do take care when refactoring - or renaming. +// They might need to be adjusted in the default config +// as well `../../../assets/config/default.yaml` /// Actions that can be bound to keys. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum Action { @@ -61,4 +65,6 @@ pub enum Action { CloseTab, GoToTab(u32), TabNameInput(Vec), + /// Detach session and exit + Detach, } diff --git a/src/common/input/config.rs b/zellij-utils/src/input/config.rs similarity index 96% rename from src/common/input/config.rs rename to zellij-utils/src/input/config.rs index 3dffe71c..c4cf0cd7 100644 --- a/src/common/input/config.rs +++ b/zellij-utils/src/input/config.rs @@ -7,8 +7,8 @@ use std::path::{Path, PathBuf}; use super::keybinds::{Keybinds, KeybindsFromYaml}; use super::options::Options; -use crate::cli::{CliArgs, ConfigCli}; -use crate::common::setup; +use crate::cli::{CliArgs, Command}; +use crate::setup; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; @@ -60,7 +60,7 @@ impl TryFrom<&CliArgs> for Config { return Config::new(&path); } - if let Some(ConfigCli::Setup(setup)) = opts.option.clone() { + if let Some(Command::Setup(ref setup)) = opts.command { if setup.clean { return Config::from_default_assets(); } @@ -177,9 +177,9 @@ mod config_test { #[test] fn try_from_cli_args_with_option_clean() { - use crate::common::setup::Setup; + use crate::setup::Setup; let mut opts = CliArgs::default(); - opts.option = Some(ConfigCli::Setup(Setup { + opts.command = Some(Command::Setup(Setup { clean: true, ..Setup::default() })); diff --git a/src/common/input/keybinds.rs b/zellij-utils/src/input/keybinds.rs similarity index 100% rename from src/common/input/keybinds.rs rename to zellij-utils/src/input/keybinds.rs diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs new file mode 100644 index 00000000..068e22bc --- /dev/null +++ b/zellij-utils/src/input/mod.rs @@ -0,0 +1,90 @@ +//! The way terminal input is handled. + +pub mod actions; +pub mod config; +pub mod keybinds; +pub mod options; + +use termion::input::TermRead; +use zellij_tile::data::{InputMode, Key, ModeInfo, Palette, PluginCapabilities}; + +/// Creates a [`Help`] struct indicating the current [`InputMode`] and its keybinds +/// (as pairs of [`String`]s). +// TODO this should probably be automatically generated in some way +pub fn get_mode_info( + mode: InputMode, + palette: Palette, + capabilities: PluginCapabilities, +) -> ModeInfo { + let mut keybinds: Vec<(String, String)> = vec![]; + match mode { + InputMode::Normal | InputMode::Locked => {} + InputMode::Resize => { + keybinds.push(("←↓↑→".to_string(), "Resize".to_string())); + } + InputMode::Pane => { + keybinds.push(("←↓↑→".to_string(), "Move focus".to_string())); + keybinds.push(("p".to_string(), "Next".to_string())); + keybinds.push(("n".to_string(), "New".to_string())); + keybinds.push(("d".to_string(), "Down split".to_string())); + keybinds.push(("r".to_string(), "Right split".to_string())); + keybinds.push(("x".to_string(), "Close".to_string())); + keybinds.push(("f".to_string(), "Fullscreen".to_string())); + } + InputMode::Tab => { + keybinds.push(("←↓↑→".to_string(), "Move focus".to_string())); + keybinds.push(("n".to_string(), "New".to_string())); + keybinds.push(("x".to_string(), "Close".to_string())); + keybinds.push(("r".to_string(), "Rename".to_string())); + keybinds.push(("s".to_string(), "Sync".to_string())); + } + InputMode::Scroll => { + keybinds.push(("↓↑".to_string(), "Scroll".to_string())); + keybinds.push(("PgUp/PgDn".to_string(), "Scroll Page".to_string())); + } + InputMode::RenameTab => { + keybinds.push(("Enter".to_string(), "when done".to_string())); + } + InputMode::Session => { + keybinds.push(("d".to_string(), "Detach".to_string())); + } + } + ModeInfo { + mode, + keybinds, + palette, + capabilities, + } +} + +pub fn parse_keys(input_bytes: &[u8]) -> Vec { + input_bytes.keys().flatten().map(cast_termion_key).collect() +} + +// FIXME: This is an absolutely cursed function that should be destroyed as soon +// as an alternative that doesn't touch zellij-tile can be developed... +pub fn cast_termion_key(event: termion::event::Key) -> Key { + match event { + termion::event::Key::Backspace => Key::Backspace, + termion::event::Key::Left => Key::Left, + termion::event::Key::Right => Key::Right, + termion::event::Key::Up => Key::Up, + termion::event::Key::Down => Key::Down, + termion::event::Key::Home => Key::Home, + termion::event::Key::End => Key::End, + termion::event::Key::PageUp => Key::PageUp, + termion::event::Key::PageDown => Key::PageDown, + termion::event::Key::BackTab => Key::BackTab, + termion::event::Key::Delete => Key::Delete, + termion::event::Key::Insert => Key::Insert, + termion::event::Key::F(n) => Key::F(n), + termion::event::Key::Char(c) => Key::Char(c), + termion::event::Key::Alt(c) => Key::Alt(c), + termion::event::Key::Ctrl(c) => Key::Ctrl(c), + termion::event::Key::Null => Key::Null, + termion::event::Key::Esc => Key::Esc, + _ => { + unimplemented!("Encountered an unknown key!") + } + } +} diff --git a/src/common/input/options.rs b/zellij-utils/src/input/options.rs similarity index 90% rename from src/common/input/options.rs rename to zellij-utils/src/input/options.rs index 0c5909d0..3476c2fb 100644 --- a/src/common/input/options.rs +++ b/zellij-utils/src/input/options.rs @@ -1,5 +1,5 @@ //! Handles cli and configuration options -use crate::cli::ConfigCli; +use crate::cli::Command; use serde::{Deserialize, Serialize}; use structopt::StructOpt; use zellij_tile::data::InputMode; @@ -47,8 +47,8 @@ impl Options { } } - pub fn from_cli(&self, other: Option) -> Options { - if let Some(ConfigCli::Options(options)) = other { + pub fn from_cli(&self, other: Option) -> Options { + if let Some(Command::Options(options)) = other { Options::merge(&self, options) } else { self.to_owned() diff --git a/src/common/input/unit/keybinds_test.rs b/zellij-utils/src/input/unit/keybinds_test.rs similarity index 100% rename from src/common/input/unit/keybinds_test.rs rename to zellij-utils/src/input/unit/keybinds_test.rs diff --git a/src/common/ipc.rs b/zellij-utils/src/ipc.rs similarity index 67% rename from src/common/ipc.rs rename to zellij-utils/src/ipc.rs index 190680dc..a160b782 100644 --- a/src/common/ipc.rs +++ b/zellij-utils/src/ipc.rs @@ -1,14 +1,21 @@ //! IPC stuff for starting to split things into a client and server model. -use crate::common::errors::{get_current_ctx, ErrorContext}; +use crate::cli::CliArgs; +use crate::pane_size::PositionAndSize; +use crate::{ + errors::{get_current_ctx, ErrorContext}, + input::{actions::Action, options::Options}, +}; use interprocess::local_socket::LocalSocketStream; use nix::unistd::dup; use serde::{Deserialize, Serialize}; -use std::collections::HashSet; +use std::fmt::{Display, Error, Formatter}; use std::io::{self, Write}; use std::marker::PhantomData; use std::os::unix::io::{AsRawFd, FromRawFd}; +use zellij_tile::data::Palette; + type SessionId = u64; #[derive(PartialEq, Eq, Serialize, Deserialize, Hash)] @@ -28,10 +35,17 @@ pub enum ClientType { Writer, } +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] +pub struct ClientAttributes { + pub position_and_size: PositionAndSize, + pub palette: Palette, +} + // Types of messages sent from the client to the server -#[derive(Serialize, Deserialize)] -pub enum _ClientToServerMsg { - // List which sessions are available +#[allow(clippy::large_enum_variant)] +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum ClientToServerMsg { + /*// List which sessions are available ListSessions, // Create a new session CreateSession, @@ -40,16 +54,49 @@ pub enum _ClientToServerMsg { // Force detach DetachSession(SessionId), // Disconnect from the session we're connected to - DisconnectFromSession, + DisconnectFromSession,*/ + TerminalResize(PositionAndSize), + NewClient(ClientAttributes, Box, Box), + AttachClient(ClientAttributes, bool), + Action(Action), + ClientExited, } // Types of messages sent from the server to the client -// @@@ Implement Serialize and Deserialize for this... -pub enum _ServerToClientMsg { - // Info about a particular session +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum ServerToClientMsg { + /*// Info about a particular session SessionInfo(Session), // A list of sessions - SessionList(HashSet), + SessionList(HashSet),*/ + Render(String), + UnblockInputThread, + Exit(ExitReason), +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum ExitReason { + Normal, + ForceDetached, + CannotAttach, + Error(String), +} + +impl Display for ExitReason { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + match self { + Self::Normal => write!(f, "Bye from Zellij!"), + Self::ForceDetached => write!( + f, + "Session was detach from this client (possibly because another client connected)" + ), + Self::CannotAttach => write!( + f, + "Session attached to another client. Use --force flag to force connect." + ), + Self::Error(e) => write!(f, "Error occured in server:\n{}", e), + } + } } /// Sends messages on a stream socket, along with an [`ErrorContext`]. diff --git a/zellij-utils/src/lib.rs b/zellij-utils/src/lib.rs new file mode 100644 index 00000000..6285047f --- /dev/null +++ b/zellij-utils/src/lib.rs @@ -0,0 +1,22 @@ +pub mod channels; +pub mod cli; +pub mod consts; +pub mod errors; +pub mod input; +pub mod ipc; +pub mod logging; +pub mod pane_size; +pub mod setup; +pub mod shared; + +pub use async_std; +pub use interprocess; +pub use libc; +pub use nix; +pub use serde; +pub use serde_yaml; +pub use signal_hook; +pub use structopt; +pub use termion; +pub use vte; +pub use zellij_tile; diff --git a/src/common/utils/logging.rs b/zellij-utils/src/logging.rs similarity index 90% rename from src/common/utils/logging.rs rename to zellij-utils/src/logging.rs index 0dd382d6..6d415e38 100644 --- a/src/common/utils/logging.rs +++ b/zellij-utils/src/logging.rs @@ -7,8 +7,8 @@ use std::{ path::{Path, PathBuf}, }; -use crate::os_input_output::set_permissions; -use crate::utils::consts::{ZELLIJ_TMP_LOG_DIR, ZELLIJ_TMP_LOG_FILE}; +use crate::consts::{ZELLIJ_TMP_LOG_DIR, ZELLIJ_TMP_LOG_FILE}; +use crate::shared::set_permissions; pub fn atomic_create_file(file_name: &Path) -> io::Result<()> { let _ = fs::OpenOptions::new() @@ -71,7 +71,7 @@ pub fn _delete_log_dir() -> io::Result<()> { } } -pub fn debug_to_file(message: u8, pid: RawFd) -> io::Result<()> { +pub fn debug_to_file(message: &[u8], pid: RawFd) -> io::Result<()> { let mut path = PathBuf::new(); path.push(&*ZELLIJ_TMP_LOG_DIR); path.push(format!("zellij-{}.log", pid.to_string())); @@ -81,5 +81,5 @@ pub fn debug_to_file(message: u8, pid: RawFd) -> io::Result<()> { .create(true) .open(&path)?; set_permissions(&path)?; - file.write_all(&[message]) + file.write_all(message) } diff --git a/zellij-utils/src/pane_size.rs b/zellij-utils/src/pane_size.rs new file mode 100644 index 00000000..2744c43f --- /dev/null +++ b/zellij-utils/src/pane_size.rs @@ -0,0 +1,24 @@ +use nix::pty::Winsize; +use serde::{Deserialize, Serialize}; + +/// Contains the position and size of a [`Pane`], or more generally of any terminal, measured +/// in character rows and columns. +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] +pub struct PositionAndSize { + pub x: usize, + pub y: usize, + pub rows: usize, + pub columns: usize, + pub max_rows: Option, + pub max_columns: Option, +} + +impl From for PositionAndSize { + fn from(winsize: Winsize) -> PositionAndSize { + PositionAndSize { + columns: winsize.ws_col as usize, + rows: winsize.ws_row as usize, + ..Default::default() + } + } +} diff --git a/src/common/setup.rs b/zellij-utils/src/setup.rs similarity index 86% rename from src/common/setup.rs rename to zellij-utils/src/setup.rs index 2e076776..0d442f0f 100644 --- a/src/common/setup.rs +++ b/zellij-utils/src/setup.rs @@ -1,8 +1,8 @@ use crate::cli::CliArgs; -use crate::common::utils::consts::{ +use crate::consts::{ FEATURES, SYSTEM_DEFAULT_CONFIG_DIR, SYSTEM_DEFAULT_DATA_DIR_PREFIX, VERSION, ZELLIJ_PROJ_DIR, }; -use crate::os_input_output::set_permissions; +use crate::shared::set_permissions; use directories_next::BaseDirs; use serde::{Deserialize, Serialize}; use std::io::Write; @@ -32,13 +32,13 @@ pub mod install { pub fn populate_data_dir(data_dir: &Path) { // First run installation of default plugins & layouts let mut assets = asset_map! { - "assets/layouts/default.yaml" => "layouts/default.yaml", - "assets/layouts/strider.yaml" => "layouts/strider.yaml", + "../assets/layouts/default.yaml" => "layouts/default.yaml", + "../assets/layouts/strider.yaml" => "layouts/strider.yaml", }; assets.extend(asset_map! { - "assets/plugins/status-bar.wasm" => "plugins/status-bar.wasm", - "assets/plugins/tab-bar.wasm" => "plugins/tab-bar.wasm", - "assets/plugins/strider.wasm" => "plugins/strider.wasm", + "../assets/plugins/status-bar.wasm" => "plugins/status-bar.wasm", + "../assets/plugins/tab-bar.wasm" => "plugins/tab-bar.wasm", + "../assets/plugins/strider.wasm" => "plugins/strider.wasm", }); assets.insert("VERSION", VERSION.as_bytes().to_vec()); @@ -57,7 +57,7 @@ pub mod install { } } -#[cfg(not(test))] +#[cfg(not(any(feature = "test", test)))] /// Goes through a predefined list and checks for an already /// existing config directory, returns the first match pub fn find_default_config_dir() -> Option { @@ -68,7 +68,7 @@ pub fn find_default_config_dir() -> Option { .flatten() } -#[cfg(test)] +#[cfg(any(feature = "test", test))] pub fn find_default_config_dir() -> Option { None } @@ -119,7 +119,7 @@ pub fn dump_asset(asset: &[u8]) -> std::io::Result<()> { pub const DEFAULT_CONFIG: &[u8] = include_bytes!(concat!( env!("CARGO_MANIFEST_DIR"), "/", - "assets/config/default.yaml" + "../assets/config/default.yaml" )); pub fn dump_default_config() -> std::io::Result<()> { @@ -146,30 +146,38 @@ pub struct Setup { impl Setup { /// Entrypoint from main - pub fn from_cli(&self, opts: CliArgs) -> std::io::Result<()> { + pub fn from_cli(&self, opts: &CliArgs) -> std::io::Result<()> { + if self.clean { + return Ok(()); + } + if self.dump_config { dump_default_config()?; + std::process::exit(0); } if self.check { - Setup::check_defaults_config(opts)?; + Setup::check_defaults_config(&opts)?; + std::process::exit(0); } if let Some(shell) = &self.generate_completion { Self::generate_completion(shell.into()); + std::process::exit(0); } Ok(()) } - pub fn check_defaults_config(opts: CliArgs) -> std::io::Result<()> { - let data_dir = opts.data_dir.unwrap_or_else(get_default_data_dir); - let config_dir = opts.config_dir.or_else(find_default_config_dir); + pub fn check_defaults_config(opts: &CliArgs) -> std::io::Result<()> { + let data_dir = opts.data_dir.clone().unwrap_or_else(get_default_data_dir); + let config_dir = opts.config_dir.clone().or_else(find_default_config_dir); let plugin_dir = data_dir.join("plugins"); let layout_dir = data_dir.join("layouts"); let system_data_dir = PathBuf::from(SYSTEM_DEFAULT_DATA_DIR_PREFIX).join("share/zellij"); let config_file = opts .config + .clone() .or_else(|| config_dir.clone().map(|p| p.join(CONFIG_NAME))); let mut message = String::new(); @@ -192,7 +200,7 @@ impl Setup { } } if let Some(config_file) = config_file { - use crate::common::input::config::Config; + use crate::input::config::Config; message.push_str(&format!("[CONFIG FILE]: {:?}\n", config_file)); match Config::new(&config_file) { Ok(_) => message.push_str(&"[CONFIG FILE]: Well defined.\n"), @@ -212,7 +220,7 @@ impl Setup { message.push_str(&format!("[ARROW SEPARATOR]: {}\n", ARROW_SEPARATOR)); message.push_str(&" Is the [ARROW_SEPARATOR] displayed correctly?\n"); - message.push_str(&" If not you may want to either start zellij with a compatible mode 'zellij options --simple-ui'\n"); + message.push_str(&" If not you may want to either start zellij with a compatible mode 'zellij options --simplified-ui'\n"); message.push_str(&" Or check the font that is in use:\n https://zellij.dev/documentation/compatibility.html#the-status-bar-fonts-dont-render-correctly\n"); message.push_str(&format!("[FEATURES]: {:?}\n", FEATURES)); diff --git a/src/common/utils/shared.rs b/zellij-utils/src/shared.rs similarity index 91% rename from src/common/utils/shared.rs rename to zellij-utils/src/shared.rs index 26a2f90a..bc8b6771 100644 --- a/src/common/utils/shared.rs +++ b/zellij-utils/src/shared.rs @@ -5,8 +5,19 @@ use std::{iter, str::from_utf8}; use strip_ansi_escapes::strip; use colors_transform::{Color, Rgb}; +use std::os::unix::fs::PermissionsExt; +use std::path::Path; +use std::{fs, io}; use zellij_tile::data::{Palette, PaletteColor, PaletteSource, Theme}; +const UNIX_PERMISSIONS: u32 = 0o700; + +pub fn set_permissions(path: &Path) -> io::Result<()> { + let mut permissions = fs::metadata(path)?.permissions(); + permissions.set_mode(UNIX_PERMISSIONS); + fs::set_permissions(path, permissions) +} + fn ansi_len(s: &str) -> usize { from_utf8(&strip(s.as_bytes()).unwrap()) .unwrap()