aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authora-kenji <aks.kenji@protonmail.com>2021-05-27 13:28:59 +0200
committera-kenji <aks.kenji@protonmail.com>2021-05-27 15:49:50 +0200
commit81b026df249c72057ed9ae9e8b4140c2ff6db160 (patch)
treea0f28e0ebbf017b53acc41d2a2094886c4781acd
parentDefault mode 368 (diff)
parentDocs(changelog): Detachable sessions #531 (diff)
downloadsl0ppij-81b026df249c72057ed9ae9e8b4140c2ff6db160.tar.gz
sl0ppij-81b026df249c72057ed9ae9e8b4140c2ff6db160.tar.xz
sl0ppij-81b026df249c72057ed9ae9e8b4140c2ff6db160.zip
Merge branch 'main' of https://github.com/zellij-org/zellij into default-mode-368
* If starting in the locked mode after the merge, the locked mode seems to need 2 actions to go to the normal mode - after that everything works as expected. - This is not intended.
-rw-r--r--.github/workflows/rust.yml30
-rw-r--r--.gitignore3
-rw-r--r--CHANGELOG.md15
-rw-r--r--CONTRIBUTING.md7
-rw-r--r--Cargo.lock290
-rw-r--r--Cargo.toml49
-rw-r--r--Makefile.toml7
-rw-r--r--README.md7
-rw-r--r--assets/config/default.yaml27
-rw-r--r--assets/plugins/status-bar.wasmbin461867 -> 462240 bytes
-rw-r--r--assets/plugins/strider.wasmbin564308 -> 564581 bytes
-rw-r--r--assets/plugins/tab-bar.wasmbin451557 -> 451906 bytes
-rw-r--r--default-plugins/status-bar/src/first_line.rs68
-rw-r--r--default-plugins/status-bar/src/main.rs64
-rw-r--r--default-plugins/tab-bar/src/line.rs12
-rw-r--r--default-plugins/tab-bar/src/main.rs2
-rw-r--r--default-plugins/tab-bar/src/tab.rs8
-rw-r--r--src/cli.rs47
-rw-r--r--src/client/mod.rs195
-rw-r--r--src/common/input/mod.rs7
-rw-r--r--src/common/mod.rs14
-rw-r--r--src/common/os_input_output.rs411
-rw-r--r--src/common/thread_bus.rs142
-rw-r--r--src/common/utils/mod.rs5
-rw-r--r--src/main.rs111
-rw-r--r--src/server/mod.rs269
-rw-r--r--src/sessions.rs109
-rw-r--r--src/tests/fakes.rs112
-rw-r--r--src/tests/integration/basic.rs21
-rw-r--r--src/tests/integration/close_pane.rs20
-rw-r--r--src/tests/integration/compatibility.rs27
-rw-r--r--src/tests/integration/layouts.rs10
-rw-r--r--src/tests/integration/move_focus_down.rs9
-rw-r--r--src/tests/integration/move_focus_left.rs10
-rw-r--r--src/tests/integration/move_focus_right.rs10
-rw-r--r--src/tests/integration/move_focus_up.rs9
-rw-r--r--src/tests/integration/resize_down.rs20
-rw-r--r--src/tests/integration/resize_left.rs20
-rw-r--r--src/tests/integration/resize_right.rs20
-rw-r--r--src/tests/integration/resize_up.rs20
-rw-r--r--src/tests/integration/tabs.rs16
-rw-r--r--src/tests/integration/terminal_window_resize.rs11
-rw-r--r--src/tests/integration/toggle_fullscreen.rs9
-rw-r--r--src/tests/mod.rs21
-rw-r--r--src/tests/utils.rs9
-rw-r--r--zellij-client/Cargo.toml16
-rw-r--r--zellij-client/src/command_is_executing.rs (renamed from src/common/command_is_executing.rs)2
-rw-r--r--zellij-client/src/input_handler.rs (renamed from src/common/input/handler.rs)114
-rw-r--r--zellij-client/src/lib.rs298
-rw-r--r--zellij-client/src/os_input_output.rs190
-rw-r--r--zellij-server/Cargo.toml25
-rw-r--r--zellij-server/src/lib.rs407
-rw-r--r--zellij-server/src/os_input_output.rs311
-rw-r--r--zellij-server/src/panes/grid.rs (renamed from src/client/panes/grid.rs)183
-rw-r--r--zellij-server/src/panes/mod.rs (renamed from src/client/panes/mod.rs)2
-rw-r--r--zellij-server/src/panes/plugin_pane.rs (renamed from src/client/panes/plugin_pane.rs)6
-rw-r--r--zellij-server/src/panes/terminal_character.rs (renamed from src/client/panes/terminal_character.rs)16
-rw-r--r--zellij-server/src/panes/terminal_pane.rs (renamed from src/client/panes/terminal_pane.rs)47
-rw-r--r--zellij-server/src/panes/unit/grid_tests.rs (renamed from src/client/panes/unit/grid_tests.rs)67
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_b.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_b.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_capital_i.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_i.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__csi_capital_z.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_z.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__terminal_reports.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__terminal_reports.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_0.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_0.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_1.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_1.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_2.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_2.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_3.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_3.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_4.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_4.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest1_5.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_5.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_0.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_0.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_1.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_1.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_10.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_10.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_11.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_11.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_12.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_12.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_13.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_13.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_14.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_14.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_2.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_2.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_3.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_3.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_4.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_4.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_5.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_5.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_6.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_6.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_7.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_7.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_8.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_8.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest2_9.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_9.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest3_0.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest3_0.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_0.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_0.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_1.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_1.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_2.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_2.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_3.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_3.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_4.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_4.snap)2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_5.snap (renamed from src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_5.snap)2
-rw-r--r--zellij-server/src/pty.rs (renamed from src/common/pty.rs)210
-rw-r--r--zellij-server/src/route.rs (renamed from src/server/route.rs)101
-rw-r--r--zellij-server/src/screen.rs (renamed from src/common/screen.rs)136
-rw-r--r--zellij-server/src/tab.rs (renamed from src/client/tab.rs)62
-rw-r--r--zellij-server/src/thread_bus.rs80
-rw-r--r--zellij-server/src/ui/boundaries.rs (renamed from src/client/boundaries.rs)12
-rw-r--r--zellij-server/src/ui/layout.rs (renamed from src/client/layout.rs)28
-rw-r--r--zellij-server/src/ui/mod.rs3
-rw-r--r--zellij-server/src/ui/pane_resizer.rs (renamed from src/client/pane_resizer.rs)7
-rw-r--r--zellij-server/src/wasm_vm.rs (renamed from src/common/wasm_vm.rs)36
-rw-r--r--zellij-tile-utils/Cargo.toml2
-rw-r--r--zellij-tile/Cargo.toml2
-rw-r--r--zellij-tile/src/data.rs3
-rw-r--r--zellij-utils/Cargo.toml39
-rw-r--r--zellij-utils/src/channels.rs65
-rw-r--r--zellij-utils/src/cli.rs82
-rw-r--r--zellij-utils/src/consts.rs (renamed from src/common/utils/consts.rs)21
-rw-r--r--zellij-utils/src/errors.rs (renamed from src/common/errors.rs)146
-rw-r--r--zellij-utils/src/input/actions.rs (renamed from src/common/input/actions.rs)6
-rw-r--r--zellij-utils/src/input/config.rs (renamed from src/common/input/config.rs)10
-rw-r--r--zellij-utils/src/input/keybinds.rs (renamed from src/common/input/keybinds.rs)0
-rw-r--r--zellij-utils/src/input/mod.rs90
-rw-r--r--zellij-utils/src/input/options.rs (renamed from src/common/input/options.rs)6
-rw-r--r--zellij-utils/src/input/unit/keybinds_test.rs (renamed from src/common/input/unit/keybinds_test.rs)0
-rw-r--r--zellij-utils/src/ipc.rs (renamed from src/common/ipc.rs)67
-rw-r--r--zellij-utils/src/lib.rs22
-rw-r--r--zellij-utils/src/logging.rs (renamed from src/common/utils/logging.rs)8
-rw-r--r--zellij-utils/src/pane_size.rs24
-rw-r--r--zellij-utils/src/setup.rs (renamed from src/common/setup.rs)42
-rw-r--r--zellij-utils/src/shared.rs (renamed from src/common/utils/shared.rs)11
121 files changed, 3129 insertions, 2213 deletions
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",
]
@@ -171,6 +171,17 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -223,18 +234,6 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -249,6 +248,12 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -458,6 +463,31 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -468,6 +498,16 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -605,12 +645,6 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -935,19 +969,6 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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",
]
@@ -1032,6 +1053,28 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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]]
@@ -1116,6 +1155,31 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1211,12 +1275,6 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1466,6 +1524,17 @@ dependencies = [
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef33d6d0cd06e0840fba9985aab098c147e67e05cee14d412d3345ed14ff30ac"
@@ -1527,12 +1596,6 @@ 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"
dependencies = [
@@ -1636,12 +1699,6 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1662,6 +1719,17 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1684,15 +1752,6 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1795,15 +1854,6 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2238,12 +2288,6 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2254,43 +2298,41 @@ dependencies = [
[[package]]
name = "zellij"
-version = "0.11.0"
+version = "0.12.0"
dependencies = [
- "ansi_term 0.12.1",
- "async-std",
- "backtrace",
- "bincode",
- "colors-transform",
- "directories-next",
- "futures",
"insta",
- "interprocess",
- "lazy_static",
- "libc",
"names",
- "nix",
- "nom",
- "serde",
+ "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-trait",
+ "daemonize",
+ "insta",
"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 <aram@poor.dev>"]
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
--- a/assets/plugins/status-bar.wasm
+++ b/assets/plugins/status-bar.wasm
Binary files differ
diff --git a/assets/plugins/strider.wasm b/assets/plugins/strider.wasm
index 80421c0d..f687db8d 100644
--- a/assets/plugins/strider.wasm
+++ b/assets/plugins/strider.wasm
Binary files differ
diff --git a/assets/plugins/tab-bar.wasm b/assets/plugins/tab-bar.wasm
index 3487df71..bb981a5b 100644
--- a/assets/plugins/tab-bar.wasm
+++ b/assets/plugins/tab-bar.wasm
Binary files 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,
@@ -255,15 +222,6 @@ fn key_indicators(
}
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);
line_part.len += key.len;
@@ -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<usize>,
-
- /// Change where zellij looks for layouts and plugins
- #[structopt(long)]
- pub data_dir: Option<PathBuf>,
-
- /// Path to a layout yaml file
- #[structopt(short, long)]
- pub layout: Option<PathBuf>,
-
- /// Change where zellij looks for the configuration
- #[structopt(short, long, env=ZELLIJ_CONFIG_FILE_ENV)]
- pub config: Option<PathBuf>,
-
- /// Change where zellij looks for the configuration
- #[structopt(long, env=ZELLIJ_CONFIG_DIR_ENV)]
- pub config_dir: Option<PathBuf>,
-
- #[structopt(subcommand)]
- pub option: Option<ConfigCli>,
-
- #[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<String>),
- UnblockInputThread,
- Exit,
-}
-
-pub fn start_client(mut os_input: Box<dyn ClientOsApi>, 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<PathBuf>, 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<Mutex<termios::Termios>>,
- receive_instructions_from_client: Option<Arc<Mutex<IpcReceiverWithContext<ServerInstruction>>>>,
- send_instructions_to_client: Arc<Mutex<Option<IpcSenderWithContext<ClientInstruction>>>>,
-}
-
-/// 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<PathBuf>) -> (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<usize, nix::Error>;
- /// 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<usize, nix::Error>;
- /// 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<dyn ServerOsApi>;
- /// 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<PathBuf>) -> (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<usize, nix::Error> {
- unistd::read(fd, buf)
- }
- fn write_to_tty_stdin(&mut self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
- unistd::write(fd, buf)
- }
- fn tcdrain(&mut self, fd: RawFd) -> Result<(), nix::Error> {
- termios::tcdrain(fd)
- }
- fn box_clone(&self) -> Box<dyn ServerOsApi> {
- 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<dyn ServerOsApi> {
- fn clone(&self) -> Box<dyn ServerOsApi> {
- 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<Mutex<termios::Termios>>,
- send_instructions_to_server: Arc<Mutex<Option<IpcSenderWithContext<ServerInstruction>>>>,
- receive_instructions_from_server: Arc<Mutex<Option<IpcReceiverWithContext<ClientInstruction>>>>,
-}
-
-/// 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<dyn io::Write>;
- /// Returns the raw contents of standard input.
- fn read_from_stdin(&self) -> Vec<u8>;
- /// Returns a [`Box`] pointer to this [`ClientOsApi`] struct.
- fn box_clone(&self) -> Box<dyn ClientOsApi>;
- /// 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<dyn Fn()>);
- /// 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<dyn ClientOsApi> {
- Box::new((*self).clone())
- }
- fn read_from_stdin(&self) -> Vec<u8> {
- let stdin = std::io::stdin();
- let mut stdin = stdin.lock();
- let buffer = stdin.fill_buf().unwrap();
- let length = buffer.len();
- let read_bytes = Vec::from(buffer);
- stdin.consume(length);
- read_bytes
- }
- fn get_stdout_writer(&self) -> Box<dyn io::Write> {
- 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<dyn Fn()>) {
- 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<dyn ClientOsApi> {
- fn clone(&self) -> Box<dyn ClientOsApi> {
- 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<T> = (
- mpsc::Sender<(T, ErrorContext)>,
- mpsc::Receiver<(T, ErrorContext)>,
-);
-/// An [MPSC](mpsc) synchronous channel with added error context.
-pub type SyncChannelWithContext<T> = (
- 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<T: Clone> {
- /// 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<T: Clone> {
- sender: SenderType<T>,
-}
-
-impl<T: Clone> SenderWithContext<T> {
- pub fn new(sender: SenderType<T>) -> 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<T: Clone> Send for SenderWithContext<T> {}
-unsafe impl<T: Clone> Sync for SenderWithContext<T> {}
-
-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<ErrorContext> = 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<ErrorContext> = 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<SenderWithContext<ScreenInstruction>>,
- pub to_pty: Option<SenderWithContext<PtyInstruction>>,
- pub to_plugin: Option<SenderWithContext<PluginInstruction>>,
- pub to_server: Option<SenderWithContext<ServerInstruction>>,
-}
-
-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<T> {
- pub receiver: mpsc::Receiver<(T, ErrorContext)>,
- pub senders: ThreadSenders,
- pub os_input: Option<Box<dyn ServerOsApi>>,
-}
-
-impl<T> Bus<T> {
- pub fn new(
- receiver: mpsc::Receiver<(T, ErrorContext)>,
- to_screen: Option<&SenderWithContext<ScreenInstruction>>,
- to_pty: Option<&SenderWithContext<PtyInstruction>>,
- to_plugin: Option<&SenderWithContext<PluginInstruction>>,
- to_server: Option<&SenderWithContext<ServerInstruction>>,
- os_input: Option<Box<dyn ServerOsApi>>,
- ) -> 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::{
+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,
};
-use std::convert::TryFrom;
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);
+ 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!("failed to open terminal:\n{}", e);
+ process::exit(1);
+ }
+ };
+ start_server(Box::new(os_input), path);
} else {
- let config = match Config::try_from(&opts) {
- Ok(config) => config,
+ let os_input = match get_client_os_input() {
+ Ok(os_input) => 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,
- );
+ 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<dyn ClientOsApi>,
- opts: CliArgs,
- server_os_input: Box<dyn ServerOsApi>,
- 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<String>),
- UnblockInputThread,
- ClientExit,
-}
-
-pub struct SessionMetaData {
- pub senders: ThreadSenders,
- screen_thread: Option<thread::JoinHandle<()>>,
- pty_thread: Option<thread::JoinHandle<()>>,
- wasm_thread: Option<thread::JoinHandle<()>>,
-}
-
-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<dyn ServerOsApi>,
- config_options: Options,
-) -> thread::JoinHandle<()> {
- let (to_server, server_receiver): ChannelWithContext<ServerInstruction> = channel();
- let to_server = SenderWithContext::new(SenderType::Sender(to_server));
- let sessions: Arc<RwLock<Option<SessionMetaData>>> = 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<dyn ServerOsApi>,
- opts: CliArgs,
- config_options: Options,
- to_server: SenderWithContext<ServerInstruction>,
- full_screen_ws: PositionAndSize,
-) -> SessionMetaData {
- let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = channel();
- let to_screen = SenderWithContext::new(SenderType::Sender(to_screen));
-
- let (to_plugin, plugin_receiver): ChannelWithContext<PluginInstruction> = channel();
- let to_plugin = SenderWithContext::new(SenderType::Sender(to_plugin));
- let (to_pty, pty_receiver): ChannelWithContext<PtyInstruction> = 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<Vec<String>, 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<Mutex<HashMap<RawFd, PositionAndSize>>>,
possible_tty_inputs: HashMap<u16, Bytes>,
last_snapshot_time: Arc<Mutex<Instant>>,
- send_instructions_to_client: SenderWithContext<ClientInstruction>,
- receive_instructions_from_server: Arc<Mutex<mpsc::Receiver<(ClientInstruction, ErrorContext)>>>,
- send_instructions_to_server: SenderWithContext<ServerInstruction>,
- receive_instructions_from_client: Arc<Mutex<mpsc::Receiver<(ServerInstruction, ErrorContext)>>>,
+ send_instructions_to_client: SenderWithContext<ServerToClientMsg>,
+ receive_instructions_from_server: Arc<Mutex<mpsc::Receiver<(ServerToClientMsg, ErrorContext)>>>,
+ send_instructions_to_server: SenderWithContext<ClientToServerMsg>,
+ receive_instructions_from_client: Arc<Mutex<mpsc::Receiver<(ClientToServerMsg, ErrorContext)>>>,
should_trigger_sigwinch: Arc<(Mutex<bool>, Condvar)>,
sigwinch_event: Option<PositionAndSize>,
}
@@ -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<ClientInstruction> =
+ let (client_sender, client_receiver): ChannelWithContext<ServerToClientMsg> =
mpsc::channel();
let send_instructions_to_client = SenderWithContext::new(SenderType::Sender(client_sender));
- let (server_sender, server_receiver): ChannelWithContext<ServerInstruction> =
+ let (server_sender, server_receiver): ChannelWithContext<ClientToServerMsg> =
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<dyn Write> {
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<dyn Fn()>) {
+ fn handle_signals(&self, sigwinch_cb: Box<dyn Fn()>, _quit_cb: Box<dyn Fn()>) {
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<dyn ServerOsApi>,
+}
+
+#[async_trait]
+impl AsyncReader for FakeAsyncReader {
+ async fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
+ // 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<PathBuf>) -> (RawFd, RawFd) {
+ fn spawn_terminal(&self, _file_to_open: Option<PathBuf>) -> (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<usize, nix::Error> {
+ fn write_to_tty_stdin(&self, pid: RawFd, buf: &[u8]) -> Result<usize, nix::Error> {
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<usize, nix::Error> {
+ fn read_from_tty_stdout(&self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
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<dyn AsyncReader> {
+ 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<dyn ServerOsApi> {
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<dyn ClientOsApi>,
+ opts: CliArgs,
+ server_os_input: Box<dyn ServerOsApi>,
+ 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<u8>],
@@ -7,7 +10,7 @@ pub fn get_output_frame_snapshots(
) -> Vec<String> {
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 <kunalmohan99@gmail.com>"]
+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
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<bool>, Condvar)>,
}
diff --git a/src/common/input/handler.rs b/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<dyn ClientOsApi>,
config: Config,
command_is_executing: CommandIsExecuting,
@@ -237,35 +193,3 @@ pub fn input_loop(
)
.handle_input();
}
-
-pub fn parse_keys(input_bytes: &[u8]) -> Vec<Key> {
- 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<ServerToClientMsg> 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<dyn ClientOsApi>,
+ 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<Mutex<termios::Termios>>,
+ send_instructions_to_server: Arc<Mutex<Option<IpcSenderWithContext<ClientToServerMsg>>>>,
+ receive_instructions_from_server: Arc<Mutex<Option<IpcReceiverWithContext<ServerToClientMsg>>>>,
+}
+
+/// 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<dyn io::Write>;
+ /// Returns the raw contents of standard input.
+ fn read_from_stdin(&self) -> Vec<u8>;
+ /// Returns a [`Box`] pointer to this [`ClientOsApi`] struct.
+ fn box_clone(&self) -> Box<dyn ClientOsApi>;
+ /// 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<dyn Fn()>, quit_cb: Box<dyn Fn()>);
+ /// 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<dyn ClientOsApi> {
+ Box::new((*self).clone())
+ }
+ fn read_from_stdin(&self) -> Vec<u8> {
+ let stdin = std::io::stdin();
+ let mut stdin = stdin.lock();
+ let buffer = stdin.fill_buf().unwrap();
+ let length = buffer.len();
+ let read_bytes = Vec::from(buffer);
+ stdin.consume(length);
+ read_bytes
+ }
+ fn get_stdout_writer(&self) -> Box<dyn io::Write> {
+ 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<dyn Fn()>, quit_cb: Box<dyn Fn()>) {
+ 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<dyn ClientOsApi> {
+ fn clone(&self) -> Box<dyn ClientOsApi> {
+ self.box_clone()
+ }
+}
+
+pub fn get_client_os_input() -> Result<ClientOsInputOutput, nix::Error> {
+ 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 <kunalmohan99@gmail.com>"]
+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<CliArgs>, Box<Options>),
+ Render(Option<String>),
+ UnblockInputThread,
+ ClientExit,
+ Error(String),
+ DetachSession,
+ AttachClient(ClientAttributes, bool),
+}
+
+impl From<ClientToServerMsg> 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<thread::JoinHandle<()>>,
+ pty_thread: Option<thread::JoinHandle<()>>,
+ wasm_thread: Option<thread::JoinHandle<()>>,
+}
+
+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<dyn ServerOsApi>, 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<ServerInstruction> =
+ mpsc::sync_channel(50);
+ let to_server = SenderWithContext::new(SenderType::SyncSender(to_server));
+ let session_data: Arc<RwLock<Option<SessionMetaData>>> = 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<String> 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<dyn ServerOsApi>,
+ opts: Box<CliArgs>,
+ config_options: Box<Options>,
+ to_server: SenderWithContext<ServerInstruction>,
+ client_attributes: ClientAttributes,
+ session_state: Arc<RwLock<SessionState>>,
+) -> SessionMetaData {
+ let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = mpsc::channel();
+ let to_screen = SenderWithContext::new(SenderType::Sender(to_screen));
+
+ let (to_plugin, plugin_receiver): ChannelWithContext<PluginInstruction> = mpsc::channel();
+ let to_plugin = SenderWithContext::new(SenderType::Sender(to_plugin));
+ let (to_pty, pty_receiver): ChannelWithContext<PtyInstruction> = 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<PathBuf>, 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<Mutex<termios::Termios>>,
+ receive_instructions_from_client: Option<Arc<Mutex<IpcReceiverWithContext<ClientToServerMsg>>>>,
+ send_instructions_to_client: Arc<Mutex<Option<IpcSenderWithContext<ServerToClientMsg>>>>,
+}
+
+// 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<usize, std::io::Error>;
+}
+
+/// 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<usize, std::io::Error> {
+ 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<PathBuf>) -> (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<usize, nix::Error>;
+ /// Creates an `AsyncReader` that can be used to read from `fd` in an async context
+ fn async_file_reader(&self, fd: RawFd) -> Box<dyn AsyncReader>;
+ /// 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<usize, nix::Error>;
+ /// 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<dyn ServerOsApi>;
+ /// 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<PathBuf>) -> (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<usize, nix::Error> {
+ unistd::read(fd, buf)
+ }
+ fn async_file_reader(&self, fd: RawFd) -> Box<dyn AsyncReader> {
+ Box::new(RawFdAsyncReader::new(fd))
+ }
+ fn write_to_tty_stdin(&self, fd: RawFd, buf: &[u8]) -> Result<usize, nix::Error> {
+ unistd::write(fd, buf)
+ }
+ fn tcdrain(&self, fd: RawFd) -> Result<(), nix::Error> {
+ termios::tcdrain(fd)
+ }
+ fn box_clone(&self) -> Box<dyn ServerOsApi> {
+ 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<dyn ServerOsApi> {
+ fn clone(&self) -> Box<dyn ServerOsApi> {
+ self.box_clone()
+ }
+}
+
+pub fn get_server_os_input() -> Result<ServerOsInputOutput, nix::Error> {
+ 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
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<u8> {
+ 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<Row>) -> Vec<Row> {
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<TerminalCharacter>,
+ 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::<Vec<&str>>()
+ .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 &params[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 &params[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<TerminalCharacter>) -> 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
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
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
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<AnsiCode>,
}
-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<AnsiCode>) -> Self {
self.foreground = foreground_code;
self
diff --git a/src/client/panes/terminal_pane.rs b/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<usize>,
- pub max_columns: Option<usize>,
-}
-
-impl From<Winsize> 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<usize>,
pub max_width: Option<usize>,
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<Vec<TerminalCharacter>> {
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
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<u8> {
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<u8> {
#[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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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<dyn ServerOsApi>,
-}
-
-impl ReadFromPid {
- pub fn new(pid: &RawFd, os_input: Box<dyn ServerOsApi>) -> ReadFromPid {
- ReadFromPid {
- pid: *pid,
- os_input,
- }
- }
-}
-
-impl Stream for ReadFromPid {
- type Item = Vec<u8>;
- fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
- 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<u8>;
/// Instructions related to PTYs (pseudoterminals).
#[derive(Clone, Debug)]
-pub enum PtyInstruction {
+pub(crate) enum PtyInstruction {
SpawnTerminal(Option<PathBuf>),
SpawnTerminalVertically(Option<PathBuf>),
SpawnTerminalHorizontally(Option<PathBuf>),
@@ -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<PtyInstruction>,
- pub id_to_child_pid: HashMap<RawFd, RawFd>,
+ pub id_to_child_pid: HashMap<RawFd, Pid>,
debug_to_file: bool,
task_handles: HashMap<RawFd, JoinHandle<()>>,
}
-pub fn pty_thread_main(mut pty: Pty, maybe_layout: Option<Layout>) {
+pub(crate) fn pty_thread_main(mut pty: Pty, maybe_layout: Option<Layout>) {
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<Layout>) {
}
}
+enum ReadResult {
+ Ok(usize),
+ Timeout,
+ Err(std::io::Error),
+}
+
+impl From<std::io::Result<usize>> for ReadResult {
+ fn from(e: std::io::Result<usize>) -> ReadResult {
+ match e {
+ Err(e) => ReadResult::Err(e),
+ Ok(n) => ReadResult::Ok(n),
+ }
+ }
+}
+
+async fn deadline_read(
+ reader: &mut dyn AsyncReader,
+ deadline: Option<Instant>,
+ 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<Instant> = 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;
+
+ 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;
- 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();
+ // yield so Screen thread has some time to render before send additional
+ // PtyBytes.
+ task::sleep(Duration::from_millis(10)).await;
}
- }
- 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;
+ ReadResult::Ok(n_bytes) => {
+ let bytes = &buf[..n_bytes];
+ if debug {
+ let _ = debug_to_file(bytes, pid);
}
- };
- } else {
- if pending_render {
- pending_render = false;
- let _ = senders.send_to_screen(ScreenInstruction::Render);
+ 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<PathBuf>) -> 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
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<ServerInstruction>,
+) -> 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<RwLock<Option<SessionMetaData>>>,
- mut os_input: Box<dyn ServerOsApi>,
+pub(crate) fn route_thread_main(
+ session_data: Arc<RwLock<Option<SessionMetaData>>>,
+ session_state: Arc<RwLock<SessionState>>,
+ os_input: Box<dyn ServerOsApi>,
to_server: SenderWithContext<ServerInstruction>,
- 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;
- }
- ServerInstruction::Action(action) => {
- route_action(
- action,
- rlocked_sessions.as_ref().unwrap(),
- &*os_input,
- capabilities,
- );
+ ClientToServerMsg::Action(action) => {
+ if let Some(rlocked_sessions) = rlocked_sessions.as_ref() {
+ if route_action(action, rlocked_sessions, &*os_input, &to_server) {
+ break;
+ }
+ }
}
- 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
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<ScreenInstruction>,
/// 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<usize, Tab>,
/// 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<usize>,
mode_info: ModeInfo,
input_mode: InputMode,
colors: Palette,
+ session_state: Arc<RwLock<SessionState>>,
}
impl Screen {
/// Creates and returns a new [`Screen`].
pub fn new(
bus: Bus<ScreenInstruction>,
- full_screen_ws: &PositionAndSize,
+ client_attributes: &ClientAttributes,
max_panes: Option<usize>,
mode_info: ModeInfo,
input_mode: InputMode,
- colors: Palette,
+ session_state: Arc<RwLock<SessionState>>,
) -> 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<ScreenInstruction>,
max_panes: Option<usize>,
- full_screen_ws: PositionAndSize,
- config_options: Options,
+ client_attributes: ClientAttributes,
+ config_options: Box<Options>,
+ session_state: Arc<RwLock<SessionState>>,
) {
- 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
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<RwLock<SessionState>>,
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<dyn ServerOsApi>,
+ os_api: Box<dyn ServerOsApi>,
senders: ThreadSenders,
max_panes: Option<usize>,
pane_id: Option<PaneId>,
mode_info: ModeInfo,
input_mode: InputMode,
colors: Palette,
+ session_state: Arc<RwLock<SessionState>>,
) -> 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<SenderWithContext<ScreenInstruction>>,
+ pub to_pty: Option<SenderWithContext<PtyInstruction>>,
+ pub to_plugin: Option<SenderWithContext<PluginInstruction>>,
+ pub to_server: Option<SenderWithContext<ServerInstruction>>,
+}
+
+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<T> {
+ pub receiver: mpsc::Receiver<(T, ErrorContext)>,
+ pub senders: ThreadSenders,
+ pub os_input: Option<Box<dyn ServerOsApi>>,
+}
+
+impl<T> Bus<T> {
+ pub fn new(
+ receiver: mpsc::Receiver<(T, ErrorContext)>,
+ to_screen: Option<&SenderWithContext<ScreenInstruction>>,
+ to_pty: Option<&SenderWithContext<PtyInstruction>>,
+ to_plugin: Option<&SenderWithContext<PluginInstruction>>,
+ to_server: Option<&SenderWithContext<ServerInstruction>>,
+ os_input: Option<Box<dyn ServerOsApi>>,
+ ) -> 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
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<PaletteColor>,
@@ -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
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<Layout>,
@@ -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
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<PaneId, Box<dyn Pane>>,
os_api: &'a mut Box<dyn ServerOsApi>,
}
diff --git a/src/common/wasm_vm.rs b/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<u32>, PathBuf),
Update(Option<u32>, Event), // Focused plugin / broadcast, event data
Render(Sender<String>, 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<PluginInstruction>, store: Store, data_dir: PathBuf) {
+pub(crate) fn wasm_thread_main(bus: Bus<PluginInstruction>, 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<PluginInstruction>, 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 <denismaximov98@gmail.com>"]
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 <b.j.rady@gmail.com>"]
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 <kunalmohan99@gmail.com>"]
+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<T> = (
+ mpsc::Sender<(T, ErrorContext)>,
+ mpsc::Receiver<(T, ErrorContext)>,
+);
+/// An [MPSC](mpsc) synchronous channel with added error context.
+pub type SyncChannelWithContext<T> = (
+ 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<T: Clone> {
+ /// 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<T: Clone> {
+ sender: SenderType<T>,
+}
+
+impl<T: Clone> SenderWithContext<T> {
+ pub fn new(sender: SenderType<T>) -> 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<T: Clone> Send for SenderWithContext<T> {}
+unsafe impl<T: Clone> Sync for SenderWithContext<T> {}
+
+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<ErrorContext> = 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<ErrorContext> = 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<usize>,
+
+ /// Change where zellij looks for layouts and plugins
+ #[structopt(long, parse(from_os_str))]
+ pub data_dir: Option<PathBuf>,
+
+ /// Run server listening at the specified socket path
+ #[structopt(long, parse(from_os_str), hidden = true)]
+ pub server: Option<PathBuf>,
+
+ /// Specify name of a new session
+ #[structopt(long, short)]
+ pub session: Option<String>,
+
+ /// Name of a layout file in the layout directory
+ #[structopt(short, long, parse(from_os_str))]
+ pub layout: Option<PathBuf>,
+
+ /// Path to a layout yaml file
+ #[structopt(long, parse(from_os_str))]
+ pub layout_path: Option<PathBuf>,
+
+ /// Change where zellij looks for the configuration file
+ #[structopt(short, long, env=ZELLIJ_CONFIG_FILE_ENV, parse(from_os_str))]
+ pub config: Option<PathBuf>,
+
+ /// Change where zellij looks for the configuration directory
+ #[structopt(long, env=ZELLIJ_CONFIG_DIR_ENV, parse(from_os_str))]
+ pub config_dir: Option<PathBuf>,
+
+ #[structopt(subcommand)]
+ pub command: Option<Command>,
+
+ #[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
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<String> = 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
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<ClientInstruction>,
-) {
+pub fn handle_panic<T>(info: &PanicInfo<'_>, sender: &SenderWithContext<T>)
+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
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<u8>),
+ /// Detach session and exit
+ Detach,
}
diff --git a/src/common/input/config.rs b/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
index 8bef47fd..8bef47fd 100644
--- a/src/common/input/keybinds.rs
+++ b/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<Key> {
+ 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
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<ConfigCli>) -> Options {
- if let Some(ConfigCli::Options(options)) = other {
+ pub fn from_cli(&self, other: Option<Command>) -> 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
index 4767fc15..4767fc15 100644
--- a/src/common/input/unit/keybinds_test.rs
+++ b/zellij-utils/src/input/unit/keybinds_test.rs
diff --git a/src/common/ipc.rs b/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<CliArgs>, Box<Options>),
+ 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<Session>),
+ SessionList(HashSet<Session>),*/
+ 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
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<usize>,
+ pub max_columns: Option<usize>,
+}
+
+impl From<Winsize> 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
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<PathBuf> {
@@ -68,7 +68,7 @@ pub fn find_default_config_dir() -> Option<PathBuf> {
.flatten()
}
-#[cfg(test)]
+#[cfg(any(feature = "test", test))]
pub fn find_default_config_dir() -> Option<PathBuf> {
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
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()