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.
This commit is contained in:
commit
81b026df24
121 changed files with 3133 additions and 2217 deletions
30
.github/workflows/rust.yml
vendored
30
.github/workflows/rust.yml
vendored
|
|
@ -16,10 +16,18 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- 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
|
- name: Add WASM target
|
||||||
run: rustup target add wasm32-wasi
|
run: rustup target add wasm32-wasi
|
||||||
- name: Install cargo-make
|
- 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
|
- name: Build
|
||||||
run: cargo make build
|
run: cargo make build
|
||||||
- name: Test
|
- name: Test
|
||||||
|
|
@ -31,8 +39,16 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- 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
|
- 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
|
- name: Check Format
|
||||||
run: cargo make check-format
|
run: cargo make check-format
|
||||||
|
|
||||||
|
|
@ -42,7 +58,15 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- 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
|
- 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
|
- name: Check Lints
|
||||||
run: cargo make clippy -D clippy::all
|
run: cargo make clippy -D clippy::all
|
||||||
|
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -3,4 +3,5 @@
|
||||||
.vscode
|
.vscode
|
||||||
.vim
|
.vim
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/assets/man/zellij.1
|
/assets/man/zellij.1
|
||||||
|
**/target
|
||||||
15
CHANGELOG.md
15
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/)
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||||
|
|
||||||
## [Unreleased]
|
## [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)
|
* 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
|
## [0.10.0] - 2021-05-14
|
||||||
* Change Switch default config loading order of `HOME` and system (https://github.com/zellij-org/zellij/pull/488)
|
* Change Switch default config loading order of `HOME` and system (https://github.com/zellij-org/zellij/pull/488)
|
||||||
|
|
|
||||||
|
|
@ -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
|
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.
|
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:
|
Here are some of the commands currently supported by the build system:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|
@ -44,8 +41,8 @@ cargo make publish
|
||||||
cargo make manpage
|
cargo make manpage
|
||||||
```
|
```
|
||||||
|
|
||||||
To run `install` or `publish`, you'll need `binaryen --version` > 97, for it's
|
To run `install` or `publish`, you'll need the package `binaryen` in the
|
||||||
command `wasm-opt`.
|
version `wasm-opt --version` > 97, for it's command `wasm-opt`.
|
||||||
|
|
||||||
## Looking for something to work on?
|
## Looking for something to work on?
|
||||||
|
|
||||||
|
|
|
||||||
290
Cargo.lock
generated
290
Cargo.lock
generated
|
|
@ -132,7 +132,7 @@ dependencies = [
|
||||||
"event-listener",
|
"event-listener",
|
||||||
"futures-lite",
|
"futures-lite",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"signal-hook",
|
"signal-hook 0.3.8",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -170,6 +170,17 @@ version = "4.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
|
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]]
|
[[package]]
|
||||||
name = "atomic-waker"
|
name = "atomic-waker"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
|
@ -222,18 +233,6 @@ version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
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]]
|
[[package]]
|
||||||
name = "blocking"
|
name = "blocking"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
|
@ -248,6 +247,12 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "boxfnonce"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5988cb1d626264ac94100be357308f29ff7cbdd3b36bda27f450a4ee3f713426"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.6.1"
|
version = "3.6.1"
|
||||||
|
|
@ -457,6 +462,31 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c36c10130df424b2f3552fcc2ddcd9b28a27b1e54b358b45874f88d1ca6888c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"signal-hook 0.1.17",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm_winapi"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0da8964ace4d3e4a044fd027919b2237000b24315a37c916f61809f1ff2140b9"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ctor"
|
name = "ctor"
|
||||||
version = "0.1.20"
|
version = "0.1.20"
|
||||||
|
|
@ -467,6 +497,16 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "daemonize"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70c24513e34f53b640819f0ac9f705b673fcf4006d7aab8778bee72ebfc89815"
|
||||||
|
dependencies = [
|
||||||
|
"boxfnonce",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
|
|
@ -604,12 +644,6 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "funty"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.14"
|
version = "0.3.14"
|
||||||
|
|
@ -934,19 +968,6 @@ version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a"
|
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]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.93"
|
version = "0.2.93"
|
||||||
|
|
@ -971,9 +992,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.3"
|
version = "0.4.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176"
|
checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
]
|
]
|
||||||
|
|
@ -1031,6 +1052,28 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.7.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"miow",
|
||||||
|
"ntapi",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miow"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "more-asserts"
|
name = "more-asserts"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|
@ -1059,16 +1102,12 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "ntapi"
|
||||||
version = "6.1.2"
|
version = "0.3.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
|
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitvec",
|
"winapi",
|
||||||
"funty",
|
|
||||||
"lexical-core",
|
|
||||||
"memchr",
|
|
||||||
"version_check",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1115,6 +1154,31 @@ version = "2.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
|
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]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.6"
|
version = "0.2.6"
|
||||||
|
|
@ -1210,12 +1274,6 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "radium"
|
|
||||||
version = "0.5.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.3.23"
|
version = "0.3.23"
|
||||||
|
|
@ -1464,6 +1522,17 @@ dependencies = [
|
||||||
"yaml-rust",
|
"yaml-rust",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.1.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook"
|
name = "signal-hook"
|
||||||
version = "0.3.8"
|
version = "0.3.8"
|
||||||
|
|
@ -1526,12 +1595,6 @@ version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "static_assertions"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "status-bar"
|
name = "status-bar"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -1635,12 +1698,6 @@ dependencies = [
|
||||||
"zellij-tile-utils",
|
"zellij-tile-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tap"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "target-lexicon"
|
name = "target-lexicon"
|
||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
|
|
@ -1661,6 +1718,17 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termbg"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e7fa67e879d2b5c902517367809bfab4e69ee6cabc03909141003721bd64282"
|
||||||
|
dependencies = [
|
||||||
|
"crossterm",
|
||||||
|
"thiserror",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "terminal_size"
|
name = "terminal_size"
|
||||||
version = "0.1.16"
|
version = "0.1.16"
|
||||||
|
|
@ -1683,15 +1751,6 @@ dependencies = [
|
||||||
"redox_termios",
|
"redox_termios",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "termios"
|
|
||||||
version = "0.3.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "textwrap"
|
name = "textwrap"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
|
@ -1794,15 +1853,6 @@ version = "1.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
|
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]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.1.8"
|
version = "0.1.8"
|
||||||
|
|
@ -2237,12 +2287,6 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wyz"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yaml-rust"
|
name = "yaml-rust"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
|
|
@ -2254,43 +2298,41 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zellij"
|
name = "zellij"
|
||||||
version = "0.11.0"
|
version = "0.12.0"
|
||||||
|
dependencies = [
|
||||||
|
"insta",
|
||||||
|
"names",
|
||||||
|
"zellij-client",
|
||||||
|
"zellij-server",
|
||||||
|
"zellij-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zellij-client"
|
||||||
|
version = "0.12.0"
|
||||||
|
dependencies = [
|
||||||
|
"termbg",
|
||||||
|
"zellij-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zellij-server"
|
||||||
|
version = "0.12.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term 0.12.1",
|
"ansi_term 0.12.1",
|
||||||
"async-std",
|
"async-trait",
|
||||||
"backtrace",
|
"daemonize",
|
||||||
"bincode",
|
|
||||||
"colors-transform",
|
|
||||||
"directories-next",
|
|
||||||
"futures",
|
|
||||||
"insta",
|
"insta",
|
||||||
"interprocess",
|
|
||||||
"lazy_static",
|
|
||||||
"libc",
|
|
||||||
"names",
|
|
||||||
"nix",
|
|
||||||
"nom",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
|
||||||
"signal-hook",
|
|
||||||
"strip-ansi-escapes",
|
|
||||||
"structopt",
|
|
||||||
"strum",
|
|
||||||
"tempfile",
|
|
||||||
"termion",
|
|
||||||
"termios",
|
|
||||||
"unicode-truncate",
|
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
"vte 0.10.1",
|
|
||||||
"wasmer",
|
"wasmer",
|
||||||
"wasmer-wasi",
|
"wasmer-wasi",
|
||||||
"zellij-tile",
|
"zellij-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zellij-tile"
|
name = "zellij-tile"
|
||||||
version = "0.11.0"
|
version = "0.12.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
@ -2300,7 +2342,33 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zellij-tile-utils"
|
name = "zellij-tile-utils"
|
||||||
version = "0.11.0"
|
version = "0.12.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term 0.12.1",
|
"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",
|
||||||
|
]
|
||||||
|
|
|
||||||
49
Cargo.toml
49
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "zellij"
|
name = "zellij"
|
||||||
version = "0.11.0"
|
version = "0.12.0"
|
||||||
authors = ["Aram Drevekenin <aram@poor.dev>"]
|
authors = ["Aram Drevekenin <aram@poor.dev>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A terminal workspace with batteries included"
|
description = "A terminal workspace with batteries included"
|
||||||
|
|
@ -8,51 +8,27 @@ license = "MIT"
|
||||||
repository = "https://github.com/zellij-org/zellij"
|
repository = "https://github.com/zellij-org/zellij"
|
||||||
homepage = "https://zellij.dev"
|
homepage = "https://zellij.dev"
|
||||||
include = ["src/**/*", "assets/plugins/*", "assets/layouts/*", "assets/config/*", "LICENSE.md", "README.md", "!**/*_test.*", "!**/tests/**/*"]
|
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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[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"
|
names = "0.11.0"
|
||||||
colors-transform = "0.2.5"
|
zellij-client = { path = "zellij-client/", version = "0.12.0" }
|
||||||
zellij-tile = { path = "zellij-tile/", version = "0.11.0" }
|
zellij-server = { path = "zellij-server/", version = "0.12.0" }
|
||||||
|
zellij-utils = { path = "zellij-utils/", version = "0.12.0" }
|
||||||
[dependencies.async-std]
|
|
||||||
version = "1.3.0"
|
|
||||||
features = ["unstable"]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = "1.6.0"
|
insta = "1.6.0"
|
||||||
tempfile = "3.2.0"
|
zellij-utils = { path = "zellij-utils/", version = "*", features = ["test"] }
|
||||||
|
zellij-client = { path = "zellij-client/", version = "*", features = ["test"] }
|
||||||
[build-dependencies]
|
zellij-server = { path = "zellij-server/", version = "*", features = ["test"] }
|
||||||
structopt = "0.3"
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
"zellij-client",
|
||||||
|
"zellij-server",
|
||||||
|
"zellij-utils",
|
||||||
"zellij-tile",
|
"zellij-tile",
|
||||||
"zellij-tile-utils",
|
"zellij-tile-utils",
|
||||||
"default-plugins/status-bar",
|
"default-plugins/status-bar",
|
||||||
|
|
@ -80,5 +56,4 @@ assets = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "enable_automatic_asset_installation", ]
|
disable_automatic_asset_installation = []
|
||||||
enable_automatic_asset_installation = []
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ env = { "SKIP_TEST" = true }
|
||||||
[tasks.test]
|
[tasks.test]
|
||||||
condition = { env_false = ["SKIP_TEST"] }
|
condition = { env_false = ["SKIP_TEST"] }
|
||||||
dependencies = ["pre-test"]
|
dependencies = ["pre-test"]
|
||||||
|
args = ["test", "--", "@@split(CARGO_MAKE_TASK_ARGS,;)"]
|
||||||
|
|
||||||
[tasks.post-test]
|
[tasks.post-test]
|
||||||
env = { "SKIP_TEST" = false }
|
env = { "SKIP_TEST" = false }
|
||||||
|
|
@ -37,6 +38,12 @@ run_task = "launch"
|
||||||
[tasks.build-workspace]
|
[tasks.build-workspace]
|
||||||
run_task = { name = "build", fork = true }
|
run_task = { name = "build", fork = true }
|
||||||
|
|
||||||
|
[tasks.build]
|
||||||
|
args = ["build"]
|
||||||
|
|
||||||
|
[tasks.build-release]
|
||||||
|
args = ["build", "--release"]
|
||||||
|
|
||||||
[tasks.build-dev-data-dir]
|
[tasks.build-dev-data-dir]
|
||||||
script_runner = "@duckscript"
|
script_runner = "@duckscript"
|
||||||
script = '''
|
script = '''
|
||||||
|
|
|
||||||
|
|
@ -36,15 +36,14 @@ cargo install zellij
|
||||||
|
|
||||||
Or you can download a prebuilt binary from our [Releases](https://github.com/zellij-org/zellij/releases).
|
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/),
|
The default plugins make use of characters that are mostly 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
|
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.
|
||||||
does not rely on such characters with `zellij options --simplified-ui`, or putting `simplified_ui: true` in the
|
|
||||||
config file.
|
|
||||||
|
|
||||||
## How do I hack on it? (Contributing)
|
## How do I hack on it? (Contributing)
|
||||||
* Clone the project
|
* Clone the project
|
||||||
* Install cargo-make with `cargo install --force cargo-make`
|
* Install cargo-make with `cargo install --force cargo-make`
|
||||||
* In the project folder, for debug builds run: `cargo make run`
|
* 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).
|
For more build commands, see [`Contributing.md`](CONTRIBUTING.md).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ keybinds:
|
||||||
key: [Ctrl: 't',]
|
key: [Ctrl: 't',]
|
||||||
- action: [SwitchToMode: Scroll,]
|
- action: [SwitchToMode: Scroll,]
|
||||||
key: [Ctrl: 's',]
|
key: [Ctrl: 's',]
|
||||||
|
- action: [SwitchToMode: Session,]
|
||||||
|
key: [Ctrl: 'o',]
|
||||||
- action: [Quit,]
|
- action: [Quit,]
|
||||||
key: [Ctrl: 'q',]
|
key: [Ctrl: 'q',]
|
||||||
- action: [NewPane: ]
|
- action: [NewPane: ]
|
||||||
|
|
@ -42,6 +44,8 @@ keybinds:
|
||||||
key: [Ctrl: 'r', Char: "\n", Char: ' ',]
|
key: [Ctrl: 'r', Char: "\n", Char: ' ',]
|
||||||
- action: [SwitchToMode: Scroll,]
|
- action: [SwitchToMode: Scroll,]
|
||||||
key: [Ctrl: 's']
|
key: [Ctrl: 's']
|
||||||
|
- action: [SwitchToMode: Session,]
|
||||||
|
key: [Ctrl: 'o',]
|
||||||
- action: [Quit]
|
- action: [Quit]
|
||||||
key: [Ctrl: 'q']
|
key: [Ctrl: 'q']
|
||||||
- action: [Resize: Left,]
|
- action: [Resize: Left,]
|
||||||
|
|
@ -77,6 +81,8 @@ keybinds:
|
||||||
key: [Ctrl: 'p', Char: "\n", Char: ' ',]
|
key: [Ctrl: 'p', Char: "\n", Char: ' ',]
|
||||||
- action: [SwitchToMode: Scroll,]
|
- action: [SwitchToMode: Scroll,]
|
||||||
key: [Ctrl: 's']
|
key: [Ctrl: 's']
|
||||||
|
- action: [SwitchToMode: Session,]
|
||||||
|
key: [Ctrl: 'o',]
|
||||||
- action: [Quit,]
|
- action: [Quit,]
|
||||||
key: [Ctrl: 'q',]
|
key: [Ctrl: 'q',]
|
||||||
- action: [MoveFocus: Left,]
|
- action: [MoveFocus: Left,]
|
||||||
|
|
@ -114,6 +120,8 @@ keybinds:
|
||||||
key: [Ctrl: 't', Char: "\n", Char: ' ',]
|
key: [Ctrl: 't', Char: "\n", Char: ' ',]
|
||||||
- action: [SwitchToMode: Scroll,]
|
- action: [SwitchToMode: Scroll,]
|
||||||
key: [Ctrl: 's']
|
key: [Ctrl: 's']
|
||||||
|
- action: [SwitchToMode: Session,]
|
||||||
|
key: [Ctrl: 'o',]
|
||||||
- action: [SwitchToMode: RenameTab, TabNameInput: [0],]
|
- action: [SwitchToMode: RenameTab, TabNameInput: [0],]
|
||||||
key: [Char: 'r']
|
key: [Char: 'r']
|
||||||
- action: [Quit,]
|
- action: [Quit,]
|
||||||
|
|
@ -168,6 +176,8 @@ keybinds:
|
||||||
key: [Ctrl: 'g',]
|
key: [Ctrl: 'g',]
|
||||||
- action: [SwitchToMode: Pane,]
|
- action: [SwitchToMode: Pane,]
|
||||||
key: [Ctrl: 'p',]
|
key: [Ctrl: 'p',]
|
||||||
|
- action: [SwitchToMode: Session,]
|
||||||
|
key: [Ctrl: 'o',]
|
||||||
- action: [Quit,]
|
- action: [Quit,]
|
||||||
key: [Ctrl: 'q',]
|
key: [Ctrl: 'q',]
|
||||||
- action: [ScrollDown,]
|
- action: [ScrollDown,]
|
||||||
|
|
@ -213,3 +223,20 @@ keybinds:
|
||||||
key: [ Alt: '[',]
|
key: [ Alt: '[',]
|
||||||
- action: [FocusNextPane,]
|
- action: [FocusNextPane,]
|
||||||
key: [ Alt: ']',]
|
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',]
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -22,6 +22,7 @@ enum CtrlKeyAction {
|
||||||
Resize,
|
Resize,
|
||||||
Scroll,
|
Scroll,
|
||||||
Quit,
|
Quit,
|
||||||
|
Session,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CtrlKeyMode {
|
enum CtrlKeyMode {
|
||||||
|
|
@ -39,16 +40,7 @@ impl CtrlKeyShortcut {
|
||||||
CtrlKeyAction::Resize => String::from("RESIZE"),
|
CtrlKeyAction::Resize => String::from("RESIZE"),
|
||||||
CtrlKeyAction::Scroll => String::from("SCROLL"),
|
CtrlKeyAction::Scroll => String::from("SCROLL"),
|
||||||
CtrlKeyAction::Quit => String::from("QUIT"),
|
CtrlKeyAction::Quit => String::from("QUIT"),
|
||||||
}
|
CtrlKeyAction::Session => String::from("SESSION"),
|
||||||
}
|
|
||||||
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"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn letter_shortcut(&self) -> char {
|
pub fn letter_shortcut(&self) -> char {
|
||||||
|
|
@ -59,6 +51,7 @@ impl CtrlKeyShortcut {
|
||||||
CtrlKeyAction::Resize => 'r',
|
CtrlKeyAction::Resize => 'r',
|
||||||
CtrlKeyAction::Scroll => 's',
|
CtrlKeyAction::Scroll => 's',
|
||||||
CtrlKeyAction::Quit => 'q',
|
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(
|
fn single_letter_ctrl_key(
|
||||||
key: &CtrlKeyShortcut,
|
key: &CtrlKeyShortcut,
|
||||||
palette: ColoredElements,
|
palette: ColoredElements,
|
||||||
|
|
@ -254,15 +221,6 @@ fn key_indicators(
|
||||||
return line_part;
|
return line_part;
|
||||||
}
|
}
|
||||||
line_part = LinePart::default();
|
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 {
|
for ctrl_key in keys {
|
||||||
let key = single_letter_ctrl_key(ctrl_key, palette, separator);
|
let key = single_letter_ctrl_key(ctrl_key, palette, separator);
|
||||||
line_part.part = format!("{}{}", line_part.part, key.part);
|
line_part.part = format!("{}{}", line_part.part, key.part);
|
||||||
|
|
@ -296,6 +254,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart {
|
||||||
CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Tab),
|
CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Tab),
|
||||||
CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Resize),
|
CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Resize),
|
||||||
CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Scroll),
|
CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Scroll),
|
||||||
|
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session),
|
||||||
CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Quit),
|
CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Quit),
|
||||||
],
|
],
|
||||||
colored_elements,
|
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::Unselected, CtrlKeyAction::Tab),
|
||||||
CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Resize),
|
CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Resize),
|
||||||
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll),
|
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll),
|
||||||
|
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session),
|
||||||
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
|
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
|
||||||
],
|
],
|
||||||
colored_elements,
|
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::Tab),
|
||||||
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize),
|
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize),
|
||||||
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll),
|
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll),
|
||||||
|
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session),
|
||||||
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
|
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
|
||||||
],
|
],
|
||||||
colored_elements,
|
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::Selected, CtrlKeyAction::Tab),
|
||||||
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize),
|
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize),
|
||||||
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll),
|
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll),
|
||||||
|
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session),
|
||||||
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
|
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
|
||||||
],
|
],
|
||||||
colored_elements,
|
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::Tab),
|
||||||
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize),
|
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize),
|
||||||
CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Scroll),
|
CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Scroll),
|
||||||
|
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session),
|
||||||
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
|
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
|
||||||
],
|
],
|
||||||
colored_elements,
|
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::Tab),
|
||||||
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize),
|
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize),
|
||||||
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll),
|
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),
|
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
|
||||||
],
|
],
|
||||||
colored_elements,
|
colored_elements,
|
||||||
|
|
|
||||||
|
|
@ -72,55 +72,61 @@ pub struct ColoredElements {
|
||||||
// that can be defined in the config perhaps
|
// that can be defined in the config perhaps
|
||||||
fn color_elements(palette: Palette) -> ColoredElements {
|
fn color_elements(palette: Palette) -> ColoredElements {
|
||||||
match palette.source {
|
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 {
|
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_left_separator: style!(palette.black, palette.green).bold(),
|
||||||
selected_char_shortcut: style!(palette.red, palette.green).bold(),
|
selected_char_shortcut: style!(palette.red, palette.green).bold(),
|
||||||
selected_char_right_separator: style!(palette.black, palette.green).bold(),
|
selected_char_right_separator: style!(palette.black, palette.green).bold(),
|
||||||
selected_styled_text: style!(palette.black, palette.green).bold(),
|
selected_styled_text: style!(palette.black, palette.green).bold(),
|
||||||
selected_suffix_separator: style!(palette.green, palette.bg).bold(),
|
selected_suffix_separator: style!(palette.green, palette.cyan).bold(),
|
||||||
unselected_prefix_separator: style!(palette.bg, palette.fg),
|
unselected_prefix_separator: style!(palette.cyan, palette.fg),
|
||||||
unselected_char_left_separator: style!(palette.black, palette.fg).bold(),
|
unselected_char_left_separator: style!(palette.black, palette.fg).bold(),
|
||||||
unselected_char_shortcut: style!(palette.red, palette.fg).bold(),
|
unselected_char_shortcut: style!(palette.red, palette.fg).bold(),
|
||||||
unselected_char_right_separator: style!(palette.black, palette.fg).bold(),
|
unselected_char_right_separator: style!(palette.black, palette.fg).bold(),
|
||||||
unselected_styled_text: style!(palette.black, palette.fg).bold(),
|
unselected_styled_text: style!(palette.black, palette.fg).bold(),
|
||||||
unselected_suffix_separator: style!(palette.fg, palette.bg),
|
unselected_suffix_separator: style!(palette.fg, palette.cyan),
|
||||||
disabled_prefix_separator: style!(palette.bg, palette.fg),
|
disabled_prefix_separator: style!(palette.cyan, palette.fg),
|
||||||
disabled_styled_text: style!(palette.bg, palette.fg).dimmed(),
|
disabled_styled_text: style!(palette.cyan, palette.fg).dimmed(),
|
||||||
disabled_suffix_separator: style!(palette.fg, palette.bg),
|
disabled_suffix_separator: style!(palette.fg, palette.cyan),
|
||||||
selected_single_letter_prefix_separator: style!(palette.bg, palette.green),
|
selected_single_letter_prefix_separator: style!(palette.cyan, palette.green),
|
||||||
selected_single_letter_char_shortcut: style!(palette.red, palette.green).bold(),
|
selected_single_letter_char_shortcut: style!(palette.red, palette.green).bold(),
|
||||||
selected_single_letter_suffix_separator: style!(palette.green, palette.bg),
|
selected_single_letter_suffix_separator: style!(palette.green, palette.cyan),
|
||||||
unselected_single_letter_prefix_separator: style!(palette.bg, palette.fg),
|
unselected_single_letter_prefix_separator: style!(palette.cyan, palette.fg),
|
||||||
unselected_single_letter_char_shortcut: style!(palette.red, palette.fg).bold(),
|
unselected_single_letter_char_shortcut: style!(palette.red, palette.fg).bold(),
|
||||||
unselected_single_letter_suffix_separator: style!(palette.fg, palette.bg),
|
unselected_single_letter_suffix_separator: style!(palette.fg, palette.cyan),
|
||||||
superkey_prefix: style!(palette.white, palette.bg).bold(),
|
superkey_prefix: style!(palette.white, palette.cyan).bold(),
|
||||||
superkey_suffix_separator: style!(palette.bg, palette.bg),
|
superkey_suffix_separator: style!(palette.cyan, palette.cyan),
|
||||||
},
|
},
|
||||||
PaletteSource::Xresources => ColoredElements {
|
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_left_separator: style!(palette.fg, palette.green).bold(),
|
||||||
selected_char_shortcut: style!(palette.red, palette.green).bold(),
|
selected_char_shortcut: style!(palette.red, palette.green).bold(),
|
||||||
selected_char_right_separator: style!(palette.fg, palette.green).bold(),
|
selected_char_right_separator: style!(palette.fg, palette.green).bold(),
|
||||||
selected_styled_text: style!(palette.bg, palette.green).bold(),
|
selected_styled_text: style!(palette.cyan, palette.green).bold(),
|
||||||
selected_suffix_separator: style!(palette.green, palette.bg).bold(),
|
selected_suffix_separator: style!(palette.green, palette.cyan).bold(),
|
||||||
unselected_prefix_separator: style!(palette.bg, palette.fg),
|
unselected_prefix_separator: style!(palette.cyan, palette.fg),
|
||||||
unselected_char_left_separator: style!(palette.bg, palette.fg).bold(),
|
unselected_char_left_separator: style!(palette.cyan, palette.fg).bold(),
|
||||||
unselected_char_shortcut: style!(palette.red, palette.fg).bold(),
|
unselected_char_shortcut: style!(palette.red, palette.fg).bold(),
|
||||||
unselected_char_right_separator: style!(palette.bg, palette.fg).bold(),
|
unselected_char_right_separator: style!(palette.cyan, palette.fg).bold(),
|
||||||
unselected_styled_text: style!(palette.bg, palette.fg).bold(),
|
unselected_styled_text: style!(palette.cyan, palette.fg).bold(),
|
||||||
unselected_suffix_separator: style!(palette.fg, palette.bg),
|
unselected_suffix_separator: style!(palette.fg, palette.cyan),
|
||||||
disabled_prefix_separator: style!(palette.bg, palette.fg),
|
disabled_prefix_separator: style!(palette.cyan, palette.fg),
|
||||||
disabled_styled_text: style!(palette.bg, palette.fg).dimmed(),
|
disabled_styled_text: style!(palette.cyan, palette.fg).dimmed(),
|
||||||
disabled_suffix_separator: style!(palette.fg, palette.bg),
|
disabled_suffix_separator: style!(palette.fg, palette.cyan),
|
||||||
selected_single_letter_prefix_separator: style!(palette.fg, palette.green),
|
selected_single_letter_prefix_separator: style!(palette.fg, palette.green),
|
||||||
selected_single_letter_char_shortcut: style!(palette.red, palette.green).bold(),
|
selected_single_letter_char_shortcut: style!(palette.red, palette.green).bold(),
|
||||||
selected_single_letter_suffix_separator: style!(palette.green, palette.fg),
|
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_char_shortcut: style!(palette.red, palette.fg).bold(),
|
||||||
unselected_single_letter_suffix_separator: style!(palette.fg, palette.bg),
|
unselected_single_letter_suffix_separator: style!(palette.fg, palette.cyan),
|
||||||
superkey_prefix: style!(palette.bg, palette.fg).bold(),
|
superkey_prefix: style!(palette.cyan, palette.fg).bold(),
|
||||||
superkey_suffix_separator: style!(palette.fg, palette.bg),
|
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
|
// [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
|
// [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)) => {
|
PaletteColor::Rgb((r, g, b)) => {
|
||||||
println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", first_line, r, g, b);
|
println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", first_line, r, g, b);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,11 +62,11 @@ fn left_more_message(tab_count_to_the_left: usize, palette: Palette, separator:
|
||||||
};
|
};
|
||||||
// 238
|
// 238
|
||||||
let more_text_len = more_text.chars().count() + 2; // 2 for the arrows
|
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)
|
let more_styled_text = style!(palette.black, palette.orange)
|
||||||
.bold()
|
.bold()
|
||||||
.paint(more_text);
|
.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!(
|
let more_styled_text = format!(
|
||||||
"{}",
|
"{}",
|
||||||
ANSIStrings(&[left_separator, more_styled_text, right_separator,])
|
ANSIStrings(&[left_separator, more_styled_text, right_separator,])
|
||||||
|
|
@ -94,11 +94,11 @@ fn right_more_message(
|
||||||
" +many → ".to_string()
|
" +many → ".to_string()
|
||||||
};
|
};
|
||||||
let more_text_len = more_text.chars().count() + 1; // 2 for the arrow
|
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)
|
let more_styled_text = style!(palette.black, palette.orange)
|
||||||
.bold()
|
.bold()
|
||||||
.paint(more_text);
|
.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!(
|
let more_styled_text = format!(
|
||||||
"{}",
|
"{}",
|
||||||
ANSIStrings(&[left_separator, more_styled_text, right_separator,])
|
ANSIStrings(&[left_separator, more_styled_text, right_separator,])
|
||||||
|
|
@ -147,7 +147,9 @@ fn add_next_tabs_msg(
|
||||||
fn tab_line_prefix(palette: Palette) -> LinePart {
|
fn tab_line_prefix(palette: Palette) -> LinePart {
|
||||||
let prefix_text = " Zellij ".to_string();
|
let prefix_text = " Zellij ".to_string();
|
||||||
let prefix_text_len = prefix_text.chars().count();
|
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 {
|
LinePart {
|
||||||
part: format!("{}", prefix_styled_text),
|
part: format!("{}", prefix_styled_text),
|
||||||
len: prefix_text_len,
|
len: prefix_text_len,
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ impl ZellijPlugin for State {
|
||||||
for bar_part in tab_line {
|
for bar_part in tab_line {
|
||||||
s = format!("{}{}", s, bar_part.part);
|
s = format!("{}{}", s, bar_part.part);
|
||||||
}
|
}
|
||||||
match self.mode_info.palette.bg {
|
match self.mode_info.palette.cyan {
|
||||||
PaletteColor::Rgb((r, g, b)) => {
|
PaletteColor::Rgb((r, g, b)) => {
|
||||||
println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", s, r, g, b);
|
println!("{}\u{1b}[48;2;{};{};{}m\u{1b}[0K", s, r, g, b);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ use zellij_tile::prelude::*;
|
||||||
use zellij_tile_utils::style;
|
use zellij_tile_utils::style;
|
||||||
|
|
||||||
pub fn active_tab(text: String, palette: Palette, separator: &str) -> LinePart {
|
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_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)
|
let tab_styled_text = style!(palette.black, palette.green)
|
||||||
.bold()
|
.bold()
|
||||||
.paint(format!(" {} ", text));
|
.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!(
|
let tab_styled_text = format!(
|
||||||
"{}",
|
"{}",
|
||||||
ANSIStrings(&[left_separator, tab_styled_text, right_separator,])
|
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 {
|
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_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)
|
let tab_styled_text = style!(palette.black, palette.fg)
|
||||||
.bold()
|
.bold()
|
||||||
.paint(format!(" {} ", text));
|
.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!(
|
let tab_styled_text = format!(
|
||||||
"{}",
|
"{}",
|
||||||
ANSIStrings(&[left_separator, tab_styled_text, right_separator,])
|
ANSIStrings(&[left_separator, tab_styled_text, right_separator,])
|
||||||
|
|
|
||||||
47
src/cli.rs
47
src/cli.rs
|
|
@ -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),
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
//! Zellij utilities.
|
|
||||||
|
|
||||||
pub mod consts;
|
|
||||||
pub mod logging;
|
|
||||||
pub mod shared;
|
|
||||||
117
src/main.rs
117
src/main.rs
|
|
@ -1,63 +1,80 @@
|
||||||
mod cli;
|
mod sessions;
|
||||||
mod client;
|
|
||||||
mod common;
|
|
||||||
mod server;
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
use client::{boundaries, layout, panes, start_client, tab};
|
use sessions::{assert_session, assert_session_ne, list_sessions};
|
||||||
use common::{
|
|
||||||
command_is_executing, errors, os_input_output, pty, screen, setup::Setup, utils, wasm_vm,
|
|
||||||
};
|
|
||||||
use server::start_server;
|
|
||||||
use structopt::StructOpt;
|
|
||||||
|
|
||||||
use crate::cli::CliArgs;
|
|
||||||
use crate::command_is_executing::CommandIsExecuting;
|
|
||||||
use crate::common::input::{config::Config, options::Options};
|
|
||||||
use crate::os_input_output::{get_client_os_input, get_server_os_input, ClientOsApi, ServerOsApi};
|
|
||||||
use crate::utils::{
|
|
||||||
consts::{ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR},
|
|
||||||
logging::*,
|
|
||||||
};
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::process;
|
||||||
|
use zellij_client::{os_input_output::get_client_os_input, start_client, ClientInfo};
|
||||||
|
use zellij_server::{os_input_output::get_server_os_input, start_server};
|
||||||
|
use zellij_utils::{
|
||||||
|
cli::{CliArgs, Command, Sessions},
|
||||||
|
consts::{ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR},
|
||||||
|
input::config::Config,
|
||||||
|
logging::*,
|
||||||
|
setup::Setup,
|
||||||
|
structopt::StructOpt,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
let opts = CliArgs::from_args();
|
let opts = CliArgs::from_args();
|
||||||
|
|
||||||
if let Some(crate::cli::ConfigCli::Setup(setup)) = opts.option.clone() {
|
if let Some(Command::Sessions(Sessions::ListSessions)) = opts.command {
|
||||||
Setup::from_cli(&setup, opts).expect("Failed to print to stdout");
|
list_sessions();
|
||||||
std::process::exit(0);
|
} else if let Some(Command::Setup(ref setup)) = opts.command {
|
||||||
} else {
|
Setup::from_cli(setup, &opts).expect("Failed to print to stdout");
|
||||||
let config = match Config::try_from(&opts) {
|
}
|
||||||
Ok(config) => config,
|
|
||||||
|
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) => {
|
Err(e) => {
|
||||||
eprintln!("There was an error in the config file:\n{}", e);
|
eprintln!("failed to open terminal:\n{}", e);
|
||||||
std::process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let config_options = Options::from_cli(&config.options, opts.option.clone());
|
start_server(Box::new(os_input), path);
|
||||||
atomic_create_dir(&*ZELLIJ_TMP_DIR).unwrap();
|
} else {
|
||||||
atomic_create_dir(&*ZELLIJ_TMP_LOG_DIR).unwrap();
|
let os_input = match get_client_os_input() {
|
||||||
let server_os_input = get_server_os_input();
|
Ok(os_input) => os_input,
|
||||||
let os_input = get_client_os_input();
|
Err(e) => {
|
||||||
start(
|
eprintln!("failed to open terminal:\n{}", e);
|
||||||
Box::new(os_input),
|
process::exit(1);
|
||||||
opts,
|
}
|
||||||
Box::new(server_os_input),
|
};
|
||||||
config,
|
if let Some(Command::Sessions(Sessions::Attach {
|
||||||
config_options,
|
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());
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
109
src/sessions.rs
Normal file
109
src/sessions.rs
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use crate::panes::PositionAndSize;
|
|
||||||
use interprocess::local_socket::LocalSocketStream;
|
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
|
|
@ -7,21 +5,28 @@ use std::path::PathBuf;
|
||||||
use std::sync::{mpsc, Arc, Condvar, Mutex};
|
use std::sync::{mpsc, Arc, Condvar, Mutex};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use crate::client::ClientInstruction;
|
use zellij_utils::{nix, zellij_tile};
|
||||||
use crate::common::thread_bus::{ChannelWithContext, SenderType, SenderWithContext};
|
|
||||||
use crate::errors::ErrorContext;
|
|
||||||
use crate::os_input_output::{ClientOsApi, ServerOsApi};
|
|
||||||
use crate::server::ServerInstruction;
|
|
||||||
use crate::tests::possible_tty_inputs::{get_possible_tty_inputs, Bytes};
|
use crate::tests::possible_tty_inputs::{get_possible_tty_inputs, Bytes};
|
||||||
use crate::tests::utils::commands::{QUIT, SLEEP};
|
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_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);
|
const MIN_TIME_BETWEEN_SNAPSHOTS: Duration = Duration::from_millis(150);
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum IoEvent {
|
pub enum IoEvent {
|
||||||
Kill(RawFd),
|
Kill(Pid),
|
||||||
SetTerminalSizeUsingFd(RawFd, u16, u16),
|
SetTerminalSizeUsingFd(RawFd, u16, u16),
|
||||||
IntoRawMode(RawFd),
|
IntoRawMode(RawFd),
|
||||||
UnsetRawMode(RawFd),
|
UnsetRawMode(RawFd),
|
||||||
|
|
@ -77,10 +82,10 @@ pub struct FakeInputOutput {
|
||||||
win_sizes: Arc<Mutex<HashMap<RawFd, PositionAndSize>>>,
|
win_sizes: Arc<Mutex<HashMap<RawFd, PositionAndSize>>>,
|
||||||
possible_tty_inputs: HashMap<u16, Bytes>,
|
possible_tty_inputs: HashMap<u16, Bytes>,
|
||||||
last_snapshot_time: Arc<Mutex<Instant>>,
|
last_snapshot_time: Arc<Mutex<Instant>>,
|
||||||
send_instructions_to_client: SenderWithContext<ClientInstruction>,
|
send_instructions_to_client: SenderWithContext<ServerToClientMsg>,
|
||||||
receive_instructions_from_server: Arc<Mutex<mpsc::Receiver<(ClientInstruction, ErrorContext)>>>,
|
receive_instructions_from_server: Arc<Mutex<mpsc::Receiver<(ServerToClientMsg, ErrorContext)>>>,
|
||||||
send_instructions_to_server: SenderWithContext<ServerInstruction>,
|
send_instructions_to_server: SenderWithContext<ClientToServerMsg>,
|
||||||
receive_instructions_from_client: Arc<Mutex<mpsc::Receiver<(ServerInstruction, ErrorContext)>>>,
|
receive_instructions_from_client: Arc<Mutex<mpsc::Receiver<(ClientToServerMsg, ErrorContext)>>>,
|
||||||
should_trigger_sigwinch: Arc<(Mutex<bool>, Condvar)>,
|
should_trigger_sigwinch: Arc<(Mutex<bool>, Condvar)>,
|
||||||
sigwinch_event: Option<PositionAndSize>,
|
sigwinch_event: Option<PositionAndSize>,
|
||||||
}
|
}
|
||||||
|
|
@ -90,10 +95,10 @@ impl FakeInputOutput {
|
||||||
let mut win_sizes = HashMap::new();
|
let mut win_sizes = HashMap::new();
|
||||||
let last_snapshot_time = Arc::new(Mutex::new(Instant::now()));
|
let last_snapshot_time = Arc::new(Mutex::new(Instant::now()));
|
||||||
let stdout_writer = FakeStdoutWriter::new(last_snapshot_time.clone());
|
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();
|
mpsc::channel();
|
||||||
let send_instructions_to_client = SenderWithContext::new(SenderType::Sender(client_sender));
|
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();
|
mpsc::channel();
|
||||||
let send_instructions_to_server = SenderWithContext::new(SenderType::Sender(server_sender));
|
let send_instructions_to_server = SenderWithContext::new(SenderType::Sender(server_sender));
|
||||||
win_sizes.insert(0, winsize); // 0 is the current terminal
|
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));
|
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![]);
|
self.stdin_writes.lock().unwrap().insert(fd, vec![]);
|
||||||
}
|
}
|
||||||
pub fn add_sigwinch_event(&mut self, new_position_and_size: PositionAndSize) {
|
pub fn add_sigwinch_event(&mut self, new_position_and_size: PositionAndSize) {
|
||||||
|
|
@ -153,7 +158,7 @@ impl ClientOsApi for FakeInputOutput {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.push(IoEvent::IntoRawMode(pid));
|
.push(IoEvent::IntoRawMode(pid));
|
||||||
}
|
}
|
||||||
fn unset_raw_mode(&mut self, pid: RawFd) {
|
fn unset_raw_mode(&self, pid: RawFd) {
|
||||||
self.io_events
|
self.io_events
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -195,17 +200,17 @@ impl ClientOsApi for FakeInputOutput {
|
||||||
fn get_stdout_writer(&self) -> Box<dyn Write> {
|
fn get_stdout_writer(&self) -> Box<dyn Write> {
|
||||||
Box::new(self.stdout_writer.clone())
|
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();
|
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
|
self.receive_instructions_from_server
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.recv()
|
.recv()
|
||||||
.unwrap()
|
.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() {
|
if self.sigwinch_event.is_some() {
|
||||||
let (lock, cvar) = &*self.should_trigger_sigwinch;
|
let (lock, cvar) = &*self.should_trigger_sigwinch;
|
||||||
{
|
{
|
||||||
|
|
@ -214,14 +219,44 @@ impl ClientOsApi for FakeInputOutput {
|
||||||
should_trigger_sigwinch = cvar.wait(should_trigger_sigwinch).unwrap();
|
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 {
|
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
|
let terminal_input = self
|
||||||
.possible_tty_inputs
|
.possible_tty_inputs
|
||||||
.get(&cols)
|
.get(&cols)
|
||||||
|
|
@ -235,12 +270,15 @@ impl ServerOsApi for FakeInputOutput {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.push(IoEvent::SetTerminalSizeUsingFd(pid, cols, rows));
|
.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;
|
let next_terminal_id = self.stdin_writes.lock().unwrap().keys().len() as RawFd + 1;
|
||||||
self.add_terminal(next_terminal_id);
|
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 mut stdin_writes = self.stdin_writes.lock().unwrap();
|
||||||
let write_buffer = stdin_writes.get_mut(&pid).unwrap();
|
let write_buffer = stdin_writes.get_mut(&pid).unwrap();
|
||||||
let mut bytes_written = 0;
|
let mut bytes_written = 0;
|
||||||
|
|
@ -250,7 +288,7 @@ impl ServerOsApi for FakeInputOutput {
|
||||||
}
|
}
|
||||||
Ok(bytes_written)
|
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 read_buffers = self.read_buffers.lock().unwrap();
|
||||||
let mut bytes_read = 0;
|
let mut bytes_read = 0;
|
||||||
match read_buffers.get_mut(&pid) {
|
match read_buffers.get_mut(&pid) {
|
||||||
|
|
@ -267,28 +305,36 @@ impl ServerOsApi for FakeInputOutput {
|
||||||
None => Err(nix::Error::Sys(nix::errno::Errno::EAGAIN)),
|
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));
|
self.io_events.lock().unwrap().push(IoEvent::TcDrain(pid));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn box_clone(&self) -> Box<dyn ServerOsApi> {
|
fn box_clone(&self) -> Box<dyn ServerOsApi> {
|
||||||
Box::new((*self).clone())
|
Box::new((*self).clone())
|
||||||
}
|
}
|
||||||
fn kill(&mut self, fd: RawFd) -> Result<(), nix::Error> {
|
fn kill(&self, pid: Pid) -> Result<(), nix::Error> {
|
||||||
self.io_events.lock().unwrap().push(IoEvent::Kill(fd));
|
self.io_events.lock().unwrap().push(IoEvent::Kill(pid));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn recv_from_client(&self) -> (ServerInstruction, ErrorContext) {
|
fn recv_from_client(&self) -> (ClientToServerMsg, ErrorContext) {
|
||||||
self.receive_instructions_from_client
|
self.receive_instructions_from_client
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.recv()
|
.recv()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
fn send_to_client(&self, msg: ClientInstruction) {
|
fn send_to_client(&self, msg: ServerToClientMsg) {
|
||||||
self.send_instructions_to_client.send(msg).unwrap();
|
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 update_receiver(&mut self, _stream: LocalSocketStream) {}
|
||||||
fn load_palette(&self) -> Palette {
|
fn load_palette(&self) -> Palette {
|
||||||
default_palette()
|
default_palette()
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::panes::PositionAndSize;
|
|
||||||
use ::insta::assert_snapshot;
|
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::fakes::FakeInputOutput;
|
||||||
|
use crate::tests::start;
|
||||||
use crate::tests::utils::commands::{
|
use crate::tests::utils::commands::{
|
||||||
BRACKETED_PASTE_END, BRACKETED_PASTE_START, PANE_MODE, QUIT, SCROLL_DOWN_IN_SCROLL_MODE,
|
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,
|
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,
|
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::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 {
|
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
||||||
FakeInputOutput::new(fake_win_size.clone())
|
FakeInputOutput::new(fake_win_size.clone())
|
||||||
|
|
@ -32,7 +33,6 @@ pub fn starts_with_one_terminal() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -61,7 +61,6 @@ pub fn split_terminals_vertically() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -90,7 +89,6 @@ pub fn split_terminals_horizontally() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -126,7 +124,6 @@ pub fn split_largest_terminal() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -155,7 +152,6 @@ pub fn cannot_split_terminals_vertically_when_active_terminal_is_too_small() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -184,7 +180,6 @@ pub fn cannot_split_terminals_horizontally_when_active_terminal_is_too_small() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -213,7 +208,6 @@ pub fn cannot_split_largest_terminal_when_there_is_no_room() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -250,7 +244,6 @@ pub fn scrolling_up_inside_a_pane() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -289,7 +282,6 @@ pub fn scrolling_down_inside_a_pane() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -325,7 +317,6 @@ pub fn scrolling_page_up_inside_a_pane() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -364,7 +355,6 @@ pub fn scrolling_page_down_inside_a_pane() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -404,7 +394,6 @@ pub fn max_panes() {
|
||||||
opts,
|
opts,
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -442,7 +431,6 @@ pub fn toggle_focused_pane_fullscreen() {
|
||||||
opts,
|
opts,
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -483,7 +471,6 @@ pub fn bracketed_paste() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
use crate::panes::PositionAndSize;
|
|
||||||
use ::insta::assert_snapshot;
|
use ::insta::assert_snapshot;
|
||||||
|
use zellij_utils::pane_size::PositionAndSize;
|
||||||
|
|
||||||
use crate::tests::fakes::FakeInputOutput;
|
use crate::tests::fakes::FakeInputOutput;
|
||||||
|
use crate::tests::start;
|
||||||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
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::{
|
use crate::tests::utils::commands::{
|
||||||
CLOSE_PANE_IN_PANE_MODE, ESC, MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT,
|
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,
|
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,
|
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 {
|
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
||||||
FakeInputOutput::new(fake_win_size.clone())
|
FakeInputOutput::new(fake_win_size.clone())
|
||||||
|
|
@ -45,7 +46,6 @@ pub fn close_pane_with_another_pane_above_it() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -89,7 +89,6 @@ pub fn close_pane_with_another_pane_below_it() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -130,7 +129,6 @@ pub fn close_pane_with_another_pane_to_the_left() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -172,7 +170,6 @@ pub fn close_pane_with_another_pane_to_the_right() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -219,7 +216,6 @@ pub fn close_pane_with_multiple_panes_above_it() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -264,7 +260,6 @@ pub fn close_pane_with_multiple_panes_below_it() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -311,7 +306,6 @@ pub fn close_pane_with_multiple_panes_to_the_left() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -356,7 +350,6 @@ pub fn close_pane_with_multiple_panes_to_the_right() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -654,7 +643,6 @@ pub fn closing_last_pane_exits_app() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
use ::insta::assert_snapshot;
|
use ::insta::assert_snapshot;
|
||||||
use ::std::collections::HashMap;
|
use ::std::collections::HashMap;
|
||||||
|
|
||||||
use crate::panes::PositionAndSize;
|
|
||||||
use crate::tests::fakes::FakeInputOutput;
|
use crate::tests::fakes::FakeInputOutput;
|
||||||
use crate::tests::possible_tty_inputs::Bytes;
|
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::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 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.
|
* 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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -78,7 +78,6 @@ pub fn fish_tab_completion_options() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -113,7 +112,6 @@ pub fn fish_select_tab_completion_options() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -152,7 +150,6 @@ pub fn vim_scroll_region_down() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -188,7 +185,6 @@ pub fn vim_ctrl_d() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -223,7 +219,6 @@ pub fn vim_ctrl_u() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -253,7 +248,6 @@ pub fn htop() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -283,7 +277,6 @@ pub fn htop_scrolling() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -313,7 +306,6 @@ pub fn htop_right_scrolling() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -351,7 +343,6 @@ pub fn vim_overwrite() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -384,7 +375,6 @@ pub fn clear_scroll_region() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -414,7 +404,6 @@ pub fn display_tab_characters_properly() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -444,7 +433,6 @@ pub fn neovim_insert_mode() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -476,7 +464,6 @@ pub fn bash_cursor_linewrap() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -508,7 +495,6 @@ pub fn fish_paste_multiline() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -538,7 +524,6 @@ pub fn git_log() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -570,7 +555,6 @@ pub fn git_diff_scrollup() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -600,7 +584,6 @@ pub fn emacs_longbuf() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -630,7 +613,6 @@ pub fn top_and_quit() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -666,7 +648,6 @@ pub fn exa_plus_omf_theme() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
use insta::assert_snapshot;
|
use insta::assert_snapshot;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::common::input::{config::Config, options::Options};
|
|
||||||
use crate::panes::PositionAndSize;
|
|
||||||
use crate::tests::fakes::FakeInputOutput;
|
use crate::tests::fakes::FakeInputOutput;
|
||||||
|
use crate::tests::start;
|
||||||
use crate::tests::utils::commands::QUIT;
|
use crate::tests::utils::commands::QUIT;
|
||||||
use crate::tests::utils::get_output_frame_snapshots;
|
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 {
|
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
||||||
FakeInputOutput::new(fake_win_size.clone())
|
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);
|
let mut fake_input_output = get_fake_os_input(&fake_win_size);
|
||||||
fake_input_output.add_terminal_input(&[&QUIT]);
|
fake_input_output.add_terminal_input(&[&QUIT]);
|
||||||
let mut opts = CliArgs::default();
|
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",
|
"src/tests/fixtures/layouts/three-panes-with-nesting.yaml",
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
@ -33,7 +34,6 @@ pub fn accepts_basic_layout() {
|
||||||
opts,
|
opts,
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
use ::insta::assert_snapshot;
|
use ::insta::assert_snapshot;
|
||||||
|
|
||||||
use crate::panes::PositionAndSize;
|
|
||||||
use crate::tests::fakes::FakeInputOutput;
|
use crate::tests::fakes::FakeInputOutput;
|
||||||
|
use crate::tests::start;
|
||||||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
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::{
|
use crate::tests::utils::commands::{
|
||||||
MOVE_FOCUS_DOWN_IN_PANE_MODE, MOVE_FOCUS_UP_IN_PANE_MODE, PANE_MODE, QUIT,
|
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,
|
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 {
|
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
||||||
FakeInputOutput::new(*fake_win_size)
|
FakeInputOutput::new(*fake_win_size)
|
||||||
|
|
@ -37,7 +38,6 @@ pub fn move_focus_down() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -75,7 +75,6 @@ pub fn move_focus_down_to_the_most_recently_used_pane() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
use ::insta::assert_snapshot;
|
use ::insta::assert_snapshot;
|
||||||
|
|
||||||
use crate::panes::PositionAndSize;
|
|
||||||
use crate::tests::fakes::FakeInputOutput;
|
use crate::tests::fakes::FakeInputOutput;
|
||||||
|
use crate::tests::start;
|
||||||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
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::{
|
use crate::tests::utils::commands::{
|
||||||
ENTER, MOVE_FOCUS_LEFT_IN_NORMAL_MODE, MOVE_FOCUS_LEFT_IN_PANE_MODE,
|
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,
|
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,
|
SPLIT_RIGHT_IN_PANE_MODE, TAB_MODE,
|
||||||
};
|
};
|
||||||
|
use zellij_utils::input::config::Config;
|
||||||
|
|
||||||
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
||||||
FakeInputOutput::new(*fake_win_size)
|
FakeInputOutput::new(*fake_win_size)
|
||||||
|
|
@ -37,7 +38,6 @@ pub fn move_focus_left() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -76,7 +76,6 @@ pub fn move_focus_left_to_the_most_recently_used_pane() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -115,7 +114,6 @@ pub fn move_focus_left_changes_tab() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
use ::insta::assert_snapshot;
|
use ::insta::assert_snapshot;
|
||||||
|
|
||||||
use crate::panes::PositionAndSize;
|
|
||||||
use crate::tests::fakes::FakeInputOutput;
|
use crate::tests::fakes::FakeInputOutput;
|
||||||
|
use crate::tests::start;
|
||||||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
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::{
|
use crate::tests::utils::commands::{
|
||||||
ENTER, MOVE_FOCUS_LEFT_IN_PANE_MODE, MOVE_FOCUS_RIGHT_IN_NORMAL_MODE,
|
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,
|
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,
|
SPLIT_RIGHT_IN_PANE_MODE, TAB_MODE,
|
||||||
};
|
};
|
||||||
|
use zellij_utils::input::config::Config;
|
||||||
|
|
||||||
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
||||||
FakeInputOutput::new(*fake_win_size)
|
FakeInputOutput::new(*fake_win_size)
|
||||||
|
|
@ -38,7 +39,6 @@ pub fn move_focus_right() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -76,7 +76,6 @@ pub fn move_focus_right_to_the_most_recently_used_pane() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -115,7 +114,6 @@ pub fn move_focus_right_changes_tab() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
use ::insta::assert_snapshot;
|
use ::insta::assert_snapshot;
|
||||||
|
|
||||||
use crate::panes::PositionAndSize;
|
|
||||||
use crate::tests::fakes::FakeInputOutput;
|
use crate::tests::fakes::FakeInputOutput;
|
||||||
|
use crate::tests::start;
|
||||||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
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::{
|
use crate::tests::utils::commands::{
|
||||||
MOVE_FOCUS_DOWN_IN_PANE_MODE, MOVE_FOCUS_UP_IN_PANE_MODE, PANE_MODE, QUIT,
|
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,
|
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 {
|
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
||||||
FakeInputOutput::new(*fake_win_size)
|
FakeInputOutput::new(*fake_win_size)
|
||||||
|
|
@ -36,7 +37,6 @@ pub fn move_focus_up() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -75,7 +75,6 @@ pub fn move_focus_up_to_the_most_recently_used_pane() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
use insta::assert_snapshot;
|
use insta::assert_snapshot;
|
||||||
|
|
||||||
use crate::panes::PositionAndSize;
|
|
||||||
use crate::tests::fakes::FakeInputOutput;
|
use crate::tests::fakes::FakeInputOutput;
|
||||||
|
use crate::tests::start;
|
||||||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
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::{
|
use crate::tests::utils::commands::{
|
||||||
MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_DOWN_IN_RESIZE_MODE,
|
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,
|
RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, SLEEP, SPLIT_DOWN_IN_PANE_MODE,
|
||||||
SPLIT_RIGHT_IN_PANE_MODE,
|
SPLIT_RIGHT_IN_PANE_MODE,
|
||||||
};
|
};
|
||||||
|
use zellij_utils::input::config::Config;
|
||||||
|
|
||||||
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
||||||
FakeInputOutput::new(*fake_win_size)
|
FakeInputOutput::new(*fake_win_size)
|
||||||
|
|
@ -48,7 +49,6 @@ pub fn resize_down_with_pane_above() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -94,7 +94,6 @@ pub fn resize_down_with_pane_below() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -146,7 +145,6 @@ pub fn resize_down_with_panes_above_and_below() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -197,7 +195,6 @@ pub fn resize_down_with_multiple_panes_above() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -250,7 +247,6 @@ pub fn resize_down_with_panes_above_aligned_left_with_current_pane() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -302,7 +298,6 @@ pub fn resize_down_with_panes_below_aligned_left_with_current_pane() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -352,7 +347,6 @@ pub fn resize_down_with_panes_above_aligned_right_with_current_pane() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -403,7 +397,6 @@ pub fn resize_down_with_panes_below_aligned_right_with_current_pane() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -704,7 +693,6 @@ pub fn cannot_resize_down_when_pane_below_is_at_minimum_height() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
use ::insta::assert_snapshot;
|
use ::insta::assert_snapshot;
|
||||||
|
|
||||||
use crate::panes::PositionAndSize;
|
|
||||||
use crate::tests::fakes::FakeInputOutput;
|
use crate::tests::fakes::FakeInputOutput;
|
||||||
|
use crate::tests::start;
|
||||||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
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::{
|
use crate::tests::utils::commands::{
|
||||||
MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE,
|
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,
|
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 {
|
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
||||||
FakeInputOutput::new(*fake_win_size)
|
FakeInputOutput::new(*fake_win_size)
|
||||||
|
|
@ -44,7 +45,6 @@ pub fn resize_left_with_pane_to_the_left() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -88,7 +88,6 @@ pub fn resize_left_with_pane_to_the_right() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -134,7 +133,6 @@ pub fn resize_left_with_panes_to_the_left_and_right() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -183,7 +181,6 @@ pub fn resize_left_with_multiple_panes_to_the_left() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
use ::insta::assert_snapshot;
|
use ::insta::assert_snapshot;
|
||||||
|
|
||||||
use crate::panes::PositionAndSize;
|
|
||||||
use crate::tests::fakes::FakeInputOutput;
|
use crate::tests::fakes::FakeInputOutput;
|
||||||
|
use crate::tests::start;
|
||||||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
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::{
|
use crate::tests::utils::commands::{
|
||||||
MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_MODE, RESIZE_RIGHT_IN_RESIZE_MODE,
|
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,
|
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 {
|
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
||||||
FakeInputOutput::new(*fake_win_size)
|
FakeInputOutput::new(*fake_win_size)
|
||||||
|
|
@ -44,7 +45,6 @@ pub fn resize_right_with_pane_to_the_left() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -88,7 +88,6 @@ pub fn resize_right_with_pane_to_the_right() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -134,7 +133,6 @@ pub fn resize_right_with_panes_to_the_left_and_right() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -183,7 +181,6 @@ pub fn resize_right_with_multiple_panes_to_the_left() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
use ::insta::assert_snapshot;
|
use ::insta::assert_snapshot;
|
||||||
|
|
||||||
use crate::panes::PositionAndSize;
|
|
||||||
use crate::tests::fakes::FakeInputOutput;
|
use crate::tests::fakes::FakeInputOutput;
|
||||||
|
use crate::tests::start;
|
||||||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
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::{
|
use crate::tests::utils::commands::{
|
||||||
MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE,
|
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,
|
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 {
|
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
||||||
FakeInputOutput::new(*fake_win_size)
|
FakeInputOutput::new(*fake_win_size)
|
||||||
|
|
@ -46,7 +47,6 @@ pub fn resize_up_with_pane_above() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -92,7 +92,6 @@ pub fn resize_up_with_pane_below() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -143,7 +142,6 @@ pub fn resize_up_with_panes_above_and_below() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -193,7 +191,6 @@ pub fn resize_up_with_multiple_panes_above() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -244,7 +241,6 @@ pub fn resize_up_with_panes_above_aligned_left_with_current_pane() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -296,7 +292,6 @@ pub fn resize_up_with_panes_below_aligned_left_with_current_pane() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -346,7 +341,6 @@ pub fn resize_up_with_panes_above_aligned_right_with_current_pane() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -397,7 +391,6 @@ pub fn resize_up_with_panes_below_aligned_right_with_current_pane() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
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(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -698,7 +687,6 @@ pub fn cannot_resize_up_when_pane_above_is_at_minimum_height() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
use insta::assert_snapshot;
|
use insta::assert_snapshot;
|
||||||
|
|
||||||
use crate::tests::fakes::FakeInputOutput;
|
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::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::CliArgs;
|
||||||
use crate::{start, CliArgs};
|
|
||||||
|
|
||||||
use crate::common::input::{config::Config, options::Options};
|
|
||||||
use crate::tests::utils::commands::{
|
use crate::tests::utils::commands::{
|
||||||
CLOSE_TAB_IN_TAB_MODE, NEW_TAB_IN_TAB_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE,
|
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,
|
SWITCH_NEXT_TAB_IN_TAB_MODE, SWITCH_PREV_TAB_IN_TAB_MODE, TAB_MODE,
|
||||||
TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_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 {
|
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
||||||
FakeInputOutput::new(*fake_win_size)
|
FakeInputOutput::new(*fake_win_size)
|
||||||
|
|
@ -38,7 +40,6 @@ pub fn open_new_tab() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -75,7 +76,6 @@ pub fn switch_to_prev_tab() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -112,7 +112,6 @@ pub fn switch_to_next_tab() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -149,7 +148,6 @@ pub fn close_tab() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -187,7 +185,6 @@ pub fn close_last_pane_in_a_tab() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -227,7 +224,6 @@ pub fn close_the_middle_tab() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -272,7 +268,6 @@ pub fn close_the_tab_that_has_a_pane_in_fullscreen() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -309,7 +304,6 @@ pub fn closing_last_tab_exits_the_app() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
use crate::panes::PositionAndSize;
|
|
||||||
use ::insta::assert_snapshot;
|
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::fakes::FakeInputOutput;
|
||||||
|
use crate::tests::start;
|
||||||
use crate::tests::utils::commands::QUIT;
|
use crate::tests::utils::commands::QUIT;
|
||||||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
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 {
|
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
||||||
FakeInputOutput::new(fake_win_size.clone())
|
FakeInputOutput::new(fake_win_size.clone())
|
||||||
|
|
@ -35,7 +36,6 @@ pub fn window_width_decrease_with_one_pane() {
|
||||||
opts,
|
opts,
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -72,7 +72,6 @@ pub fn window_width_increase_with_one_pane() {
|
||||||
opts,
|
opts,
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -109,7 +108,6 @@ pub fn window_height_increase_with_one_pane() {
|
||||||
opts,
|
opts,
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
@ -146,7 +144,6 @@ pub fn window_width_and_height_decrease_with_one_pane() {
|
||||||
opts,
|
opts,
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
use insta::assert_snapshot;
|
use insta::assert_snapshot;
|
||||||
|
|
||||||
use crate::panes::PositionAndSize;
|
|
||||||
use crate::tests::fakes::FakeInputOutput;
|
use crate::tests::fakes::FakeInputOutput;
|
||||||
|
use crate::tests::start;
|
||||||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
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::{
|
use crate::tests::utils::commands::{
|
||||||
MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE,
|
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,
|
TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE,
|
||||||
};
|
};
|
||||||
|
use zellij_utils::input::config::Config;
|
||||||
|
|
||||||
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
||||||
FakeInputOutput::new(*fake_win_size)
|
FakeInputOutput::new(*fake_win_size)
|
||||||
|
|
@ -37,7 +38,6 @@ pub fn adding_new_terminal_in_fullscreen() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
@ -73,7 +73,6 @@ pub fn move_focus_is_disabled_in_fullscreen() {
|
||||||
CliArgs::default(),
|
CliArgs::default(),
|
||||||
Box::new(fake_input_output.clone()),
|
Box::new(fake_input_output.clone()),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
Options::default(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,24 @@ pub mod integration;
|
||||||
pub mod possible_tty_inputs;
|
pub mod possible_tty_inputs;
|
||||||
pub mod tty_inputs;
|
pub mod tty_inputs;
|
||||||
pub mod utils;
|
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();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
use crate::panes::PositionAndSize;
|
use zellij_utils::{vte, zellij_tile};
|
||||||
use crate::panes::TerminalPane;
|
|
||||||
|
use zellij_server::{panes::TerminalPane, tab::Pane};
|
||||||
|
use zellij_tile::data::Palette;
|
||||||
|
use zellij_utils::pane_size::PositionAndSize;
|
||||||
|
|
||||||
pub fn get_output_frame_snapshots(
|
pub fn get_output_frame_snapshots(
|
||||||
output_frames: &[Vec<u8>],
|
output_frames: &[Vec<u8>],
|
||||||
|
|
@ -7,7 +10,7 @@ pub fn get_output_frame_snapshots(
|
||||||
) -> Vec<String> {
|
) -> Vec<String> {
|
||||||
let mut vte_parser = vte::Parser::new();
|
let mut vte_parser = vte::Parser::new();
|
||||||
let main_pid = 0;
|
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![];
|
let mut snapshots = vec![];
|
||||||
for frame in output_frames.iter() {
|
for frame in output_frames.iter() {
|
||||||
|
|
|
||||||
16
zellij-client/Cargo.toml
Normal file
16
zellij-client/Cargo.toml
Normal file
|
|
@ -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"]
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
use std::sync::{Arc, Condvar, Mutex};
|
use std::sync::{Arc, Condvar, Mutex};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CommandIsExecuting {
|
pub(crate) struct CommandIsExecuting {
|
||||||
input_thread: Arc<(Mutex<bool>, Condvar)>,
|
input_thread: Arc<(Mutex<bool>, Condvar)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
//! Main input logic.
|
//! Main input logic.
|
||||||
|
|
||||||
use super::actions::Action;
|
use zellij_utils::{termion, zellij_tile};
|
||||||
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 termion::input::{TermRead, TermReadEventsAndRaw};
|
use crate::{os_input_output::ClientOsApi, ClientInstruction, CommandIsExecuting};
|
||||||
use zellij_tile::data::{InputMode, Key, ModeInfo, Palette, PluginCapabilities};
|
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
|
/// Handles the dispatching of [`Action`]s according to the current
|
||||||
/// [`InputMode`], and keep tracks of the current [`InputMode`].
|
/// [`InputMode`], and keep tracks of the current [`InputMode`].
|
||||||
|
|
@ -133,14 +133,16 @@ impl InputHandler {
|
||||||
let mut should_break = false;
|
let mut should_break = false;
|
||||||
|
|
||||||
match action {
|
match action {
|
||||||
Action::Quit => {
|
Action::Quit | Action::Detach => {
|
||||||
|
self.os_input
|
||||||
|
.send_to_server(ClientToServerMsg::Action(action));
|
||||||
self.exit();
|
self.exit();
|
||||||
should_break = true;
|
should_break = true;
|
||||||
}
|
}
|
||||||
Action::SwitchToMode(mode) => {
|
Action::SwitchToMode(mode) => {
|
||||||
self.mode = mode;
|
self.mode = mode;
|
||||||
self.os_input
|
self.os_input
|
||||||
.send_to_server(ServerInstruction::Action(action));
|
.send_to_server(ClientToServerMsg::Action(action));
|
||||||
}
|
}
|
||||||
Action::CloseFocus
|
Action::CloseFocus
|
||||||
| Action::NewPane(_)
|
| Action::NewPane(_)
|
||||||
|
|
@ -152,13 +154,13 @@ impl InputHandler {
|
||||||
| Action::MoveFocusOrTab(_) => {
|
| Action::MoveFocusOrTab(_) => {
|
||||||
self.command_is_executing.blocking_input_thread();
|
self.command_is_executing.blocking_input_thread();
|
||||||
self.os_input
|
self.os_input
|
||||||
.send_to_server(ServerInstruction::Action(action));
|
.send_to_server(ClientToServerMsg::Action(action));
|
||||||
self.command_is_executing
|
self.command_is_executing
|
||||||
.wait_until_input_thread_is_unblocked();
|
.wait_until_input_thread_is_unblocked();
|
||||||
}
|
}
|
||||||
_ => self
|
_ => self
|
||||||
.os_input
|
.os_input
|
||||||
.send_to_server(ServerInstruction::Action(action)),
|
.send_to_server(ClientToServerMsg::Action(action)),
|
||||||
}
|
}
|
||||||
|
|
||||||
should_break
|
should_break
|
||||||
|
|
@ -168,60 +170,14 @@ impl InputHandler {
|
||||||
/// same as quitting Zellij).
|
/// same as quitting Zellij).
|
||||||
fn exit(&mut self) {
|
fn exit(&mut self) {
|
||||||
self.send_client_instructions
|
self.send_client_instructions
|
||||||
.send(ClientInstruction::Exit)
|
.send(ClientInstruction::Exit(ExitReason::Normal))
|
||||||
.unwrap();
|
.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
|
/// Entry point to the module. Instantiates an [`InputHandler`] and starts
|
||||||
/// its [`InputHandler::handle_input()`] loop.
|
/// its [`InputHandler::handle_input()`] loop.
|
||||||
pub fn input_loop(
|
pub(crate) fn input_loop(
|
||||||
os_input: Box<dyn ClientOsApi>,
|
os_input: Box<dyn ClientOsApi>,
|
||||||
config: Config,
|
config: Config,
|
||||||
command_is_executing: CommandIsExecuting,
|
command_is_executing: CommandIsExecuting,
|
||||||
|
|
@ -237,35 +193,3 @@ pub fn input_loop(
|
||||||
)
|
)
|
||||||
.handle_input();
|
.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!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
298
zellij-client/src/lib.rs
Normal file
298
zellij-client/src/lib.rs
Normal file
|
|
@ -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();
|
||||||
|
}
|
||||||
190
zellij-client/src/os_input_output.rs
Normal file
190
zellij-client/src/os_input_output.rs
Normal file
|
|
@ -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)),
|
||||||
|
})
|
||||||
|
}
|
||||||
25
zellij-server/Cargo.toml
Normal file
25
zellij-server/Cargo.toml
Normal file
|
|
@ -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"]
|
||||||
407
zellij-server/src/lib.rs
Normal file
407
zellij-server/src/lib.rs
Normal file
|
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
311
zellij-server/src/os_input_output.rs
Normal file
311
zellij-server/src/os_input_output.rs
Normal file
|
|
@ -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)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -2,22 +2,43 @@ use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
collections::{BTreeSet, VecDeque},
|
collections::{BTreeSet, VecDeque},
|
||||||
fmt::{self, Debug, Formatter},
|
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 TABSTOP_WIDTH: usize = 8; // TODO: is this always right?
|
||||||
const SCROLL_BACK: usize = 10_000;
|
const SCROLL_BACK: usize = 10_000;
|
||||||
|
|
||||||
use crate::utils::consts::VERSION;
|
use vte::{Params, Perform};
|
||||||
use crate::utils::logging::debug_log_to_file;
|
use zellij_tile::data::{Palette, PaletteColor};
|
||||||
use crate::utils::shared::version_number;
|
use zellij_utils::{consts::VERSION, logging::debug_log_to_file, shared::version_number};
|
||||||
|
|
||||||
use crate::panes::terminal_character::{
|
use crate::panes::terminal_character::{
|
||||||
CharacterStyles, CharsetIndex, Cursor, CursorShape, StandardCharset, TerminalCharacter,
|
CharacterStyles, CharsetIndex, Cursor, CursorShape, StandardCharset, TerminalCharacter,
|
||||||
EMPTY_TERMINAL_CHARACTER,
|
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> {
|
fn get_top_non_canonical_rows(rows: &mut Vec<Row>) -> Vec<Row> {
|
||||||
let mut index_of_last_non_canonical_row = None;
|
let mut index_of_last_non_canonical_row = None;
|
||||||
for (i, row) in rows.iter().enumerate() {
|
for (i, row) in rows.iter().enumerate() {
|
||||||
|
|
@ -180,6 +201,7 @@ pub struct Grid {
|
||||||
scroll_region: Option<(usize, usize)>,
|
scroll_region: Option<(usize, usize)>,
|
||||||
active_charset: CharsetIndex,
|
active_charset: CharsetIndex,
|
||||||
preceding_char: Option<TerminalCharacter>,
|
preceding_char: Option<TerminalCharacter>,
|
||||||
|
colors: Palette,
|
||||||
pub should_render: bool,
|
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. "[D")
|
pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "[D")
|
||||||
pub erasure_mode: bool, // ERM
|
pub erasure_mode: bool, // ERM
|
||||||
|
|
@ -205,7 +227,7 @@ impl Debug for Grid {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Grid {
|
impl Grid {
|
||||||
pub fn new(rows: usize, columns: usize) -> Self {
|
pub fn new(rows: usize, columns: usize, colors: Palette) -> Self {
|
||||||
Grid {
|
Grid {
|
||||||
lines_above: VecDeque::with_capacity(SCROLL_BACK),
|
lines_above: VecDeque::with_capacity(SCROLL_BACK),
|
||||||
viewport: vec![Row::new().canonical()],
|
viewport: vec![Row::new().canonical()],
|
||||||
|
|
@ -226,6 +248,7 @@ impl Grid {
|
||||||
clear_viewport_before_rendering: false,
|
clear_viewport_before_rendering: false,
|
||||||
active_charset: Default::default(),
|
active_charset: Default::default(),
|
||||||
pending_messages_to_pty: vec![],
|
pending_messages_to_pty: vec![],
|
||||||
|
colors,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn contains_widechar(&self) -> bool {
|
pub fn contains_widechar(&self) -> bool {
|
||||||
|
|
@ -1013,8 +1036,139 @@ impl Perform for Grid {
|
||||||
// TBD
|
// TBD
|
||||||
}
|
}
|
||||||
|
|
||||||
fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) {
|
fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
|
||||||
// TBD
|
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 ¶ms[1..] {
|
||||||
|
// currently only getting the color sequence is supported,
|
||||||
|
// setting still isn't
|
||||||
|
if param == b"?" {
|
||||||
|
let color_response_message = match self.colors.bg {
|
||||||
|
PaletteColor::Rgb((r, g, b)) => {
|
||||||
|
format!(
|
||||||
|
"\u{1b}]{};rgb:{1:02x}{1:02x}/{2:02x}{2:02x}/{3:02x}{3:02x}{4}",
|
||||||
|
// dynamic_code, color.r, color.g, color.b, terminator
|
||||||
|
dynamic_code, r, g, b, terminator
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
format!(
|
||||||
|
"\u{1b}]{};rgb:{1:02x}{1:02x}/{2:02x}{2:02x}/{3:02x}{3:02x}{4}",
|
||||||
|
// dynamic_code, color.r, color.g, color.b, terminator
|
||||||
|
dynamic_code, 0, 0, 0, terminator
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.pending_messages_to_pty
|
||||||
|
.push(color_response_message.as_bytes().to_vec());
|
||||||
|
}
|
||||||
|
dynamic_code += 1;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set cursor style.
|
||||||
|
b"50" => {
|
||||||
|
if params.len() >= 2
|
||||||
|
&& params[1].len() >= 13
|
||||||
|
&& params[1][0..12] == *b"CursorShape="
|
||||||
|
{
|
||||||
|
let shape = match params[1][12] as char {
|
||||||
|
'0' => Some(CursorShape::Block),
|
||||||
|
'1' => Some(CursorShape::Beam),
|
||||||
|
'2' => Some(CursorShape::Underline),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(cursor_shape) = shape {
|
||||||
|
self.cursor.change_shape(cursor_shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set clipboard.
|
||||||
|
b"52" => {
|
||||||
|
if params.len() < 3 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _clipboard = params[1].get(0).unwrap_or(&b'c');
|
||||||
|
match params[2] {
|
||||||
|
b"?" => {
|
||||||
|
// TBD: paste from own clipboard - currently unsupported
|
||||||
|
}
|
||||||
|
_base64 => {
|
||||||
|
// TBD: copy to own clipboard - currently unsupported
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset color index.
|
||||||
|
b"104" => {
|
||||||
|
// Reset all color indexes when no parameters are given.
|
||||||
|
if params.len() == 1 {
|
||||||
|
// TBD - reset all color changes - currently unsupported
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset color indexes given as parameters.
|
||||||
|
for param in ¶ms[1..] {
|
||||||
|
if let Some(_index) = parse_number(param) {
|
||||||
|
// TBD - reset color index - currently unimplemented
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset foreground color.
|
||||||
|
b"110" => {
|
||||||
|
// TBD - reset foreground color - currently unimplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset background color.
|
||||||
|
b"111" => {
|
||||||
|
// TBD - reset background color - currently unimplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset text cursor color.
|
||||||
|
b"112" => {
|
||||||
|
// TBD - reset text cursor color - currently unimplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], _ignore: bool, c: char) {
|
fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], _ignore: bool, c: char) {
|
||||||
|
|
@ -1347,7 +1501,7 @@ impl Perform for Grid {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let result = debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params));
|
let result = debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params));
|
||||||
#[cfg(not(test))]
|
#[cfg(not(any(feature = "test", test)))]
|
||||||
result.unwrap();
|
result.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1436,13 +1590,19 @@ impl Debug for Row {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Row {
|
impl Default for Row {
|
||||||
pub fn new() -> Self {
|
fn default() -> Self {
|
||||||
Row {
|
Row {
|
||||||
columns: vec![],
|
columns: vec![],
|
||||||
is_canonical: false,
|
is_canonical: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Row {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
pub fn from_columns(columns: Vec<TerminalCharacter>) -> Self {
|
pub fn from_columns(columns: Vec<TerminalCharacter>) -> Self {
|
||||||
Row {
|
Row {
|
||||||
columns,
|
columns,
|
||||||
|
|
@ -1528,6 +1688,9 @@ impl Row {
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.columns.len()
|
self.columns.len()
|
||||||
}
|
}
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.columns.is_empty()
|
||||||
|
}
|
||||||
pub fn delete_character(&mut self, x: usize) {
|
pub fn delete_character(&mut self, x: usize) {
|
||||||
if x < self.columns.len() {
|
if x < self.columns.len() {
|
||||||
self.columns.remove(x);
|
self.columns.remove(x);
|
||||||
|
|
@ -4,6 +4,6 @@ mod terminal_character;
|
||||||
mod terminal_pane;
|
mod terminal_pane;
|
||||||
|
|
||||||
pub use grid::*;
|
pub use grid::*;
|
||||||
pub use plugin_pane::*;
|
pub(crate) use plugin_pane::*;
|
||||||
pub use terminal_character::*;
|
pub use terminal_character::*;
|
||||||
pub use terminal_pane::*;
|
pub use terminal_pane::*;
|
||||||
|
|
@ -2,13 +2,13 @@ use std::sync::mpsc::channel;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::unimplemented;
|
use std::unimplemented;
|
||||||
|
|
||||||
use crate::common::thread_bus::SenderWithContext;
|
use crate::panes::PaneId;
|
||||||
use crate::panes::{PaneId, PositionAndSize};
|
|
||||||
use crate::pty::VteBytes;
|
use crate::pty::VteBytes;
|
||||||
use crate::tab::Pane;
|
use crate::tab::Pane;
|
||||||
use crate::wasm_vm::PluginInstruction;
|
use crate::wasm_vm::PluginInstruction;
|
||||||
|
use zellij_utils::{channels::SenderWithContext, pane_size::PositionAndSize};
|
||||||
|
|
||||||
pub struct PluginPane {
|
pub(crate) struct PluginPane {
|
||||||
pub pid: u32,
|
pub pid: u32,
|
||||||
pub should_render: bool,
|
pub should_render: bool,
|
||||||
pub selectable: bool,
|
pub selectable: bool,
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use unicode_width::UnicodeWidthChar;
|
use unicode_width::UnicodeWidthChar;
|
||||||
|
|
||||||
use crate::utils::logging::debug_log_to_file;
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt::{self, Debug, Display, Formatter};
|
use std::fmt::{self, Debug, Display, Formatter};
|
||||||
use std::ops::{Index, IndexMut};
|
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 {
|
pub const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter {
|
||||||
character: ' ',
|
character: ' ',
|
||||||
|
|
@ -110,9 +110,9 @@ pub struct CharacterStyles {
|
||||||
pub italic: Option<AnsiCode>,
|
pub italic: Option<AnsiCode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CharacterStyles {
|
impl Default for CharacterStyles {
|
||||||
pub fn new() -> Self {
|
fn default() -> Self {
|
||||||
CharacterStyles {
|
Self {
|
||||||
foreground: None,
|
foreground: None,
|
||||||
background: None,
|
background: None,
|
||||||
strike: None,
|
strike: None,
|
||||||
|
|
@ -126,6 +126,12 @@ impl CharacterStyles {
|
||||||
italic: None,
|
italic: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CharacterStyles {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
pub fn foreground(mut self, foreground_code: Option<AnsiCode>) -> Self {
|
pub fn foreground(mut self, foreground_code: Option<AnsiCode>) -> Self {
|
||||||
self.foreground = foreground_code;
|
self.foreground = foreground_code;
|
||||||
self
|
self
|
||||||
|
|
@ -1,45 +1,26 @@
|
||||||
|
use zellij_utils::{vte, zellij_tile};
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
use zellij_tile::data::Palette;
|
||||||
|
use zellij_utils::pane_size::PositionAndSize;
|
||||||
|
|
||||||
use nix::pty::Winsize;
|
use crate::panes::{
|
||||||
use serde::{Deserialize, Serialize};
|
grid::Grid,
|
||||||
|
terminal_character::{
|
||||||
use crate::panes::grid::Grid;
|
CharacterStyles, CursorShape, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
|
||||||
use crate::panes::terminal_character::{
|
},
|
||||||
CharacterStyles, CursorShape, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
|
|
||||||
};
|
};
|
||||||
use crate::pty::VteBytes;
|
use crate::pty::VteBytes;
|
||||||
use crate::tab::Pane;
|
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 {
|
pub enum PaneId {
|
||||||
Terminal(RawFd),
|
Terminal(RawFd),
|
||||||
Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct?
|
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 struct TerminalPane {
|
||||||
pub grid: Grid,
|
pub grid: Grid,
|
||||||
pub pid: RawFd,
|
pub pid: RawFd,
|
||||||
|
|
@ -49,6 +30,7 @@ pub struct TerminalPane {
|
||||||
pub max_height: Option<usize>,
|
pub max_height: Option<usize>,
|
||||||
pub max_width: Option<usize>,
|
pub max_width: Option<usize>,
|
||||||
pub active_at: Instant,
|
pub active_at: Instant,
|
||||||
|
pub colors: Palette,
|
||||||
vte_parser: vte::Parser,
|
vte_parser: vte::Parser,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -307,8 +289,8 @@ impl Pane for TerminalPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalPane {
|
impl TerminalPane {
|
||||||
pub fn new(pid: RawFd, position_and_size: PositionAndSize) -> TerminalPane {
|
pub fn new(pid: RawFd, position_and_size: PositionAndSize, palette: Palette) -> TerminalPane {
|
||||||
let grid = Grid::new(position_and_size.rows, position_and_size.columns);
|
let grid = Grid::new(position_and_size.rows, position_and_size.columns, palette);
|
||||||
TerminalPane {
|
TerminalPane {
|
||||||
pid,
|
pid,
|
||||||
grid,
|
grid,
|
||||||
|
|
@ -319,6 +301,7 @@ impl TerminalPane {
|
||||||
max_width: None,
|
max_width: None,
|
||||||
vte_parser: vte::Parser::new(),
|
vte_parser: vte::Parser::new(),
|
||||||
active_at: Instant::now(),
|
active_at: Instant::now(),
|
||||||
|
colors: palette,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_x(&self) -> usize {
|
pub fn get_x(&self) -> usize {
|
||||||
|
|
@ -354,7 +337,7 @@ impl TerminalPane {
|
||||||
pub fn read_buffer_as_lines(&self) -> Vec<Vec<TerminalCharacter>> {
|
pub fn read_buffer_as_lines(&self) -> Vec<Vec<TerminalCharacter>> {
|
||||||
self.grid.as_character_lines()
|
self.grid.as_character_lines()
|
||||||
}
|
}
|
||||||
#[cfg(test)]
|
#[cfg(any(feature = "test", test))]
|
||||||
pub fn cursor_coordinates(&self) -> Option<(usize, usize)> {
|
pub fn cursor_coordinates(&self) -> Option<(usize, usize)> {
|
||||||
// (x, y)
|
// (x, y)
|
||||||
self.grid.cursor_coordinates()
|
self.grid.cursor_coordinates()
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
use super::super::Grid;
|
use super::super::Grid;
|
||||||
use ::insta::assert_snapshot;
|
use ::insta::assert_snapshot;
|
||||||
|
use zellij_utils::{vte, zellij_tile::data::Palette};
|
||||||
|
|
||||||
fn read_fixture(fixture_name: &str) -> Vec<u8> {
|
fn read_fixture(fixture_name: &str) -> Vec<u8> {
|
||||||
let mut path_to_file = std::path::PathBuf::new();
|
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("tests");
|
||||||
path_to_file.push("fixtures");
|
path_to_file.push("fixtures");
|
||||||
path_to_file.push(fixture_name);
|
path_to_file.push(fixture_name);
|
||||||
|
|
@ -15,7 +16,7 @@ fn read_fixture(fixture_name: &str) -> Vec<u8> {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest1_0() {
|
fn vttest1_0() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest1-0";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -27,7 +28,7 @@ fn vttest1_0() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest1_1() {
|
fn vttest1_1() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest1-1";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -39,7 +40,7 @@ fn vttest1_1() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest1_2() {
|
fn vttest1_2() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest1-2";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -51,7 +52,7 @@ fn vttest1_2() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest1_3() {
|
fn vttest1_3() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest1-3";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -63,7 +64,7 @@ fn vttest1_3() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest1_4() {
|
fn vttest1_4() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest1-4";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -75,7 +76,7 @@ fn vttest1_4() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest1_5() {
|
fn vttest1_5() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest1-5";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -87,7 +88,7 @@ fn vttest1_5() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest2_0() {
|
fn vttest2_0() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest2-0";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -99,7 +100,7 @@ fn vttest2_0() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest2_1() {
|
fn vttest2_1() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest2-1";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -111,7 +112,7 @@ fn vttest2_1() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest2_2() {
|
fn vttest2_2() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest2-2";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -123,7 +124,7 @@ fn vttest2_2() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest2_3() {
|
fn vttest2_3() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest2-3";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -135,7 +136,7 @@ fn vttest2_3() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest2_4() {
|
fn vttest2_4() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest2-4";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -147,7 +148,7 @@ fn vttest2_4() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest2_5() {
|
fn vttest2_5() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest2-5";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -159,7 +160,7 @@ fn vttest2_5() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest2_6() {
|
fn vttest2_6() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest2-6";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -171,7 +172,7 @@ fn vttest2_6() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest2_7() {
|
fn vttest2_7() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest2-7";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -183,7 +184,7 @@ fn vttest2_7() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest2_8() {
|
fn vttest2_8() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest2-8";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -195,7 +196,7 @@ fn vttest2_8() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest2_9() {
|
fn vttest2_9() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest2-9";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -207,7 +208,7 @@ fn vttest2_9() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest2_10() {
|
fn vttest2_10() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest2-10";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -219,7 +220,7 @@ fn vttest2_10() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest2_11() {
|
fn vttest2_11() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest2-11";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -231,7 +232,7 @@ fn vttest2_11() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest2_12() {
|
fn vttest2_12() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest2-12";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -243,7 +244,7 @@ fn vttest2_12() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest2_13() {
|
fn vttest2_13() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest2-13";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -255,7 +256,7 @@ fn vttest2_13() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest2_14() {
|
fn vttest2_14() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest2-14";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -267,7 +268,7 @@ fn vttest2_14() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest3_0() {
|
fn vttest3_0() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest3-0";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -279,7 +280,7 @@ fn vttest3_0() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest8_0() {
|
fn vttest8_0() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest8-0";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -291,7 +292,7 @@ fn vttest8_0() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest8_1() {
|
fn vttest8_1() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest8-1";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -303,7 +304,7 @@ fn vttest8_1() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest8_2() {
|
fn vttest8_2() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest8-2";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -315,7 +316,7 @@ fn vttest8_2() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest8_3() {
|
fn vttest8_3() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest8-3";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -327,7 +328,7 @@ fn vttest8_3() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest8_4() {
|
fn vttest8_4() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest8-4";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -339,7 +340,7 @@ fn vttest8_4() {
|
||||||
#[test]
|
#[test]
|
||||||
fn vttest8_5() {
|
fn vttest8_5() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "vttest8-5";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -351,7 +352,7 @@ fn vttest8_5() {
|
||||||
#[test]
|
#[test]
|
||||||
fn csi_b() {
|
fn csi_b() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "csi-b";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -363,7 +364,7 @@ fn csi_b() {
|
||||||
#[test]
|
#[test]
|
||||||
fn csi_capital_i() {
|
fn csi_capital_i() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "csi-capital-i";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -375,7 +376,7 @@ fn csi_capital_i() {
|
||||||
#[test]
|
#[test]
|
||||||
fn csi_capital_z() {
|
fn csi_capital_z() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "csi-capital-z";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -387,7 +388,7 @@ fn csi_capital_z() {
|
||||||
#[test]
|
#[test]
|
||||||
fn terminal_reports() {
|
fn terminal_reports() {
|
||||||
let mut vte_parser = vte::Parser::new();
|
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 fixture_name = "terminal_reports";
|
||||||
let content = read_fixture(fixture_name);
|
let content = read_fixture(fixture_name);
|
||||||
for byte in content {
|
for byte in content {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -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)"
|
expression: "format!(\"{:?}\", grid . pending_messages_to_pty)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/grid.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/grid.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/grid.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/grid.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/grid.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/grid.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/client/panes/./unit/grid_tests.rs
|
source: zellij-server/src/panes/./unit/grid_tests.rs
|
||||||
expression: "format!(\"{:?}\", grid)"
|
expression: "format!(\"{:?}\", grid)"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,73 +1,31 @@
|
||||||
use async_std::stream::*;
|
use zellij_utils::async_std;
|
||||||
use async_std::task;
|
|
||||||
use async_std::task::*;
|
use async_std::future::timeout as async_timeout;
|
||||||
|
use async_std::task::{self, JoinHandle};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::pin::*;
|
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use crate::client::panes::PaneId;
|
use crate::{
|
||||||
use crate::common::errors::{get_current_ctx, ContextType, PtyContext};
|
os_input_output::{AsyncReader, Pid, ServerOsApi},
|
||||||
use crate::common::screen::ScreenInstruction;
|
panes::PaneId,
|
||||||
use crate::common::thread_bus::{Bus, ThreadSenders};
|
screen::ScreenInstruction,
|
||||||
use crate::layout::Layout;
|
thread_bus::{Bus, ThreadSenders},
|
||||||
use crate::os_input_output::ServerOsApi;
|
ui::layout::Layout,
|
||||||
use crate::server::ServerInstruction;
|
wasm_vm::PluginInstruction,
|
||||||
use crate::utils::logging::debug_to_file;
|
ServerInstruction,
|
||||||
use crate::wasm_vm::PluginInstruction;
|
};
|
||||||
|
use zellij_utils::{
|
||||||
pub struct ReadFromPid {
|
errors::{get_current_ctx, ContextType, PtyContext},
|
||||||
pid: RawFd,
|
logging::debug_to_file,
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type VteBytes = Vec<u8>;
|
pub type VteBytes = Vec<u8>;
|
||||||
|
|
||||||
/// Instructions related to PTYs (pseudoterminals).
|
/// Instructions related to PTYs (pseudoterminals).
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum PtyInstruction {
|
pub(crate) enum PtyInstruction {
|
||||||
SpawnTerminal(Option<PathBuf>),
|
SpawnTerminal(Option<PathBuf>),
|
||||||
SpawnTerminalVertically(Option<PathBuf>),
|
SpawnTerminalVertically(Option<PathBuf>),
|
||||||
SpawnTerminalHorizontally(Option<PathBuf>),
|
SpawnTerminalHorizontally(Option<PathBuf>),
|
||||||
|
|
@ -77,17 +35,31 @@ pub enum PtyInstruction {
|
||||||
Exit,
|
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 bus: Bus<PtyInstruction>,
|
||||||
pub id_to_child_pid: HashMap<RawFd, RawFd>,
|
pub id_to_child_pid: HashMap<RawFd, Pid>,
|
||||||
debug_to_file: bool,
|
debug_to_file: bool,
|
||||||
task_handles: HashMap<RawFd, JoinHandle<()>>,
|
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 {
|
loop {
|
||||||
let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel");
|
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 {
|
match event {
|
||||||
PtyInstruction::SpawnTerminal(file_to_open) => {
|
PtyInstruction::SpawnTerminal(file_to_open) => {
|
||||||
let pid = pty.spawn_terminal(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(
|
fn stream_terminal_bytes(
|
||||||
pid: RawFd,
|
pid: RawFd,
|
||||||
senders: ThreadSenders,
|
senders: ThreadSenders,
|
||||||
|
|
@ -150,53 +158,43 @@ fn stream_terminal_bytes(
|
||||||
task::spawn({
|
task::spawn({
|
||||||
async move {
|
async move {
|
||||||
err_ctx.add_call(ContextType::AsyncTask);
|
err_ctx.add_call(ContextType::AsyncTask);
|
||||||
let mut terminal_bytes = ReadFromPid::new(&pid, os_input);
|
|
||||||
|
|
||||||
let mut last_byte_receive_time: Option<Instant> = None;
|
// After a successful read, we keep on reading additional data up to a duration of
|
||||||
let mut pending_render = false;
|
// `render_pause`. This is in order to batch up PtyBytes before rendering them.
|
||||||
let max_render_pause = Duration::from_millis(30);
|
// Once `render_deadline` has elapsed, we send Render.
|
||||||
|
let render_pause = Duration::from_millis(30);
|
||||||
|
let mut render_deadline = None;
|
||||||
|
|
||||||
while let Some(bytes) = terminal_bytes.next().await {
|
let mut buf = [0u8; 65536];
|
||||||
let bytes_is_empty = bytes.is_empty();
|
let mut async_reader = os_input.async_file_reader(pid);
|
||||||
if debug {
|
loop {
|
||||||
for byte in bytes.iter() {
|
match deadline_read(async_reader.as_mut(), render_deadline, &mut buf).await {
|
||||||
debug_to_file(*byte, pid).unwrap();
|
ReadResult::Ok(0) | ReadResult::Err(_) => break, // EOF or error
|
||||||
}
|
ReadResult::Timeout => {
|
||||||
}
|
|
||||||
if !bytes_is_empty {
|
|
||||||
let _ = senders.send_to_screen(ScreenInstruction::PtyBytes(pid, bytes));
|
|
||||||
// for UX reasons, if we got something on the wire, we only send the render notice if:
|
|
||||||
// 1. there aren't any more bytes on the wire afterwards
|
|
||||||
// 2. a certain period (currently 30ms) has elapsed since the last render
|
|
||||||
// (otherwise if we get a large amount of data, the display would hang
|
|
||||||
// until it's done)
|
|
||||||
// 3. the stream has ended, and so we render 1 last time
|
|
||||||
match last_byte_receive_time.as_mut() {
|
|
||||||
Some(receive_time) => {
|
|
||||||
if receive_time.elapsed() > max_render_pause {
|
|
||||||
pending_render = false;
|
|
||||||
let _ = senders.send_to_screen(ScreenInstruction::Render);
|
|
||||||
last_byte_receive_time = Some(Instant::now());
|
|
||||||
} else {
|
|
||||||
pending_render = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
last_byte_receive_time = Some(Instant::now());
|
|
||||||
pending_render = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
if pending_render {
|
|
||||||
pending_render = false;
|
|
||||||
let _ = senders.send_to_screen(ScreenInstruction::Render);
|
let _ = senders.send_to_screen(ScreenInstruction::Render);
|
||||||
|
// next read does not need a deadline as we just rendered everything
|
||||||
|
render_deadline = None;
|
||||||
|
|
||||||
|
// yield so Screen thread has some time to render before send additional
|
||||||
|
// PtyBytes.
|
||||||
|
task::sleep(Duration::from_millis(10)).await;
|
||||||
|
}
|
||||||
|
ReadResult::Ok(n_bytes) => {
|
||||||
|
let bytes = &buf[..n_bytes];
|
||||||
|
if debug {
|
||||||
|
let _ = debug_to_file(bytes, pid);
|
||||||
|
}
|
||||||
|
let _ = senders
|
||||||
|
.send_to_screen(ScreenInstruction::PtyBytes(pid, bytes.to_vec()));
|
||||||
|
// if we already have a render_deadline we keep it, otherwise we set it
|
||||||
|
// to the duration of `render_pause`.
|
||||||
|
render_deadline.get_or_insert(Instant::now() + render_pause);
|
||||||
}
|
}
|
||||||
last_byte_receive_time = None;
|
|
||||||
task::sleep(::std::time::Duration::from_millis(10)).await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
senders.send_to_screen(ScreenInstruction::Render).unwrap();
|
let _ = senders.send_to_screen(ScreenInstruction::Render);
|
||||||
#[cfg(not(test))]
|
|
||||||
|
#[cfg(not(any(feature = "test", test)))]
|
||||||
// this is a little hacky, and is because the tests end the file as soon as
|
// 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
|
// 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
|
// 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 {
|
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
|
.bus
|
||||||
.os_input
|
.os_input
|
||||||
.as_mut()
|
.as_mut()
|
||||||
|
|
@ -237,7 +235,7 @@ impl Pty {
|
||||||
let total_panes = layout.total_terminal_panes();
|
let total_panes = layout.total_terminal_panes();
|
||||||
let mut new_pane_pids = vec![];
|
let mut new_pane_pids = vec![];
|
||||||
for _ in 0..total_panes {
|
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.bus.os_input.as_mut().unwrap().spawn_terminal(None);
|
||||||
self.id_to_child_pid.insert(pid_primary, pid_secondary);
|
self.id_to_child_pid.insert(pid_primary, pid_secondary);
|
||||||
new_pane_pids.push(pid_primary);
|
new_pane_pids.push(pid_primary);
|
||||||
|
|
@ -1,23 +1,27 @@
|
||||||
use std::sync::{Arc, RwLock};
|
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::{
|
||||||
use crate::common::input::actions::{Action, Direction};
|
os_input_output::ServerOsApi, pty::PtyInstruction, screen::ScreenInstruction,
|
||||||
use crate::common::input::handler::get_mode_info;
|
wasm_vm::PluginInstruction, ServerInstruction, SessionMetaData, SessionState,
|
||||||
use crate::common::os_input_output::ServerOsApi;
|
};
|
||||||
use crate::common::pty::PtyInstruction;
|
use zellij_utils::{
|
||||||
use crate::common::screen::ScreenInstruction;
|
channels::SenderWithContext,
|
||||||
use crate::common::thread_bus::SenderWithContext;
|
input::{
|
||||||
use crate::common::wasm_vm::PluginInstruction;
|
actions::{Action, Direction},
|
||||||
use crate::server::{ServerInstruction, SessionMetaData};
|
get_mode_info,
|
||||||
|
},
|
||||||
|
ipc::{ClientToServerMsg, ExitReason, ServerToClientMsg},
|
||||||
|
};
|
||||||
|
|
||||||
fn route_action(
|
fn route_action(
|
||||||
action: Action,
|
action: Action,
|
||||||
session: &SessionMetaData,
|
session: &SessionMetaData,
|
||||||
os_input: &dyn ServerOsApi,
|
os_input: &dyn ServerOsApi,
|
||||||
capabilities: PluginCapabilities,
|
to_server: &SenderWithContext<ServerInstruction>,
|
||||||
) {
|
) -> bool {
|
||||||
|
let mut should_break = false;
|
||||||
match action {
|
match action {
|
||||||
Action::Write(val) => {
|
Action::Write(val) => {
|
||||||
session
|
session
|
||||||
|
|
@ -31,11 +35,14 @@ fn route_action(
|
||||||
}
|
}
|
||||||
Action::SwitchToMode(mode) => {
|
Action::SwitchToMode(mode) => {
|
||||||
let palette = os_input.load_palette();
|
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
|
session
|
||||||
.senders
|
.senders
|
||||||
.send_to_plugin(PluginInstruction::Update(
|
.send_to_plugin(PluginInstruction::Update(
|
||||||
None,
|
None,
|
||||||
Event::ModeUpdate(get_mode_info(mode, palette, capabilities)),
|
Event::ModeUpdate(get_mode_info(mode, palette, session.capabilities)),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
session
|
session
|
||||||
|
|
@ -43,7 +50,7 @@ fn route_action(
|
||||||
.send_to_screen(ScreenInstruction::ChangeMode(get_mode_info(
|
.send_to_screen(ScreenInstruction::ChangeMode(get_mode_info(
|
||||||
mode,
|
mode,
|
||||||
palette,
|
palette,
|
||||||
capabilities,
|
session.capabilities,
|
||||||
)))
|
)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
session
|
session
|
||||||
|
|
@ -181,35 +188,39 @@ fn route_action(
|
||||||
.send_to_screen(ScreenInstruction::UpdateTabName(c))
|
.send_to_screen(ScreenInstruction::UpdateTabName(c))
|
||||||
.unwrap();
|
.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::NoOp => {}
|
||||||
Action::Quit => panic!("Received unexpected action"),
|
|
||||||
}
|
}
|
||||||
|
should_break
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn route_thread_main(
|
pub(crate) fn route_thread_main(
|
||||||
sessions: Arc<RwLock<Option<SessionMetaData>>>,
|
session_data: Arc<RwLock<Option<SessionMetaData>>>,
|
||||||
mut os_input: Box<dyn ServerOsApi>,
|
session_state: Arc<RwLock<SessionState>>,
|
||||||
|
os_input: Box<dyn ServerOsApi>,
|
||||||
to_server: SenderWithContext<ServerInstruction>,
|
to_server: SenderWithContext<ServerInstruction>,
|
||||||
capabilities: PluginCapabilities,
|
|
||||||
) {
|
) {
|
||||||
loop {
|
loop {
|
||||||
let (instruction, mut err_ctx) = os_input.recv_from_client();
|
let (instruction, err_ctx) = os_input.recv_from_client();
|
||||||
err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction)));
|
err_ctx.update_thread_ctx();
|
||||||
let rlocked_sessions = sessions.read().unwrap();
|
let rlocked_sessions = session_data.read().unwrap();
|
||||||
|
|
||||||
match instruction {
|
match instruction {
|
||||||
ServerInstruction::ClientExit => {
|
ClientToServerMsg::Action(action) => {
|
||||||
to_server.send(instruction).unwrap();
|
if let Some(rlocked_sessions) = rlocked_sessions.as_ref() {
|
||||||
break;
|
if route_action(action, rlocked_sessions, &*os_input, &to_server) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ServerInstruction::Action(action) => {
|
ClientToServerMsg::TerminalResize(new_size) => {
|
||||||
route_action(
|
|
||||||
action,
|
|
||||||
rlocked_sessions.as_ref().unwrap(),
|
|
||||||
&*os_input,
|
|
||||||
capabilities,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ServerInstruction::TerminalResize(new_size) => {
|
|
||||||
rlocked_sessions
|
rlocked_sessions
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -217,13 +228,25 @@ pub fn route_thread_main(
|
||||||
.send_to_screen(ScreenInstruction::TerminalResize(new_size))
|
.send_to_screen(ScreenInstruction::TerminalResize(new_size))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
ServerInstruction::NewClient(..) => {
|
ClientToServerMsg::NewClient(..) => {
|
||||||
os_input.add_client_sender();
|
if *session_state.read().unwrap() != SessionState::Uninitialized {
|
||||||
to_server.send(instruction).unwrap();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
ClientToServerMsg::AttachClient(_, force) => {
|
||||||
to_server.send(instruction).unwrap();
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,23 +3,30 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use crate::common::input::options::Options;
|
use zellij_utils::zellij_tile;
|
||||||
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 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_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`].
|
/// Instructions that can be sent to the [`Screen`].
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ScreenInstruction {
|
pub(crate) enum ScreenInstruction {
|
||||||
PtyBytes(RawFd, VteBytes),
|
PtyBytes(RawFd, VteBytes),
|
||||||
Render,
|
Render,
|
||||||
NewPane(PaneId),
|
NewPane(PaneId),
|
||||||
|
|
@ -63,9 +70,61 @@ pub enum ScreenInstruction {
|
||||||
ChangeMode(ModeInfo),
|
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).
|
/// 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`.
|
/// 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.
|
/// A Bus for sending and receiving messages with the other threads.
|
||||||
pub bus: Bus<ScreenInstruction>,
|
pub bus: Bus<ScreenInstruction>,
|
||||||
/// An optional maximal amount of panes allowed per [`Tab`] in this [`Screen`] instance.
|
/// 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.
|
/// A map between this [`Screen`]'s tabs and their ID/key.
|
||||||
tabs: BTreeMap<usize, Tab>,
|
tabs: BTreeMap<usize, Tab>,
|
||||||
/// The full size of this [`Screen`].
|
/// The full size of this [`Screen`].
|
||||||
full_screen_ws: PositionAndSize,
|
position_and_size: PositionAndSize,
|
||||||
/// The index of this [`Screen`]'s active [`Tab`].
|
/// The index of this [`Screen`]'s active [`Tab`].
|
||||||
active_tab_index: Option<usize>,
|
active_tab_index: Option<usize>,
|
||||||
mode_info: ModeInfo,
|
mode_info: ModeInfo,
|
||||||
input_mode: InputMode,
|
input_mode: InputMode,
|
||||||
colors: Palette,
|
colors: Palette,
|
||||||
|
session_state: Arc<RwLock<SessionState>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Screen {
|
impl Screen {
|
||||||
/// Creates and returns a new [`Screen`].
|
/// Creates and returns a new [`Screen`].
|
||||||
pub fn new(
|
pub fn new(
|
||||||
bus: Bus<ScreenInstruction>,
|
bus: Bus<ScreenInstruction>,
|
||||||
full_screen_ws: &PositionAndSize,
|
client_attributes: &ClientAttributes,
|
||||||
max_panes: Option<usize>,
|
max_panes: Option<usize>,
|
||||||
mode_info: ModeInfo,
|
mode_info: ModeInfo,
|
||||||
input_mode: InputMode,
|
input_mode: InputMode,
|
||||||
colors: Palette,
|
session_state: Arc<RwLock<SessionState>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Screen {
|
Screen {
|
||||||
bus,
|
bus,
|
||||||
max_panes,
|
max_panes,
|
||||||
full_screen_ws: *full_screen_ws,
|
position_and_size: client_attributes.position_and_size,
|
||||||
|
colors: client_attributes.palette,
|
||||||
active_tab_index: None,
|
active_tab_index: None,
|
||||||
tabs: BTreeMap::new(),
|
tabs: BTreeMap::new(),
|
||||||
mode_info,
|
mode_info,
|
||||||
input_mode,
|
input_mode,
|
||||||
colors,
|
session_state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,7 +173,7 @@ impl Screen {
|
||||||
tab_index,
|
tab_index,
|
||||||
position,
|
position,
|
||||||
String::new(),
|
String::new(),
|
||||||
&self.full_screen_ws,
|
&self.position_and_size,
|
||||||
self.bus.os_input.as_ref().unwrap().clone(),
|
self.bus.os_input.as_ref().unwrap().clone(),
|
||||||
self.bus.senders.clone(),
|
self.bus.senders.clone(),
|
||||||
self.max_panes,
|
self.max_panes,
|
||||||
|
|
@ -120,6 +181,7 @@ impl Screen {
|
||||||
self.mode_info.clone(),
|
self.mode_info.clone(),
|
||||||
self.input_mode,
|
self.input_mode,
|
||||||
self.colors,
|
self.colors,
|
||||||
|
self.session_state.clone(),
|
||||||
);
|
);
|
||||||
self.active_tab_index = Some(tab_index);
|
self.active_tab_index = Some(tab_index);
|
||||||
self.tabs.insert(tab_index, tab);
|
self.tabs.insert(tab_index, tab);
|
||||||
|
|
@ -204,10 +266,12 @@ impl Screen {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if self.tabs.is_empty() {
|
if self.tabs.is_empty() {
|
||||||
self.active_tab_index = None;
|
self.active_tab_index = None;
|
||||||
self.bus
|
if *self.session_state.read().unwrap() == SessionState::Attached {
|
||||||
.senders
|
self.bus
|
||||||
.send_to_server(ServerInstruction::Render(None))
|
.senders
|
||||||
.unwrap();
|
.send_to_server(ServerInstruction::Render(None))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
for t in self.tabs.values_mut() {
|
for t in self.tabs.values_mut() {
|
||||||
if t.position > active_tab.position {
|
if t.position > active_tab.position {
|
||||||
|
|
@ -219,7 +283,7 @@ impl Screen {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize_to_screen(&mut self, new_screen_size: PositionAndSize) {
|
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() {
|
for (_, tab) in self.tabs.iter_mut() {
|
||||||
tab.resize_whole_tab(new_screen_size);
|
tab.resize_whole_tab(new_screen_size);
|
||||||
}
|
}
|
||||||
|
|
@ -229,6 +293,9 @@ impl Screen {
|
||||||
|
|
||||||
/// Renders this [`Screen`], which amounts to rendering its active [`Tab`].
|
/// Renders this [`Screen`], which amounts to rendering its active [`Tab`].
|
||||||
pub fn render(&mut self) {
|
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 let Some(active_tab) = self.get_active_tab_mut() {
|
||||||
if active_tab.get_active_pane().is_some() {
|
if active_tab.get_active_pane().is_some() {
|
||||||
active_tab.render();
|
active_tab.render();
|
||||||
|
|
@ -268,7 +335,7 @@ impl Screen {
|
||||||
tab_index,
|
tab_index,
|
||||||
position,
|
position,
|
||||||
String::new(),
|
String::new(),
|
||||||
&self.full_screen_ws,
|
&self.position_and_size,
|
||||||
self.bus.os_input.as_ref().unwrap().clone(),
|
self.bus.os_input.as_ref().unwrap().clone(),
|
||||||
self.bus.senders.clone(),
|
self.bus.senders.clone(),
|
||||||
self.max_panes,
|
self.max_panes,
|
||||||
|
|
@ -276,6 +343,7 @@ impl Screen {
|
||||||
self.mode_info.clone(),
|
self.mode_info.clone(),
|
||||||
self.input_mode,
|
self.input_mode,
|
||||||
self.colors,
|
self.colors,
|
||||||
|
self.session_state.clone(),
|
||||||
);
|
);
|
||||||
tab.apply_layout(layout, new_pids);
|
tab.apply_layout(layout, new_pids);
|
||||||
self.active_tab_index = Some(tab_index);
|
self.active_tab_index = Some(tab_index);
|
||||||
|
|
@ -318,6 +386,7 @@ impl Screen {
|
||||||
self.update_tabs();
|
self.update_tabs();
|
||||||
}
|
}
|
||||||
pub fn change_mode(&mut self, mode_info: ModeInfo) {
|
pub fn change_mode(&mut self, mode_info: ModeInfo) {
|
||||||
|
self.colors = mode_info.palette;
|
||||||
self.mode_info = mode_info;
|
self.mode_info = mode_info;
|
||||||
for tab in self.tabs.values_mut() {
|
for tab in self.tabs.values_mut() {
|
||||||
tab.mode_info = self.mode_info.clone();
|
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>,
|
bus: Bus<ScreenInstruction>,
|
||||||
max_panes: Option<usize>,
|
max_panes: Option<usize>,
|
||||||
full_screen_ws: PositionAndSize,
|
client_attributes: ClientAttributes,
|
||||||
config_options: Options,
|
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 capabilities = config_options.simplified_ui;
|
||||||
let default_mode = config_options.default_mode.unwrap_or_default();
|
let default_mode = config_options.default_mode.unwrap_or_default();
|
||||||
|
|
||||||
let mut screen = Screen::new(
|
let mut screen = Screen::new(
|
||||||
bus,
|
bus,
|
||||||
&full_screen_ws,
|
&client_attributes,
|
||||||
max_panes,
|
max_panes,
|
||||||
ModeInfo {
|
ModeInfo {
|
||||||
palette: colors,
|
palette: client_attributes.palette,
|
||||||
capabilities: PluginCapabilities {
|
capabilities: PluginCapabilities {
|
||||||
arrow_fonts: capabilities,
|
arrow_fonts: capabilities,
|
||||||
},
|
},
|
||||||
|
|
@ -348,14 +420,14 @@ pub fn screen_thread_main(
|
||||||
..ModeInfo::default()
|
..ModeInfo::default()
|
||||||
},
|
},
|
||||||
default_mode,
|
default_mode,
|
||||||
colors,
|
session_state,
|
||||||
);
|
);
|
||||||
loop {
|
loop {
|
||||||
let (event, mut err_ctx) = screen
|
let (event, mut err_ctx) = screen
|
||||||
.bus
|
.bus
|
||||||
.recv()
|
.recv()
|
||||||
.expect("failed to receive event on channel");
|
.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 {
|
match event {
|
||||||
ScreenInstruction::PtyBytes(pid, vte_bytes) => {
|
ScreenInstruction::PtyBytes(pid, vte_bytes) => {
|
||||||
let active_tab = screen.get_active_tab_mut().unwrap();
|
let active_tab = screen.get_active_tab_mut().unwrap();
|
||||||
|
|
@ -1,26 +1,27 @@
|
||||||
//! `Tab`s holds multiple panes. It tracks their coordinates (x/y) and size,
|
//! `Tab`s holds multiple panes. It tracks their coordinates (x/y) and size,
|
||||||
//! as well as how they should be resized
|
//! as well as how they should be resized
|
||||||
|
|
||||||
use crate::client::pane_resizer::PaneResizer;
|
use zellij_utils::{serde, zellij_tile};
|
||||||
use crate::common::input::handler::parse_keys;
|
|
||||||
use crate::common::thread_bus::ThreadSenders;
|
use crate::{
|
||||||
use crate::layout::Layout;
|
os_input_output::ServerOsApi,
|
||||||
use crate::os_input_output::ServerOsApi;
|
panes::{PaneId, PluginPane, TerminalPane},
|
||||||
use crate::panes::{PaneId, PositionAndSize, TerminalPane};
|
pty::{PtyInstruction, VteBytes},
|
||||||
use crate::pty::{PtyInstruction, VteBytes};
|
thread_bus::ThreadSenders,
|
||||||
use crate::server::ServerInstruction;
|
ui::{boundaries::Boundaries, layout::Layout, pane_resizer::PaneResizer},
|
||||||
use crate::utils::shared::adjust_to_size;
|
wasm_vm::PluginInstruction,
|
||||||
use crate::wasm_vm::PluginInstruction;
|
ServerInstruction, SessionState,
|
||||||
use crate::{boundaries::Boundaries, panes::PluginPane};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use std::sync::mpsc::channel;
|
use std::sync::{mpsc::channel, Arc, RwLock};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Reverse,
|
cmp::Reverse,
|
||||||
collections::{BTreeMap, HashSet},
|
collections::{BTreeMap, HashSet},
|
||||||
};
|
};
|
||||||
use zellij_tile::data::{Event, InputMode, ModeInfo, Palette};
|
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
|
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)
|
(first_rect, second_rect)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Tab {
|
pub(crate) struct Tab {
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
pub position: usize,
|
pub position: usize,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
@ -73,13 +74,15 @@ pub struct Tab {
|
||||||
pub senders: ThreadSenders,
|
pub senders: ThreadSenders,
|
||||||
synchronize_is_active: bool,
|
synchronize_is_active: bool,
|
||||||
should_clear_display_before_rendering: bool,
|
should_clear_display_before_rendering: bool,
|
||||||
|
session_state: Arc<RwLock<SessionState>>,
|
||||||
pub mode_info: ModeInfo,
|
pub mode_info: ModeInfo,
|
||||||
pub input_mode: InputMode,
|
pub input_mode: InputMode,
|
||||||
pub colors: Palette,
|
pub colors: Palette,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||||
pub struct TabData {
|
#[serde(crate = "self::serde")]
|
||||||
|
pub(crate) struct TabData {
|
||||||
/* subset of fields to publish to plugins */
|
/* subset of fields to publish to plugins */
|
||||||
pub position: usize,
|
pub position: usize,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
@ -233,16 +236,17 @@ impl Tab {
|
||||||
position: usize,
|
position: usize,
|
||||||
name: String,
|
name: String,
|
||||||
full_screen_ws: &PositionAndSize,
|
full_screen_ws: &PositionAndSize,
|
||||||
mut os_api: Box<dyn ServerOsApi>,
|
os_api: Box<dyn ServerOsApi>,
|
||||||
senders: ThreadSenders,
|
senders: ThreadSenders,
|
||||||
max_panes: Option<usize>,
|
max_panes: Option<usize>,
|
||||||
pane_id: Option<PaneId>,
|
pane_id: Option<PaneId>,
|
||||||
mode_info: ModeInfo,
|
mode_info: ModeInfo,
|
||||||
input_mode: InputMode,
|
input_mode: InputMode,
|
||||||
colors: Palette,
|
colors: Palette,
|
||||||
|
session_state: Arc<RwLock<SessionState>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let panes = if let Some(PaneId::Terminal(pid)) = pane_id {
|
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(
|
os_api.set_terminal_size_using_fd(
|
||||||
new_terminal.pid,
|
new_terminal.pid,
|
||||||
new_terminal.columns() as u16,
|
new_terminal.columns() as u16,
|
||||||
|
|
@ -271,6 +275,7 @@ impl Tab {
|
||||||
mode_info,
|
mode_info,
|
||||||
input_mode,
|
input_mode,
|
||||||
colors,
|
colors,
|
||||||
|
session_state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -344,7 +349,7 @@ impl Tab {
|
||||||
} else {
|
} else {
|
||||||
// there are still panes left to fill, use the pids we received in this method
|
// 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 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(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
new_terminal.pid,
|
new_terminal.pid,
|
||||||
new_terminal.columns() as u16,
|
new_terminal.columns() as u16,
|
||||||
|
|
@ -372,7 +377,7 @@ impl Tab {
|
||||||
}
|
}
|
||||||
if !self.has_panes() {
|
if !self.has_panes() {
|
||||||
if let PaneId::Terminal(term_pid) = pid {
|
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(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
new_terminal.pid,
|
new_terminal.pid,
|
||||||
new_terminal.columns() as u16,
|
new_terminal.columns() as u16,
|
||||||
|
|
@ -422,7 +427,7 @@ impl Tab {
|
||||||
{
|
{
|
||||||
if let PaneId::Terminal(term_pid) = pid {
|
if let PaneId::Terminal(term_pid) = pid {
|
||||||
let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&terminal_ws);
|
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(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
new_terminal.pid,
|
new_terminal.pid,
|
||||||
bottom_winsize.columns as u16,
|
bottom_winsize.columns as u16,
|
||||||
|
|
@ -442,7 +447,7 @@ impl Tab {
|
||||||
} else if terminal_to_split.columns() > terminal_to_split.min_width() * 2 {
|
} else if terminal_to_split.columns() > terminal_to_split.min_width() * 2 {
|
||||||
if let PaneId::Terminal(term_pid) = pid {
|
if let PaneId::Terminal(term_pid) = pid {
|
||||||
let (left_winsize, right_winsize) = split_vertically_with_gap(&terminal_ws);
|
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(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
new_terminal.pid,
|
new_terminal.pid,
|
||||||
right_winsize.columns as u16,
|
right_winsize.columns as u16,
|
||||||
|
|
@ -470,7 +475,7 @@ impl Tab {
|
||||||
}
|
}
|
||||||
if !self.has_panes() {
|
if !self.has_panes() {
|
||||||
if let PaneId::Terminal(term_pid) = pid {
|
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(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
new_terminal.pid,
|
new_terminal.pid,
|
||||||
new_terminal.columns() as u16,
|
new_terminal.columns() as u16,
|
||||||
|
|
@ -500,7 +505,7 @@ impl Tab {
|
||||||
|
|
||||||
active_pane.change_pos_and_size(&top_winsize);
|
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(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
new_terminal.pid,
|
new_terminal.pid,
|
||||||
bottom_winsize.columns as u16,
|
bottom_winsize.columns as u16,
|
||||||
|
|
@ -527,7 +532,7 @@ impl Tab {
|
||||||
}
|
}
|
||||||
if !self.has_panes() {
|
if !self.has_panes() {
|
||||||
if let PaneId::Terminal(term_pid) = pid {
|
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(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
new_terminal.pid,
|
new_terminal.pid,
|
||||||
new_terminal.columns() as u16,
|
new_terminal.columns() as u16,
|
||||||
|
|
@ -557,7 +562,7 @@ impl Tab {
|
||||||
|
|
||||||
active_pane.change_pos_and_size(&left_winsize);
|
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(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
new_terminal.pid,
|
new_terminal.pid,
|
||||||
right_winsize.columns as u16,
|
right_winsize.columns as u16,
|
||||||
|
|
@ -625,9 +630,9 @@ impl Tab {
|
||||||
match pane_id {
|
match pane_id {
|
||||||
PaneId::Terminal(active_terminal_id) => {
|
PaneId::Terminal(active_terminal_id) => {
|
||||||
let active_terminal = self.panes.get(&pane_id).unwrap();
|
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
|
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");
|
.expect("failed to write to terminal");
|
||||||
self.os_api
|
self.os_api
|
||||||
.tcdrain(active_terminal_id)
|
.tcdrain(active_terminal_id)
|
||||||
|
|
@ -720,9 +725,12 @@ impl Tab {
|
||||||
self.panes.iter().any(|(_, p)| p.contains_widechar())
|
self.panes.iter().any(|(_, p)| p.contains_widechar())
|
||||||
}
|
}
|
||||||
pub fn render(&mut self) {
|
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
|
// 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
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
// if any pane contain widechar, all pane in the same row will messup. We should render them every time
|
// if any pane contain widechar, all pane in the same row will messup. We should render them every time
|
||||||
80
zellij-server/src/thread_bus.rs
Normal file
80
zellij-server/src/thread_bus.rs
Normal file
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
|
use zellij_utils::zellij_tile;
|
||||||
|
|
||||||
use crate::tab::Pane;
|
use crate::tab::Pane;
|
||||||
use crate::utils::shared::colors;
|
|
||||||
use ansi_term::Colour::{Fixed, RGB};
|
use ansi_term::Colour::{Fixed, RGB};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use zellij_tile::data::{InputMode, Palette, PaletteColor};
|
use zellij_tile::data::{InputMode, Palette, PaletteColor};
|
||||||
|
use zellij_utils::shared::colors;
|
||||||
|
|
||||||
use std::fmt::{Display, Error, Formatter};
|
use std::fmt::{Display, Error, Formatter};
|
||||||
pub mod boundary_type {
|
pub mod boundary_type {
|
||||||
|
|
@ -19,10 +21,10 @@ pub mod boundary_type {
|
||||||
pub const CROSS: &str = "┼";
|
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)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct BoundarySymbol {
|
pub(crate) struct BoundarySymbol {
|
||||||
boundary_type: BoundaryType,
|
boundary_type: BoundaryType,
|
||||||
invisible: bool,
|
invisible: bool,
|
||||||
color: Option<PaletteColor>,
|
color: Option<PaletteColor>,
|
||||||
|
|
@ -392,7 +394,7 @@ fn combine_symbols(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Debug)]
|
#[derive(PartialEq, Eq, Hash, Debug)]
|
||||||
pub struct Coordinates {
|
pub(crate) struct Coordinates {
|
||||||
x: usize,
|
x: usize,
|
||||||
y: usize,
|
y: usize,
|
||||||
}
|
}
|
||||||
|
|
@ -403,7 +405,7 @@ impl Coordinates {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Rect {
|
pub(crate) trait Rect {
|
||||||
fn x(&self) -> usize;
|
fn x(&self) -> usize;
|
||||||
fn y(&self) -> usize;
|
fn y(&self) -> usize;
|
||||||
fn rows(&self) -> usize;
|
fn rows(&self) -> usize;
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
|
use zellij_utils::{serde, serde_yaml};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::{fs::File, io::prelude::*};
|
use std::{fs::File, io::prelude::*};
|
||||||
|
|
||||||
use crate::panes::PositionAndSize;
|
use zellij_utils::pane_size::PositionAndSize;
|
||||||
|
|
||||||
fn split_space_to_parts_vertically(
|
fn split_space_to_parts_vertically(
|
||||||
space_to_split: &PositionAndSize,
|
space_to_split: &PositionAndSize,
|
||||||
|
|
@ -167,19 +169,22 @@ fn split_space(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub enum Direction {
|
#[serde(crate = "self::serde")]
|
||||||
|
pub(crate) enum Direction {
|
||||||
Horizontal,
|
Horizontal,
|
||||||
Vertical,
|
Vertical,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
||||||
pub enum SplitSize {
|
#[serde(crate = "self::serde")]
|
||||||
|
pub(crate) enum SplitSize {
|
||||||
Percent(u8), // 1 to 100
|
Percent(u8), // 1 to 100
|
||||||
Fixed(u16), // An absolute number of columns or rows
|
Fixed(u16), // An absolute number of columns or rows
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Layout {
|
#[serde(crate = "self::serde")]
|
||||||
|
pub(crate) struct Layout {
|
||||||
pub direction: Direction,
|
pub direction: Direction,
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
pub parts: Vec<Layout>,
|
pub parts: Vec<Layout>,
|
||||||
|
|
@ -190,10 +195,9 @@ pub struct Layout {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout {
|
impl Layout {
|
||||||
pub fn new(layout_path: &Path, data_dir: &Path) -> Self {
|
pub fn new(layout_path: &Path) -> Self {
|
||||||
let layout_dir = data_dir.join("layouts/");
|
|
||||||
let mut layout_file = File::open(&layout_path)
|
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()));
|
.unwrap_or_else(|_| panic!("cannot find layout {}", &layout_path.display()));
|
||||||
|
|
||||||
let mut layout = String::new();
|
let mut layout = String::new();
|
||||||
|
|
@ -207,14 +211,8 @@ impl Layout {
|
||||||
|
|
||||||
// It wants to use Path here, but that doesn't compile.
|
// It wants to use Path here, but that doesn't compile.
|
||||||
#[allow(clippy::ptr_arg)]
|
#[allow(clippy::ptr_arg)]
|
||||||
pub fn from_defaults(layout_path: &PathBuf, data_dir: &Path) -> Self {
|
pub fn from_dir(layout: &PathBuf, data_dir: &Path) -> Self {
|
||||||
Self::new(
|
Self::new(&data_dir.join("layouts/").join(layout))
|
||||||
&data_dir
|
|
||||||
.join("layouts/")
|
|
||||||
.join(layout_path)
|
|
||||||
.with_extension("yaml"),
|
|
||||||
&data_dir,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn total_terminal_panes(&self) -> usize {
|
pub fn total_terminal_panes(&self) -> usize {
|
||||||
3
zellij-server/src/ui/mod.rs
Normal file
3
zellij-server/src/ui/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod boundaries;
|
||||||
|
pub mod layout;
|
||||||
|
pub mod pane_resizer;
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
use crate::os_input_output::ServerOsApi;
|
use crate::{os_input_output::ServerOsApi, panes::PaneId, tab::Pane};
|
||||||
use crate::panes::{PaneId, PositionAndSize};
|
|
||||||
use crate::tab::Pane;
|
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
collections::{BTreeMap, HashSet},
|
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>>,
|
panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>,
|
||||||
os_api: &'a mut Box<dyn ServerOsApi>,
|
os_api: &'a mut Box<dyn ServerOsApi>,
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue