Merge branch 'main' of https://github.com/zellij-org/zellij into set-data-dir
This commit is contained in:
commit
4134c952f6
96 changed files with 3608 additions and 1538 deletions
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -3,27 +3,27 @@ name: "\U0001F41B Bug Report"
|
|||
about: "If something isn't working as expected."
|
||||
labels: bug
|
||||
---
|
||||
Thank you for taking the time to file an issue!
|
||||
You can erase any parts of this template not applicable to your issue.
|
||||
Thank you for taking the time to file this issue! Please follow the instructions and fill the missing parts below the instructions, if it is meaningful. Try to be brief and concise.
|
||||
|
||||
## In Case of Graphical, or Performance Issues
|
||||
**In Case of Graphical or Performance Issues**
|
||||
|
||||
Please:
|
||||
1. Delete the contents of `/tmp/zellij/zellij-log`.
|
||||
2. Run `zellij --debug` and then recreate your issue.
|
||||
1. Delete the contents of `/tmp/zellij-1000/zellij-log`, ie with `cd /tmp/zellij-1000/` and `rm -fr zellij-log/`
|
||||
2. Run `zellij --debug`
|
||||
3. Recreate your issue.
|
||||
3. Quit Zellij immediately with ctrl-q (your bug should ideally still be visible on screen)
|
||||
|
||||
Please attach the files that were created in
|
||||
Please attach the files that were created in `/tmp/zellij-1000/zellij-log/` to the extent you are comfortable with.
|
||||
|
||||
`/tmp/zellij/zellij-log/`
|
||||
|
||||
To the extent you are comfortable with.
|
||||
|
||||
Also please add the size in columns/lines of the terminal in which the bug happened. You can usually find these out with `tput lines` and `tput cols`.
|
||||
|
||||
And the name and version of progams you interacted with as well as
|
||||
the operating system.
|
||||
|
||||
## Information
|
||||
**Basic information**
|
||||
|
||||
`zellij --version`:
|
||||
`tput lines`:
|
||||
`tput cols`:
|
||||
`uname -av` or `ver`(Windows):
|
||||
|
||||
List of programs you interact with as, `PROGRAM --version`: output cropped meaningful, for example:
|
||||
`nvim --version`: NVIM v0.5.0-dev+1299-g1c2e504d5 (used the appimage release)
|
||||
`alacritty --version`: alacritty 0.7.2 (5ac8060b)
|
||||
|
||||
**Further information**
|
||||
Reproduction steps, noticable behavior, related issues etc
|
||||
|
|
|
|||
19
CHANGELOG.md
19
CHANGELOG.md
|
|
@ -5,8 +5,27 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
|
||||
## [Unreleased]
|
||||
* Change Switch default config loading order of `HOME` and system (https://github.com/zellij-org/zellij/pull/488)
|
||||
* Add support for requesting a simpler layout from plugins, move `clean` flag from `options` to `setup` (https://github.com/zellij-org/zellij/pull/479)
|
||||
* Improve config loading slightly (https://github.com/zellij-org/zellij/pull/492)
|
||||
* Terminal compatibility: preserve current style when clearing viewport (https://github.com/zellij-org/zellij/pull/493)
|
||||
* Fix propagation of plugin ui request (https://github.com/zellij-org/zellij/pull/495)
|
||||
* Handle pasted text properly (https://github.com/zellij-org/zellij/pull/494)
|
||||
* Fix default keybinds for tab -> resize mode (https://github.com/zellij-org/zellij/pull/497)
|
||||
|
||||
## [0.9.0] - 2021-05-11
|
||||
* Add more functionality to unbinding the default keybindings (https://github.com/zellij-org/zellij/pull/468)
|
||||
* Terminal compatibility: fix support for CSI subparameters (https://github.com/zellij-org/zellij/pull/469)
|
||||
* Move the sync command to tab mode (https://github.com/zellij-org/zellij/pull/412)
|
||||
* Fix exit code of `dump-default-config` (https://github.com/zellij-org/zellij/pull/480)
|
||||
* Feature: Switch tabs using `Alt + h/l` in normal mode if there are no panes in the direction (https://github.com/zellij-org/zellij/pull/471)
|
||||
* Terminal Compatibility: various behaviour fixes (https://github.com/zellij-org/zellij/pull/486)
|
||||
* Fix handling of `$HOME` `config` directory, especially relevant for darwin systems (https://github.com/zellij-org/zellij/pull/487)
|
||||
|
||||
## [0.8.0] - 2021-05-07
|
||||
* Terminal compatibility: pass vttest 8 (https://github.com/zellij-org/zellij/pull/461)
|
||||
* Add a Manpage (https://github.com/zellij-org/zellij/pull/455)
|
||||
* Code infrastructure changes to support the upcoming session detach (https://github.com/zellij-org/zellij/pull/223)
|
||||
|
||||
## [0.7.0] - 2021-05-04
|
||||
* Fix the tab '(Sync)' suffix in named tabs (https://github.com/zellij-org/zellij/pull/410)
|
||||
|
|
|
|||
8
Cargo.lock
generated
8
Cargo.lock
generated
|
|
@ -1871,9 +1871,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "vte"
|
||||
version = "0.8.0"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96cc8a191608603611e78c6ec11dafef37e3cca0775aeef1931824753e81711d"
|
||||
checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"utf8parse 0.2.0",
|
||||
|
|
@ -2254,7 +2254,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zellij"
|
||||
version = "0.8.0"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1",
|
||||
"async-std",
|
||||
|
|
@ -2282,7 +2282,7 @@ dependencies = [
|
|||
"termios",
|
||||
"unicode-truncate",
|
||||
"unicode-width",
|
||||
"vte 0.8.0",
|
||||
"vte 0.10.1",
|
||||
"wasmer",
|
||||
"wasmer-wasi",
|
||||
"zellij-tile",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "zellij"
|
||||
version = "0.8.0"
|
||||
version = "0.10.0"
|
||||
authors = ["Aram Drevekenin <aram@poor.dev>"]
|
||||
edition = "2018"
|
||||
description = "A terminal workspace with batteries included"
|
||||
|
|
@ -30,7 +30,7 @@ termion = "1.5.0"
|
|||
termios = "0.3"
|
||||
unicode-truncate = "0.2.0"
|
||||
unicode-width = "0.1.8"
|
||||
vte = "0.8.0"
|
||||
vte = "0.10.1"
|
||||
strum = "0.20.0"
|
||||
lazy_static = "1.4.0"
|
||||
wasmer = "1.0.0"
|
||||
|
|
@ -80,4 +80,5 @@ assets = [
|
|||
]
|
||||
|
||||
[features]
|
||||
default = [ "enable_automatic_asset_installation", ]
|
||||
enable_automatic_asset_installation = []
|
||||
|
|
|
|||
|
|
@ -94,15 +94,19 @@ end
|
|||
'''
|
||||
|
||||
[tasks.manpage]
|
||||
workspace = false
|
||||
description = "Use mandown crate to create or update man entry from docs/MANPAGES.md"
|
||||
script = "mandown docs/MANPAGE.md ZELLIJ 1 > assets/man/zellij.1"
|
||||
script = '''
|
||||
root_dir=${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}
|
||||
mkdir -p ${root_dir}/assets/man
|
||||
mandown ${root_dir}/docs/MANPAGE.md ZELLIJ 1 > ${root_dir}/assets/man/zellij.1
|
||||
'''
|
||||
dependencies = ["install-mandown"]
|
||||
|
||||
[tasks.install-mandown]
|
||||
command = "cargo"
|
||||
args = ["install", "mandown"]
|
||||
|
||||
|
||||
# CI Releasing Zellij
|
||||
[tasks.ci-build-release]
|
||||
workspace = false
|
||||
|
|
|
|||
16
README.md
16
README.md
|
|
@ -36,12 +36,17 @@ cargo install zellij
|
|||
|
||||
Or you can download a prebuilt binary from our [Releases](https://github.com/zellij-org/zellij/releases).
|
||||
|
||||
## How do I hack on it?
|
||||
As the default plugins make use of characters that are mostly only found in [nerdfonts](https://www.nerdfonts.com/),
|
||||
you get the best experience either with installing nerdfonts, or telling the plugins that you request a ui, that
|
||||
does not rely on such characters with `zellij options --simplified-ui`, or putting `simplified_ui: true` in the
|
||||
config file.
|
||||
|
||||
## How do I hack on it? (Contributing)
|
||||
* Clone the project
|
||||
* Install cargo-make with `cargo install --force cargo-make`
|
||||
* In the project folder, run: `cargo make run`
|
||||
* In the project folder, for debug builds run: `cargo make run`
|
||||
|
||||
For more build commands, take a look at [`Contributing.md`](CONTRIBUTING.md).
|
||||
For more build commands, see [`Contributing.md`](CONTRIBUTING.md).
|
||||
|
||||
## Configuration
|
||||
For configuring Zellij, please see the [Configuration documentation](https://zellij.dev/documentation/configuration.html).
|
||||
|
|
@ -68,11 +73,6 @@ This section contains an ever-changing list of the major features that are eithe
|
|||
* **Support for multiple terminal windows across screens** - Transfer panes across different windows and screens by having them all belong to the same session.
|
||||
* **Smart layouts** - expand the current layout system so that it rearranges and hides panes intelligently when new ones are added or the window size is changed.
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
Take a look at [`Contributing.md`](CONTRIBUTING.md) guide.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ keybinds:
|
|||
key: [Ctrl: 'q',]
|
||||
- action: [NewPane: ]
|
||||
key: [ Alt: 'n',]
|
||||
- action: [MoveFocus: Left,]
|
||||
- action: [MoveFocusOrTab: Left,]
|
||||
key: [ Alt: 'h',]
|
||||
- action: [MoveFocus: Right,]
|
||||
- action: [MoveFocusOrTab: Right,]
|
||||
key: [ Alt: 'l',]
|
||||
- action: [MoveFocus: Down,]
|
||||
key: [ Alt: 'j',]
|
||||
|
|
@ -97,8 +97,6 @@ keybinds:
|
|||
key: [Char: 'r',]
|
||||
- action: [CloseFocus,]
|
||||
key: [Char: 'x',]
|
||||
- action: [ToggleActiveSyncPanes]
|
||||
key: [Char: 's']
|
||||
- action: [ToggleFocusFullscreen,]
|
||||
key: [Char: 'f',]
|
||||
- action: [FocusPreviousPane,]
|
||||
|
|
@ -110,8 +108,10 @@ keybinds:
|
|||
key: [Ctrl: 'g']
|
||||
- action: [SwitchToMode: Pane,]
|
||||
key: [Ctrl: 'p',]
|
||||
- action: [SwitchToMode: Resize,]
|
||||
key: [Ctrl: 'r',]
|
||||
- action: [SwitchToMode: Normal,]
|
||||
key: [Ctrl: 'r', Ctrl: 't', Char: "\n", Char: ' ',]
|
||||
key: [Ctrl: 't', Char: "\n", Char: ' ',]
|
||||
- action: [SwitchToMode: Scroll,]
|
||||
key: [Ctrl: 's']
|
||||
- action: [SwitchToMode: RenameTab, TabNameInput: [0],]
|
||||
|
|
@ -130,6 +130,8 @@ keybinds:
|
|||
key: [ Char: 'n',]
|
||||
- action: [CloseTab,]
|
||||
key: [ Char: 'x',]
|
||||
- action: [ToggleActiveSyncTab]
|
||||
key: [Char: 's']
|
||||
- action: [MoveFocus: Left,]
|
||||
key: [ Alt: 'h',]
|
||||
- action: [MoveFocus: Right,]
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use ansi_term::ANSIStrings;
|
|||
use zellij_tile::prelude::*;
|
||||
|
||||
use crate::color_elements;
|
||||
use crate::{ColoredElements, LinePart, ARROW_SEPARATOR};
|
||||
use crate::{ColoredElements, LinePart};
|
||||
|
||||
struct CtrlKeyShortcut {
|
||||
mode: CtrlKeyMode,
|
||||
|
|
@ -63,13 +63,18 @@ impl CtrlKeyShortcut {
|
|||
}
|
||||
}
|
||||
|
||||
fn unselected_mode_shortcut(letter: char, text: &str, palette: ColoredElements) -> LinePart {
|
||||
let prefix_separator = palette.unselected_prefix_separator.paint(ARROW_SEPARATOR);
|
||||
fn unselected_mode_shortcut(
|
||||
letter: char,
|
||||
text: &str,
|
||||
palette: ColoredElements,
|
||||
separator: &str,
|
||||
) -> LinePart {
|
||||
let prefix_separator = palette.unselected_prefix_separator.paint(separator);
|
||||
let char_left_separator = palette.unselected_char_left_separator.paint(" <");
|
||||
let char_shortcut = palette.unselected_char_shortcut.paint(letter.to_string());
|
||||
let char_right_separator = palette.unselected_char_right_separator.paint(">");
|
||||
let styled_text = palette.unselected_styled_text.paint(format!("{} ", text));
|
||||
let suffix_separator = palette.unselected_suffix_separator.paint(ARROW_SEPARATOR);
|
||||
let suffix_separator = palette.unselected_suffix_separator.paint(separator);
|
||||
LinePart {
|
||||
part: ANSIStrings(&[
|
||||
prefix_separator,
|
||||
|
|
@ -84,13 +89,18 @@ fn unselected_mode_shortcut(letter: char, text: &str, palette: ColoredElements)
|
|||
}
|
||||
}
|
||||
|
||||
fn selected_mode_shortcut(letter: char, text: &str, palette: ColoredElements) -> LinePart {
|
||||
let prefix_separator = palette.selected_prefix_separator.paint(ARROW_SEPARATOR);
|
||||
fn selected_mode_shortcut(
|
||||
letter: char,
|
||||
text: &str,
|
||||
palette: ColoredElements,
|
||||
separator: &str,
|
||||
) -> LinePart {
|
||||
let prefix_separator = palette.selected_prefix_separator.paint(separator);
|
||||
let char_left_separator = palette.selected_char_left_separator.paint(" <".to_string());
|
||||
let char_shortcut = palette.selected_char_shortcut.paint(format!("{}", letter));
|
||||
let char_right_separator = palette.selected_char_right_separator.paint(">".to_string());
|
||||
let styled_text = palette.selected_styled_text.paint(format!("{} ", text));
|
||||
let suffix_separator = palette.selected_suffix_separator.paint(ARROW_SEPARATOR);
|
||||
let suffix_separator = palette.selected_suffix_separator.paint(separator);
|
||||
LinePart {
|
||||
part: ANSIStrings(&[
|
||||
prefix_separator,
|
||||
|
|
@ -105,69 +115,89 @@ fn selected_mode_shortcut(letter: char, text: &str, palette: ColoredElements) ->
|
|||
}
|
||||
}
|
||||
|
||||
fn disabled_mode_shortcut(text: &str, palette: ColoredElements) -> LinePart {
|
||||
let prefix_separator = palette.disabled_prefix_separator.paint(ARROW_SEPARATOR);
|
||||
fn disabled_mode_shortcut(text: &str, palette: ColoredElements, separator: &str) -> LinePart {
|
||||
let prefix_separator = palette.disabled_prefix_separator.paint(separator);
|
||||
let styled_text = palette.disabled_styled_text.paint(format!("{} ", text));
|
||||
let suffix_separator = palette.disabled_suffix_separator.paint(ARROW_SEPARATOR);
|
||||
let suffix_separator = palette.disabled_suffix_separator.paint(separator);
|
||||
LinePart {
|
||||
part: format!("{}{}{}", prefix_separator, styled_text, suffix_separator),
|
||||
len: text.chars().count() + 2 + 1, // 2 for the arrows, 1 for the padding in the end
|
||||
}
|
||||
}
|
||||
|
||||
fn selected_mode_shortcut_single_letter(letter: char, palette: ColoredElements) -> LinePart {
|
||||
fn selected_mode_shortcut_single_letter(
|
||||
letter: char,
|
||||
palette: ColoredElements,
|
||||
separator: &str,
|
||||
) -> LinePart {
|
||||
let char_shortcut_text = format!(" {} ", letter);
|
||||
let len = char_shortcut_text.chars().count() + 4; // 2 for the arrows, 2 for the padding
|
||||
let prefix_separator = palette
|
||||
.selected_single_letter_prefix_separator
|
||||
.paint(ARROW_SEPARATOR);
|
||||
.paint(separator);
|
||||
let char_shortcut = palette
|
||||
.selected_single_letter_char_shortcut
|
||||
.paint(char_shortcut_text);
|
||||
let suffix_separator = palette
|
||||
.selected_single_letter_suffix_separator
|
||||
.paint(ARROW_SEPARATOR);
|
||||
.paint(separator);
|
||||
LinePart {
|
||||
part: ANSIStrings(&[prefix_separator, char_shortcut, suffix_separator]).to_string(),
|
||||
len,
|
||||
}
|
||||
}
|
||||
|
||||
fn unselected_mode_shortcut_single_letter(letter: char, palette: ColoredElements) -> LinePart {
|
||||
fn unselected_mode_shortcut_single_letter(
|
||||
letter: char,
|
||||
palette: ColoredElements,
|
||||
separator: &str,
|
||||
) -> LinePart {
|
||||
let char_shortcut_text = format!(" {} ", letter);
|
||||
let len = char_shortcut_text.chars().count() + 4; // 2 for the arrows, 2 for the padding
|
||||
let prefix_separator = palette
|
||||
.unselected_single_letter_prefix_separator
|
||||
.paint(ARROW_SEPARATOR);
|
||||
.paint(separator);
|
||||
let char_shortcut = palette
|
||||
.unselected_single_letter_char_shortcut
|
||||
.paint(char_shortcut_text);
|
||||
let suffix_separator = palette
|
||||
.unselected_single_letter_suffix_separator
|
||||
.paint(ARROW_SEPARATOR);
|
||||
.paint(separator);
|
||||
LinePart {
|
||||
part: ANSIStrings(&[prefix_separator, char_shortcut, suffix_separator]).to_string(),
|
||||
len,
|
||||
}
|
||||
}
|
||||
|
||||
fn full_ctrl_key(key: &CtrlKeyShortcut, palette: ColoredElements) -> LinePart {
|
||||
fn full_ctrl_key(key: &CtrlKeyShortcut, palette: ColoredElements, separator: &str) -> LinePart {
|
||||
let full_text = key.full_text();
|
||||
let letter_shortcut = key.letter_shortcut();
|
||||
match key.mode {
|
||||
CtrlKeyMode::Unselected => {
|
||||
unselected_mode_shortcut(letter_shortcut, &format!(" {}", full_text), palette)
|
||||
}
|
||||
CtrlKeyMode::Selected => {
|
||||
selected_mode_shortcut(letter_shortcut, &format!(" {}", full_text), palette)
|
||||
}
|
||||
CtrlKeyMode::Disabled => {
|
||||
disabled_mode_shortcut(&format!(" <{}> {}", letter_shortcut, full_text), palette)
|
||||
}
|
||||
CtrlKeyMode::Unselected => unselected_mode_shortcut(
|
||||
letter_shortcut,
|
||||
&format!(" {}", full_text),
|
||||
palette,
|
||||
separator,
|
||||
),
|
||||
CtrlKeyMode::Selected => selected_mode_shortcut(
|
||||
letter_shortcut,
|
||||
&format!(" {}", full_text),
|
||||
palette,
|
||||
separator,
|
||||
),
|
||||
CtrlKeyMode::Disabled => disabled_mode_shortcut(
|
||||
&format!(" <{}> {}", letter_shortcut, full_text),
|
||||
palette,
|
||||
separator,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn shortened_ctrl_key(key: &CtrlKeyShortcut, palette: ColoredElements) -> LinePart {
|
||||
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 {
|
||||
|
|
@ -176,33 +206,47 @@ fn shortened_ctrl_key(key: &CtrlKeyShortcut, palette: ColoredElements) -> LinePa
|
|||
};
|
||||
match key.mode {
|
||||
CtrlKeyMode::Unselected => {
|
||||
unselected_mode_shortcut(letter_shortcut, &shortened_text, palette)
|
||||
unselected_mode_shortcut(letter_shortcut, &shortened_text, palette, separator)
|
||||
}
|
||||
CtrlKeyMode::Selected => {
|
||||
selected_mode_shortcut(letter_shortcut, &shortened_text, palette, separator)
|
||||
}
|
||||
CtrlKeyMode::Selected => selected_mode_shortcut(letter_shortcut, &shortened_text, palette),
|
||||
CtrlKeyMode::Disabled => disabled_mode_shortcut(
|
||||
&format!(" <{}>{}", letter_shortcut, shortened_text),
|
||||
palette,
|
||||
),
|
||||
CtrlKeyMode::Disabled => disabled_mode_shortcut(
|
||||
&format!(" <{}>{}", letter_shortcut, shortened_text),
|
||||
palette,
|
||||
separator,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn single_letter_ctrl_key(key: &CtrlKeyShortcut, palette: ColoredElements) -> LinePart {
|
||||
fn single_letter_ctrl_key(
|
||||
key: &CtrlKeyShortcut,
|
||||
palette: ColoredElements,
|
||||
separator: &str,
|
||||
) -> LinePart {
|
||||
let letter_shortcut = key.letter_shortcut();
|
||||
match key.mode {
|
||||
CtrlKeyMode::Unselected => unselected_mode_shortcut_single_letter(letter_shortcut, palette),
|
||||
CtrlKeyMode::Selected => selected_mode_shortcut_single_letter(letter_shortcut, palette),
|
||||
CtrlKeyMode::Disabled => disabled_mode_shortcut(&format!(" {}", letter_shortcut), palette),
|
||||
CtrlKeyMode::Unselected => {
|
||||
unselected_mode_shortcut_single_letter(letter_shortcut, palette, separator)
|
||||
}
|
||||
CtrlKeyMode::Selected => {
|
||||
selected_mode_shortcut_single_letter(letter_shortcut, palette, separator)
|
||||
}
|
||||
CtrlKeyMode::Disabled => {
|
||||
disabled_mode_shortcut(&format!(" {}", letter_shortcut), palette, separator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_indicators(max_len: usize, keys: &[CtrlKeyShortcut], palette: ColoredElements) -> LinePart {
|
||||
fn key_indicators(
|
||||
max_len: usize,
|
||||
keys: &[CtrlKeyShortcut],
|
||||
palette: ColoredElements,
|
||||
separator: &str,
|
||||
) -> LinePart {
|
||||
let mut line_part = LinePart::default();
|
||||
for ctrl_key in keys {
|
||||
let key = full_ctrl_key(ctrl_key, palette);
|
||||
let key = full_ctrl_key(ctrl_key, palette, separator);
|
||||
line_part.part = format!("{}{}", line_part.part, key.part);
|
||||
line_part.len += key.len;
|
||||
}
|
||||
|
|
@ -211,7 +255,7 @@ fn key_indicators(max_len: usize, keys: &[CtrlKeyShortcut], palette: ColoredElem
|
|||
}
|
||||
line_part = LinePart::default();
|
||||
for ctrl_key in keys {
|
||||
let key = shortened_ctrl_key(ctrl_key, palette);
|
||||
let key = shortened_ctrl_key(ctrl_key, palette, separator);
|
||||
line_part.part = format!("{}{}", line_part.part, key.part);
|
||||
line_part.len += key.len;
|
||||
}
|
||||
|
|
@ -220,7 +264,7 @@ fn key_indicators(max_len: usize, keys: &[CtrlKeyShortcut], palette: ColoredElem
|
|||
}
|
||||
line_part = LinePart::default();
|
||||
for ctrl_key in keys {
|
||||
let key = single_letter_ctrl_key(ctrl_key, palette);
|
||||
let key = single_letter_ctrl_key(ctrl_key, palette, separator);
|
||||
line_part.part = format!("{}{}", line_part.part, key.part);
|
||||
line_part.len += key.len;
|
||||
}
|
||||
|
|
@ -231,17 +275,17 @@ fn key_indicators(max_len: usize, keys: &[CtrlKeyShortcut], palette: ColoredElem
|
|||
line_part
|
||||
}
|
||||
|
||||
pub fn superkey(palette: ColoredElements) -> LinePart {
|
||||
pub fn superkey(palette: ColoredElements, separator: &str) -> LinePart {
|
||||
let prefix_text = " Ctrl +";
|
||||
let prefix = palette.superkey_prefix.paint(prefix_text);
|
||||
let suffix_separator = palette.superkey_suffix_separator.paint(ARROW_SEPARATOR);
|
||||
let suffix_separator = palette.superkey_suffix_separator.paint(separator);
|
||||
LinePart {
|
||||
part: ANSIStrings(&[prefix, suffix_separator]).to_string(),
|
||||
len: prefix_text.chars().count(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ctrl_keys(help: &ModeInfo, max_len: usize) -> LinePart {
|
||||
pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart {
|
||||
let colored_elements = color_elements(help.palette);
|
||||
match &help.mode {
|
||||
InputMode::Locked => key_indicators(
|
||||
|
|
@ -255,6 +299,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize) -> LinePart {
|
|||
CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Quit),
|
||||
],
|
||||
colored_elements,
|
||||
separator,
|
||||
),
|
||||
InputMode::Resize => key_indicators(
|
||||
max_len,
|
||||
|
|
@ -267,6 +312,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize) -> LinePart {
|
|||
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
|
||||
],
|
||||
colored_elements,
|
||||
separator,
|
||||
),
|
||||
InputMode::Pane => key_indicators(
|
||||
max_len,
|
||||
|
|
@ -279,6 +325,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize) -> LinePart {
|
|||
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
|
||||
],
|
||||
colored_elements,
|
||||
separator,
|
||||
),
|
||||
InputMode::Tab | InputMode::RenameTab => key_indicators(
|
||||
max_len,
|
||||
|
|
@ -291,6 +338,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize) -> LinePart {
|
|||
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
|
||||
],
|
||||
colored_elements,
|
||||
separator,
|
||||
),
|
||||
InputMode::Scroll => key_indicators(
|
||||
max_len,
|
||||
|
|
@ -303,6 +351,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize) -> LinePart {
|
|||
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
|
||||
],
|
||||
colored_elements,
|
||||
separator,
|
||||
),
|
||||
InputMode::Normal => key_indicators(
|
||||
max_len,
|
||||
|
|
@ -315,6 +364,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize) -> LinePart {
|
|||
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
|
||||
],
|
||||
colored_elements,
|
||||
separator,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,9 +140,15 @@ impl ZellijPlugin for State {
|
|||
}
|
||||
|
||||
fn render(&mut self, _rows: usize, cols: usize) {
|
||||
let separator = if !self.mode_info.capabilities.arrow_fonts {
|
||||
ARROW_SEPARATOR
|
||||
} else {
|
||||
&""
|
||||
};
|
||||
|
||||
let colored_elements = color_elements(self.mode_info.palette);
|
||||
let superkey = superkey(colored_elements);
|
||||
let ctrl_keys = ctrl_keys(&self.mode_info, cols - superkey.len);
|
||||
let superkey = superkey(colored_elements, separator);
|
||||
let ctrl_keys = ctrl_keys(&self.mode_info, cols - superkey.len, separator);
|
||||
|
||||
let first_line = format!("{}{}", superkey, ctrl_keys);
|
||||
let second_line = keybinds(&self.mode_info, cols);
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ fn populate_tabs_in_tab_line(
|
|||
}
|
||||
}
|
||||
|
||||
fn left_more_message(tab_count_to_the_left: usize, palette: Palette) -> LinePart {
|
||||
fn left_more_message(tab_count_to_the_left: usize, palette: Palette, separator: &str) -> LinePart {
|
||||
if tab_count_to_the_left == 0 {
|
||||
return LinePart {
|
||||
part: String::new(),
|
||||
|
|
@ -62,11 +62,11 @@ fn left_more_message(tab_count_to_the_left: usize, palette: Palette) -> LinePart
|
|||
};
|
||||
// 238
|
||||
let more_text_len = more_text.chars().count() + 2; // 2 for the arrows
|
||||
let left_separator = style!(palette.bg, palette.orange).paint(ARROW_SEPARATOR);
|
||||
let left_separator = style!(palette.bg, palette.orange).paint(separator);
|
||||
let more_styled_text = style!(palette.black, palette.orange)
|
||||
.bold()
|
||||
.paint(more_text);
|
||||
let right_separator = style!(palette.orange, palette.bg).paint(ARROW_SEPARATOR);
|
||||
let right_separator = style!(palette.orange, palette.bg).paint(separator);
|
||||
let more_styled_text = format!(
|
||||
"{}",
|
||||
ANSIStrings(&[left_separator, more_styled_text, right_separator,])
|
||||
|
|
@ -77,7 +77,11 @@ fn left_more_message(tab_count_to_the_left: usize, palette: Palette) -> LinePart
|
|||
}
|
||||
}
|
||||
|
||||
fn right_more_message(tab_count_to_the_right: usize, palette: Palette) -> LinePart {
|
||||
fn right_more_message(
|
||||
tab_count_to_the_right: usize,
|
||||
palette: Palette,
|
||||
separator: &str,
|
||||
) -> LinePart {
|
||||
if tab_count_to_the_right == 0 {
|
||||
return LinePart {
|
||||
part: String::new(),
|
||||
|
|
@ -90,11 +94,11 @@ fn right_more_message(tab_count_to_the_right: usize, palette: Palette) -> LinePa
|
|||
" +many → ".to_string()
|
||||
};
|
||||
let more_text_len = more_text.chars().count() + 1; // 2 for the arrow
|
||||
let left_separator = style!(palette.bg, palette.orange).paint(ARROW_SEPARATOR);
|
||||
let left_separator = style!(palette.bg, palette.orange).paint(separator);
|
||||
let more_styled_text = style!(palette.black, palette.orange)
|
||||
.bold()
|
||||
.paint(more_text);
|
||||
let right_separator = style!(palette.orange, palette.bg).paint(ARROW_SEPARATOR);
|
||||
let right_separator = style!(palette.orange, palette.bg).paint(separator);
|
||||
let more_styled_text = format!(
|
||||
"{}",
|
||||
ANSIStrings(&[left_separator, more_styled_text, right_separator,])
|
||||
|
|
@ -111,14 +115,15 @@ fn add_previous_tabs_msg(
|
|||
title_bar: &mut Vec<LinePart>,
|
||||
cols: usize,
|
||||
palette: Palette,
|
||||
separator: &str,
|
||||
) {
|
||||
while get_current_title_len(&tabs_to_render)
|
||||
+ left_more_message(tabs_before_active.len(), palette).len
|
||||
+ left_more_message(tabs_before_active.len(), palette, separator).len
|
||||
>= cols
|
||||
{
|
||||
tabs_before_active.push(tabs_to_render.remove(0));
|
||||
}
|
||||
let left_more_message = left_more_message(tabs_before_active.len(), palette);
|
||||
let left_more_message = left_more_message(tabs_before_active.len(), palette, separator);
|
||||
title_bar.push(left_more_message);
|
||||
}
|
||||
|
||||
|
|
@ -127,14 +132,15 @@ fn add_next_tabs_msg(
|
|||
title_bar: &mut Vec<LinePart>,
|
||||
cols: usize,
|
||||
palette: Palette,
|
||||
separator: &str,
|
||||
) {
|
||||
while get_current_title_len(&title_bar)
|
||||
+ right_more_message(tabs_after_active.len(), palette).len
|
||||
+ right_more_message(tabs_after_active.len(), palette, separator).len
|
||||
>= cols
|
||||
{
|
||||
tabs_after_active.insert(0, title_bar.pop().unwrap());
|
||||
}
|
||||
let right_more_message = right_more_message(tabs_after_active.len(), palette);
|
||||
let right_more_message = right_more_message(tabs_after_active.len(), palette, separator);
|
||||
title_bar.push(right_more_message);
|
||||
}
|
||||
|
||||
|
|
@ -148,11 +154,20 @@ fn tab_line_prefix(palette: Palette) -> LinePart {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn tab_separator(capabilities: PluginCapabilities) -> &'static str {
|
||||
if !capabilities.arrow_fonts {
|
||||
ARROW_SEPARATOR
|
||||
} else {
|
||||
&""
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tab_line(
|
||||
mut all_tabs: Vec<LinePart>,
|
||||
active_tab_index: usize,
|
||||
cols: usize,
|
||||
palette: Palette,
|
||||
capabilities: PluginCapabilities,
|
||||
) -> Vec<LinePart> {
|
||||
let mut tabs_to_render: Vec<LinePart> = vec![];
|
||||
let mut tabs_after_active = all_tabs.split_off(active_tab_index);
|
||||
|
|
@ -180,6 +195,7 @@ pub fn tab_line(
|
|||
&mut tab_line,
|
||||
cols - prefix.len,
|
||||
palette,
|
||||
tab_separator(capabilities),
|
||||
);
|
||||
}
|
||||
tab_line.append(&mut tabs_to_render);
|
||||
|
|
@ -189,6 +205,7 @@ pub fn tab_line(
|
|||
&mut tab_line,
|
||||
cols - prefix.len,
|
||||
palette,
|
||||
tab_separator(capabilities),
|
||||
);
|
||||
}
|
||||
tab_line.insert(0, prefix);
|
||||
|
|
|
|||
|
|
@ -60,10 +60,17 @@ impl ZellijPlugin for State {
|
|||
t.position,
|
||||
t.is_sync_panes_active,
|
||||
self.mode_info.palette,
|
||||
self.mode_info.capabilities,
|
||||
);
|
||||
all_tabs.push(tab);
|
||||
}
|
||||
let tab_line = tab_line(all_tabs, active_tab_index, cols, self.mode_info.palette);
|
||||
let tab_line = tab_line(
|
||||
all_tabs,
|
||||
active_tab_index,
|
||||
cols,
|
||||
self.mode_info.palette,
|
||||
self.mode_info.capabilities,
|
||||
);
|
||||
let mut s = String::new();
|
||||
for bar_part in tab_line {
|
||||
s = format!("{}{}", s, bar_part.part);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
use crate::{LinePart, ARROW_SEPARATOR};
|
||||
use crate::{line::tab_separator, LinePart};
|
||||
use ansi_term::ANSIStrings;
|
||||
use zellij_tile::prelude::*;
|
||||
use zellij_tile_utils::style;
|
||||
|
||||
pub fn active_tab(text: String, palette: Palette) -> LinePart {
|
||||
let left_separator = style!(palette.bg, palette.green).paint(ARROW_SEPARATOR);
|
||||
pub fn active_tab(text: String, palette: Palette, separator: &str) -> LinePart {
|
||||
let left_separator = style!(palette.bg, palette.green).paint(separator);
|
||||
let tab_text_len = text.chars().count() + 4; // 2 for left and right separators, 2 for the text padding
|
||||
let tab_styled_text = style!(palette.black, palette.green)
|
||||
.bold()
|
||||
.paint(format!(" {} ", text));
|
||||
let right_separator = style!(palette.green, palette.bg).paint(ARROW_SEPARATOR);
|
||||
let right_separator = style!(palette.green, palette.bg).paint(separator);
|
||||
let tab_styled_text = format!(
|
||||
"{}",
|
||||
ANSIStrings(&[left_separator, tab_styled_text, right_separator,])
|
||||
|
|
@ -20,13 +20,13 @@ pub fn active_tab(text: String, palette: Palette) -> LinePart {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn non_active_tab(text: String, palette: Palette) -> LinePart {
|
||||
let left_separator = style!(palette.bg, palette.fg).paint(ARROW_SEPARATOR);
|
||||
pub fn non_active_tab(text: String, palette: Palette, separator: &str) -> LinePart {
|
||||
let left_separator = style!(palette.bg, palette.fg).paint(separator);
|
||||
let tab_text_len = text.chars().count() + 4; // 2 for left and right separators, 2 for the padding
|
||||
let tab_styled_text = style!(palette.black, palette.fg)
|
||||
.bold()
|
||||
.paint(format!(" {} ", text));
|
||||
let right_separator = style!(palette.fg, palette.bg).paint(ARROW_SEPARATOR);
|
||||
let right_separator = style!(palette.fg, palette.bg).paint(separator);
|
||||
let tab_styled_text = format!(
|
||||
"{}",
|
||||
ANSIStrings(&[left_separator, tab_styled_text, right_separator,])
|
||||
|
|
@ -43,7 +43,9 @@ pub fn tab_style(
|
|||
position: usize,
|
||||
is_sync_panes_active: bool,
|
||||
palette: Palette,
|
||||
capabilities: PluginCapabilities,
|
||||
) -> LinePart {
|
||||
let separator = tab_separator(capabilities);
|
||||
let mut tab_text = if text.is_empty() {
|
||||
format!("Tab #{}", position + 1)
|
||||
} else {
|
||||
|
|
@ -53,8 +55,8 @@ pub fn tab_style(
|
|||
tab_text.push_str(" (Sync)");
|
||||
}
|
||||
if is_active_tab {
|
||||
active_tab(tab_text, palette)
|
||||
active_tab(tab_text, palette, separator)
|
||||
} else {
|
||||
non_active_tab(tab_text, palette)
|
||||
non_active_tab(tab_text, palette, separator)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,5 +37,5 @@ The Boundaries refer to those lines that are drawn between terminal panes. A few
|
|||
* The Rect trait is here so that different panes can implement it, giving boundaries a generic way to calculate the size of the pane and draw boundaries around it.
|
||||
* Here we use the [unicode box drawing characters](https://en.wikipedia.org/wiki/Box-drawing_character) in order to draw the borders. There's some logic here about combining them together for all possible combinations of pane locations.
|
||||
|
||||
## PTY Bus (src/pty_bus.rs)
|
||||
## PTY Bus (src/pty.rs)
|
||||
The PtyBus keeps track of several asynchronous streams that read from pty sockets (eg. /dev/pts/999), parse those bytes into ANSI/VT events and send them on to the Screen so that they can be received in the relevant TerminalPane.
|
||||
|
|
|
|||
|
|
@ -101,6 +101,34 @@ where "normal" stands for a mode name (see MODES section), "action" part
|
|||
specifies the actions to be executed by Zellij (see ACTIONS section) and "key"
|
||||
is used to list keys or key combinations bound to given actions (see KEYS).
|
||||
|
||||
The default keybinds can be unbound either for a specific mode, or for every mode.
|
||||
It supports either a list of `keybinds`, or a bool indicating that every keybind
|
||||
should be unbound:
|
||||
|
||||
```
|
||||
keybinds:
|
||||
unbind: true
|
||||
```
|
||||
Will unbind every default binding.
|
||||
|
||||
```
|
||||
keybinds:
|
||||
unbind: [ Ctrl: 'p']
|
||||
```
|
||||
Will unbind every default `^P` binding for each mode.
|
||||
```
|
||||
keybinds:
|
||||
normal:
|
||||
- unbind: true
|
||||
```
|
||||
Will unbind every default keybind for the `normal` mode.
|
||||
```
|
||||
keybinds:
|
||||
normal:
|
||||
- unbind: [ Alt: 'n', Ctrl: 'g']
|
||||
```
|
||||
Will unbind every default keybind for `n` and `^g` for the `normal` mode.
|
||||
|
||||
ACTIONS
|
||||
-------
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
keybinds:
|
||||
normal:
|
||||
- unbind : true
|
||||
- action: [GoToTab: 1,]
|
||||
key: [F: 1,]
|
||||
- action: [GoToTab: 2,]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
simplified_ui: true
|
||||
keybinds:
|
||||
unbind: true
|
||||
normal:
|
||||
|
|
@ -16,9 +17,9 @@ keybinds:
|
|||
key: [Ctrl: 'q',]
|
||||
- action: [NewPane: ]
|
||||
key: [ Alt: 'n',]
|
||||
- action: [MoveFocus: Left,]
|
||||
- action: [MoveFocusOrTab: Left,]
|
||||
key: [ Alt: 'h',]
|
||||
- action: [MoveFocus: Right,]
|
||||
- action: [MoveFocusOrTab: Right,]
|
||||
key: [ Alt: 'l',]
|
||||
- action: [MoveFocus: Down,]
|
||||
key: [ Alt: 'j',]
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ keybinds:
|
|||
key: [Ctrl: 'q',]
|
||||
- action: [NewPane: ]
|
||||
key: [ Alt: 'n',]
|
||||
- action: [MoveFocus: Left,]
|
||||
- action: [MoveFocusOrTab: Left,]
|
||||
key: [ Alt: 'h',]
|
||||
- action: [MoveFocus: Right,]
|
||||
- action: [MoveFocusOrTab: Right,]
|
||||
key: [ Alt: 'l',]
|
||||
- action: [MoveFocus: Down,]
|
||||
key: [ Alt: 'j',]
|
||||
|
|
|
|||
14
src/cli.rs
14
src/cli.rs
|
|
@ -1,4 +1,5 @@
|
|||
use super::common::utils::consts::{ZELLIJ_CONFIG_DIR_ENV, ZELLIJ_CONFIG_FILE_ENV};
|
||||
use crate::common::input::options::Options;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
|
@ -36,21 +37,20 @@ pub struct CliArgs {
|
|||
#[derive(Debug, StructOpt, Clone, Serialize, Deserialize)]
|
||||
pub enum ConfigCli {
|
||||
/// Change the behaviour of zellij
|
||||
#[structopt(name = "option")]
|
||||
Config {
|
||||
/// Disables loading of configuration file at default location
|
||||
#[structopt(long)]
|
||||
clean: bool,
|
||||
},
|
||||
#[structopt(name = "options")]
|
||||
Options(Options),
|
||||
|
||||
#[structopt(name = "generate-completion")]
|
||||
GenerateCompletion { shell: String },
|
||||
|
||||
#[structopt(name = "setup")]
|
||||
Setup {
|
||||
/// Disables loading of configuration file at default location
|
||||
/// Dump the default configuration file to stdout
|
||||
#[structopt(long)]
|
||||
dump_config: bool,
|
||||
/// Disables loading of configuration file at default location,
|
||||
/// loads the defaults that zellij ships with
|
||||
#[structopt(long)]
|
||||
clean: bool,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ impl Layout {
|
|||
}
|
||||
|
||||
// It wants to use Path here, but that doesn't compile.
|
||||
#[warn(clippy::ptr_arg)]
|
||||
#[allow(clippy::ptr_arg)]
|
||||
pub fn from_defaults(layout_path: &PathBuf, data_dir: &Path) -> Self {
|
||||
Self::new(
|
||||
&data_dir
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@ use crate::common::{
|
|||
errors::{ClientContext, ContextType},
|
||||
input::config::Config,
|
||||
input::handler::input_loop,
|
||||
input::options::Options,
|
||||
os_input_output::ClientOsApi,
|
||||
SenderType, SenderWithContext, SyncChannelWithContext,
|
||||
thread_bus::{SenderType, SenderWithContext, SyncChannelWithContext},
|
||||
};
|
||||
use crate::server::ServerInstruction;
|
||||
|
||||
|
|
@ -30,20 +31,36 @@ pub enum ClientInstruction {
|
|||
}
|
||||
|
||||
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));
|
||||
os_input.send_to_server(ServerInstruction::NewClient(
|
||||
full_screen_ws,
|
||||
opts,
|
||||
config_options,
|
||||
));
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -4,13 +4,15 @@ use std::{
|
|||
fmt::{self, Debug, Formatter},
|
||||
};
|
||||
|
||||
use vte::{Params, Perform};
|
||||
|
||||
const TABSTOP_WIDTH: usize = 8; // TODO: is this always right?
|
||||
const SCROLL_BACK: usize = 10_000;
|
||||
|
||||
use crate::utils::logging::debug_log_to_file;
|
||||
|
||||
use crate::panes::terminal_character::{
|
||||
CharacterStyles, CharsetIndex, Cursor, StandardCharset, TerminalCharacter,
|
||||
CharacterStyles, CharsetIndex, Cursor, CursorShape, StandardCharset, TerminalCharacter,
|
||||
EMPTY_TERMINAL_CHARACTER,
|
||||
};
|
||||
|
||||
|
|
@ -175,6 +177,7 @@ pub struct Grid {
|
|||
saved_cursor_position: Option<Cursor>,
|
||||
scroll_region: Option<(usize, usize)>,
|
||||
active_charset: CharsetIndex,
|
||||
preceding_char: Option<TerminalCharacter>,
|
||||
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 erasure_mode: bool, // ERM
|
||||
|
|
@ -208,6 +211,7 @@ impl Grid {
|
|||
cursor: Cursor::new(0, 0),
|
||||
saved_cursor_position: None,
|
||||
scroll_region: None,
|
||||
preceding_char: None,
|
||||
width: columns,
|
||||
height: rows,
|
||||
should_render: true,
|
||||
|
|
@ -243,6 +247,26 @@ impl Grid {
|
|||
empty_character.styles = styles;
|
||||
self.pad_current_line_until(self.cursor.x);
|
||||
}
|
||||
pub fn move_to_previous_tabstop(&mut self) {
|
||||
let mut previous_tabstop = None;
|
||||
for tabstop in self.horizontal_tabstops.iter() {
|
||||
if *tabstop >= self.cursor.x {
|
||||
break;
|
||||
}
|
||||
previous_tabstop = Some(tabstop);
|
||||
}
|
||||
match previous_tabstop {
|
||||
Some(tabstop) => {
|
||||
self.cursor.x = *tabstop;
|
||||
}
|
||||
None => {
|
||||
self.cursor.x = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn cursor_shape(&self) -> CursorShape {
|
||||
self.cursor.get_shape()
|
||||
}
|
||||
fn set_horizontal_tabstop(&mut self) {
|
||||
self.horizontal_tabstops.insert(self.cursor.x);
|
||||
}
|
||||
|
|
@ -811,7 +835,8 @@ impl Grid {
|
|||
pub fn show_cursor(&mut self) {
|
||||
self.cursor.is_hidden = false;
|
||||
}
|
||||
pub fn set_scroll_region(&mut self, top_line_index: usize, bottom_line_index: usize) {
|
||||
pub fn set_scroll_region(&mut self, top_line_index: usize, bottom_line_index: Option<usize>) {
|
||||
let bottom_line_index = bottom_line_index.unwrap_or(self.height);
|
||||
self.scroll_region = Some((top_line_index, bottom_line_index));
|
||||
}
|
||||
pub fn clear_scroll_region(&mut self) {
|
||||
|
|
@ -922,10 +947,14 @@ impl Grid {
|
|||
self.active_charset = Default::default();
|
||||
self.erasure_mode = false;
|
||||
self.disable_linewrap = false;
|
||||
self.cursor.change_shape(CursorShape::Block);
|
||||
}
|
||||
fn set_preceding_character(&mut self, terminal_character: TerminalCharacter) {
|
||||
self.preceding_char = Some(terminal_character);
|
||||
}
|
||||
}
|
||||
|
||||
impl vte::Perform for Grid {
|
||||
impl Perform for Grid {
|
||||
fn print(&mut self, c: char) {
|
||||
let c = self.cursor.charsets[self.active_charset].map(c);
|
||||
// apparently, building TerminalCharacter like this without a "new" method
|
||||
|
|
@ -934,6 +963,7 @@ impl vte::Perform for Grid {
|
|||
character: c,
|
||||
styles: self.cursor.pending_styles,
|
||||
};
|
||||
self.set_preceding_character(terminal_character);
|
||||
self.add_character(terminal_character);
|
||||
}
|
||||
|
||||
|
|
@ -947,9 +977,10 @@ impl vte::Perform for Grid {
|
|||
// tab
|
||||
self.advance_to_next_tabstop(self.cursor.pending_styles);
|
||||
}
|
||||
10 | 11 => {
|
||||
10 | 11 | 12 => {
|
||||
// 0a, newline
|
||||
// 0b, vertical tabulation
|
||||
// 0c, form feed
|
||||
self.add_newline();
|
||||
}
|
||||
13 => {
|
||||
|
|
@ -966,7 +997,7 @@ impl vte::Perform for Grid {
|
|||
}
|
||||
}
|
||||
|
||||
fn hook(&mut self, _params: &[i64], _intermediates: &[u8], _ignore: bool, _c: char) {
|
||||
fn hook(&mut self, _params: &Params, _intermediates: &[u8], _ignore: bool, _c: char) {
|
||||
// TBD
|
||||
}
|
||||
|
||||
|
|
@ -982,90 +1013,80 @@ impl vte::Perform for Grid {
|
|||
// TBD
|
||||
}
|
||||
|
||||
fn csi_dispatch(&mut self, params: &[i64], _intermediates: &[u8], _ignore: bool, c: char) {
|
||||
fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], _ignore: bool, c: char) {
|
||||
let mut params_iter = params.iter();
|
||||
let mut next_param_or = |default: u16| {
|
||||
params_iter
|
||||
.next()
|
||||
.map(|param| param[0])
|
||||
.filter(|¶m| param != 0)
|
||||
.unwrap_or(default) as usize
|
||||
};
|
||||
if c == 'm' {
|
||||
self.cursor
|
||||
.pending_styles
|
||||
.add_style_from_ansi_params(params);
|
||||
} else if c == 'C' {
|
||||
.add_style_from_ansi_params(&mut params_iter);
|
||||
} else if c == 'C' || c == 'a' {
|
||||
// move cursor forward
|
||||
let move_by = if params[0] == 0 {
|
||||
1
|
||||
} else {
|
||||
params[0] as usize
|
||||
};
|
||||
let move_by = next_param_or(1);
|
||||
self.move_cursor_forward_until_edge(move_by);
|
||||
} else if c == 'K' {
|
||||
// clear line (0 => right, 1 => left, 2 => all)
|
||||
if params[0] == 0 {
|
||||
let mut char_to_replace = EMPTY_TERMINAL_CHARACTER;
|
||||
char_to_replace.styles = self.cursor.pending_styles;
|
||||
self.replace_characters_in_line_after_cursor(char_to_replace);
|
||||
} else if params[0] == 1 {
|
||||
let mut char_to_replace = EMPTY_TERMINAL_CHARACTER;
|
||||
char_to_replace.styles = self.cursor.pending_styles;
|
||||
self.replace_characters_in_line_before_cursor(char_to_replace);
|
||||
} else if params[0] == 2 {
|
||||
self.clear_cursor_line();
|
||||
}
|
||||
if let Some(clear_type) = params_iter.next().map(|param| param[0]) {
|
||||
if clear_type == 0 {
|
||||
let mut char_to_replace = EMPTY_TERMINAL_CHARACTER;
|
||||
char_to_replace.styles = self.cursor.pending_styles;
|
||||
self.replace_characters_in_line_after_cursor(char_to_replace);
|
||||
} else if clear_type == 1 {
|
||||
let mut char_to_replace = EMPTY_TERMINAL_CHARACTER;
|
||||
char_to_replace.styles = self.cursor.pending_styles;
|
||||
self.replace_characters_in_line_before_cursor(char_to_replace);
|
||||
} else if clear_type == 2 {
|
||||
self.clear_cursor_line();
|
||||
}
|
||||
};
|
||||
} else if c == 'J' {
|
||||
// clear all (0 => below, 1 => above, 2 => all, 3 => saved)
|
||||
let mut char_to_replace = EMPTY_TERMINAL_CHARACTER;
|
||||
char_to_replace.styles = self.cursor.pending_styles;
|
||||
if params[0] == 0 {
|
||||
self.clear_all_after_cursor(char_to_replace);
|
||||
} else if params[0] == 1 {
|
||||
self.clear_all_before_cursor(char_to_replace);
|
||||
} else if params[0] == 2 {
|
||||
self.clear_all(char_to_replace);
|
||||
}
|
||||
// TODO: implement 1
|
||||
|
||||
if let Some(clear_type) = params_iter.next().map(|param| param[0]) {
|
||||
if clear_type == 0 {
|
||||
self.clear_all_after_cursor(char_to_replace);
|
||||
} else if clear_type == 1 {
|
||||
self.clear_all_before_cursor(char_to_replace);
|
||||
} else if clear_type == 2 {
|
||||
self.fill_viewport(char_to_replace);
|
||||
}
|
||||
};
|
||||
} else if c == 'H' || c == 'f' {
|
||||
// goto row/col
|
||||
// we subtract 1 from the row/column because these are 1 indexed
|
||||
// (except when they are 0, in which case they should be 1
|
||||
// don't look at me, I don't make the rules)
|
||||
let (row, col) = if params.len() == 1 {
|
||||
if params[0] == 0 {
|
||||
(0, params[0] as usize)
|
||||
} else {
|
||||
((params[0] as usize).saturating_sub(1), params[0] as usize)
|
||||
}
|
||||
} else if params[0] == 0 {
|
||||
(0, (params[1] as usize).saturating_sub(1))
|
||||
} else {
|
||||
(
|
||||
(params[0] as usize).saturating_sub(1),
|
||||
(params[1] as usize).saturating_sub(1),
|
||||
)
|
||||
};
|
||||
let row = next_param_or(1).saturating_sub(1);
|
||||
let col = next_param_or(1).saturating_sub(1);
|
||||
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
self.move_cursor_to(col, row, pad_character);
|
||||
} else if c == 'A' {
|
||||
// move cursor up until edge of screen
|
||||
let move_up_count = if params[0] == 0 { 1 } else { params[0] };
|
||||
let move_up_count = next_param_or(1);
|
||||
self.move_cursor_up(move_up_count as usize);
|
||||
} else if c == 'B' {
|
||||
} else if c == 'B' || c == 'e' {
|
||||
// move cursor down until edge of screen
|
||||
let move_down_count = if params[0] == 0 { 1 } else { params[0] };
|
||||
let move_down_count = next_param_or(1);
|
||||
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
self.move_cursor_down(move_down_count as usize, pad_character);
|
||||
} else if c == 'D' {
|
||||
let move_back_count = if params[0] == 0 {
|
||||
1
|
||||
} else {
|
||||
params[0] as usize
|
||||
};
|
||||
let move_back_count = next_param_or(1);
|
||||
self.move_cursor_back(move_back_count);
|
||||
} else if c == 'l' {
|
||||
let first_intermediate_is_questionmark = match _intermediates.get(0) {
|
||||
let first_intermediate_is_questionmark = match intermediates.get(0) {
|
||||
Some(b'?') => true,
|
||||
None => false,
|
||||
_ => false,
|
||||
};
|
||||
if first_intermediate_is_questionmark {
|
||||
match params.get(0) {
|
||||
Some(&1049) => {
|
||||
match params_iter.next().map(|param| param[0]) {
|
||||
Some(1049) => {
|
||||
if let Some((
|
||||
alternative_lines_above,
|
||||
alternative_viewport,
|
||||
|
|
@ -1081,44 +1102,44 @@ impl vte::Perform for Grid {
|
|||
self.change_size(self.height, self.width); // the alternative_viewport might have been of a different size...
|
||||
self.mark_for_rerender();
|
||||
}
|
||||
Some(&25) => {
|
||||
Some(25) => {
|
||||
self.hide_cursor();
|
||||
self.mark_for_rerender();
|
||||
}
|
||||
Some(&1) => {
|
||||
Some(1) => {
|
||||
self.cursor_key_mode = false;
|
||||
}
|
||||
Some(&3) => {
|
||||
Some(3) => {
|
||||
// DECCOLM - only side effects
|
||||
self.scroll_region = None;
|
||||
self.clear_all(EMPTY_TERMINAL_CHARACTER);
|
||||
self.cursor.x = 0;
|
||||
self.cursor.y = 0;
|
||||
}
|
||||
Some(&6) => {
|
||||
Some(6) => {
|
||||
self.erasure_mode = false;
|
||||
}
|
||||
Some(&7) => {
|
||||
Some(7) => {
|
||||
self.disable_linewrap = true;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
} else if let Some(&4) = params.get(0) {
|
||||
} else if let Some(4) = params_iter.next().map(|param| param[0]) {
|
||||
self.insert_mode = false;
|
||||
}
|
||||
} else if c == 'h' {
|
||||
let first_intermediate_is_questionmark = match _intermediates.get(0) {
|
||||
let first_intermediate_is_questionmark = match intermediates.get(0) {
|
||||
Some(b'?') => true,
|
||||
None => false,
|
||||
_ => false,
|
||||
};
|
||||
if first_intermediate_is_questionmark {
|
||||
match params.get(0) {
|
||||
Some(&25) => {
|
||||
match params_iter.next().map(|param| param[0]) {
|
||||
Some(25) => {
|
||||
self.show_cursor();
|
||||
self.mark_for_rerender();
|
||||
}
|
||||
Some(&1049) => {
|
||||
Some(1049) => {
|
||||
let current_lines_above = std::mem::replace(
|
||||
&mut self.lines_above,
|
||||
VecDeque::with_capacity(SCROLL_BACK),
|
||||
|
|
@ -1130,99 +1151,75 @@ impl vte::Perform for Grid {
|
|||
Some((current_lines_above, current_viewport, current_cursor));
|
||||
self.clear_viewport_before_rendering = true;
|
||||
}
|
||||
Some(&1) => {
|
||||
Some(1) => {
|
||||
self.cursor_key_mode = true;
|
||||
}
|
||||
Some(&3) => {
|
||||
Some(3) => {
|
||||
// DECCOLM - only side effects
|
||||
self.scroll_region = None;
|
||||
self.clear_all(EMPTY_TERMINAL_CHARACTER);
|
||||
self.cursor.x = 0;
|
||||
self.cursor.y = 0;
|
||||
}
|
||||
Some(&6) => {
|
||||
Some(6) => {
|
||||
self.erasure_mode = true;
|
||||
}
|
||||
Some(&7) => {
|
||||
Some(7) => {
|
||||
self.disable_linewrap = false;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
} else if let Some(&4) = params.get(0) {
|
||||
} else if let Some(4) = params_iter.next().map(|param| param[0]) {
|
||||
self.insert_mode = true;
|
||||
}
|
||||
} else if c == 'r' {
|
||||
if params.len() > 1 {
|
||||
// minus 1 because these are 1 indexed
|
||||
let top_line_index = (params[0] as usize).saturating_sub(1);
|
||||
let bottom_line_index = (params[1] as usize).saturating_sub(1);
|
||||
self.set_scroll_region(top_line_index, bottom_line_index);
|
||||
let top = (next_param_or(1) as usize).saturating_sub(1);
|
||||
let bottom = params_iter
|
||||
.next()
|
||||
.map(|param| param[0] as usize)
|
||||
.filter(|¶m| param != 0)
|
||||
.map(|bottom| bottom.saturating_sub(1));
|
||||
self.set_scroll_region(top, bottom);
|
||||
if self.erasure_mode {
|
||||
self.move_cursor_to_line(top_line_index, EMPTY_TERMINAL_CHARACTER);
|
||||
self.move_cursor_to_line(top, EMPTY_TERMINAL_CHARACTER);
|
||||
self.move_cursor_to_beginning_of_line();
|
||||
}
|
||||
self.show_cursor();
|
||||
} else {
|
||||
self.clear_scroll_region();
|
||||
}
|
||||
} else if c == 'M' {
|
||||
// delete lines if currently inside scroll region
|
||||
let line_count_to_delete = if params[0] == 0 {
|
||||
1
|
||||
} else {
|
||||
params[0] as usize
|
||||
};
|
||||
let line_count_to_delete = next_param_or(1);
|
||||
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
self.delete_lines_in_scroll_region(line_count_to_delete, pad_character);
|
||||
} else if c == 'L' {
|
||||
// insert blank lines if inside scroll region
|
||||
let line_count_to_add = if params[0] == 0 {
|
||||
1
|
||||
} else {
|
||||
params[0] as usize
|
||||
};
|
||||
let line_count_to_add = next_param_or(1);
|
||||
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
self.add_empty_lines_in_scroll_region(line_count_to_add, pad_character);
|
||||
} else if c == 'q' {
|
||||
// ignore for now to run on mac
|
||||
} else if c == 'G' {
|
||||
let column = if params[0] == 0 {
|
||||
0
|
||||
} else {
|
||||
params[0] as usize - 1
|
||||
};
|
||||
} else if c == 'G' || c == '`' {
|
||||
let column = next_param_or(1).saturating_sub(1);
|
||||
self.move_cursor_to_column(column);
|
||||
} else if c == 'g' {
|
||||
if params[0] == 0 {
|
||||
let clear_type = next_param_or(0);
|
||||
if clear_type == 0 {
|
||||
self.clear_tabstop(self.cursor.x);
|
||||
} else if params[0] == 3 {
|
||||
} else if clear_type == 3 {
|
||||
self.clear_all_tabstops();
|
||||
}
|
||||
} else if c == 'd' {
|
||||
// goto line
|
||||
let line = if params[0] == 0 {
|
||||
1
|
||||
} else {
|
||||
// minus 1 because this is 1 indexed
|
||||
params[0] as usize - 1
|
||||
};
|
||||
let line = next_param_or(1).saturating_sub(1);
|
||||
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
self.move_cursor_to_line(line, pad_character);
|
||||
} else if c == 'P' {
|
||||
// erase characters
|
||||
let count = if params[0] == 0 {
|
||||
1
|
||||
} else {
|
||||
params[0] as usize
|
||||
};
|
||||
let count = next_param_or(1);
|
||||
self.erase_characters(count, self.cursor.pending_styles);
|
||||
} else if c == 'X' {
|
||||
// erase characters and replace with empty characters of current style
|
||||
let count = if params[0] == 0 {
|
||||
1
|
||||
} else {
|
||||
params[0] as usize
|
||||
};
|
||||
let count = next_param_or(1);
|
||||
self.replace_with_empty_chars(count, self.cursor.pending_styles);
|
||||
} else if c == 'T' {
|
||||
/*
|
||||
|
|
@ -1230,38 +1227,66 @@ impl vte::Perform for Grid {
|
|||
* Scroll down, new lines inserted at top of screen
|
||||
* [4T = Scroll down 4, bring previous lines back into view
|
||||
*/
|
||||
let line_count: i64 = *params.get(0).expect("A number of lines was expected.");
|
||||
|
||||
if line_count >= 0 {
|
||||
self.rotate_scroll_region_up(line_count as usize);
|
||||
} else {
|
||||
// TODO: can this actually happen?
|
||||
self.rotate_scroll_region_down(line_count.abs() as usize);
|
||||
}
|
||||
let line_count = next_param_or(1);
|
||||
self.rotate_scroll_region_up(line_count as usize);
|
||||
} else if c == 'S' {
|
||||
// move scroll up
|
||||
let count = if params[0] == 0 {
|
||||
1
|
||||
} else {
|
||||
params[0] as usize
|
||||
};
|
||||
let count = next_param_or(1);
|
||||
self.rotate_scroll_region_down(count);
|
||||
} else if c == 's' {
|
||||
self.save_cursor_position();
|
||||
} else if c == 'u' {
|
||||
self.restore_cursor_position();
|
||||
} else if c == '@' {
|
||||
let count = if params[0] == 0 {
|
||||
1
|
||||
} else {
|
||||
params[0] as usize
|
||||
};
|
||||
let count = next_param_or(1);
|
||||
for _ in 0..count {
|
||||
// TODO: should this be styled?
|
||||
self.insert_character_at_cursor_position(EMPTY_TERMINAL_CHARACTER);
|
||||
}
|
||||
} else if c == 'b' {
|
||||
if let Some(c) = self.preceding_char {
|
||||
for _ in 0..next_param_or(1) {
|
||||
self.add_character(c);
|
||||
}
|
||||
}
|
||||
} else if c == 'E' {
|
||||
let count = next_param_or(1);
|
||||
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
self.move_cursor_down(count, pad_character);
|
||||
} else if c == 'F' {
|
||||
let count = next_param_or(1);
|
||||
self.move_cursor_up(count);
|
||||
self.move_cursor_to_beginning_of_line();
|
||||
} else if c == 'I' {
|
||||
for _ in 0..next_param_or(1) {
|
||||
self.advance_to_next_tabstop(self.cursor.pending_styles);
|
||||
}
|
||||
} else if c == 'q' {
|
||||
let first_intermediate_is_space = matches!(intermediates.get(0), Some(b' '));
|
||||
if first_intermediate_is_space {
|
||||
// DECSCUSR (CSI Ps SP q) -- Set Cursor Style.
|
||||
let cursor_style_id = next_param_or(0);
|
||||
let shape = match cursor_style_id {
|
||||
0 | 2 => Some(CursorShape::Block),
|
||||
1 => Some(CursorShape::BlinkingBlock),
|
||||
3 => Some(CursorShape::BlinkingUnderline),
|
||||
4 => Some(CursorShape::Underline),
|
||||
5 => Some(CursorShape::BlinkingBeam),
|
||||
6 => Some(CursorShape::Beam),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(cursor_shape) = shape {
|
||||
self.cursor.change_shape(cursor_shape);
|
||||
}
|
||||
}
|
||||
} else if c == 'Z' {
|
||||
for _ in 0..next_param_or(1) {
|
||||
self.move_to_previous_tabstop();
|
||||
}
|
||||
} else {
|
||||
let _ = debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params));
|
||||
let result = debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params));
|
||||
#[cfg(not(test))]
|
||||
result.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1304,6 +1329,7 @@ impl vte::Perform for Grid {
|
|||
self.move_cursor_to_beginning_of_line();
|
||||
}
|
||||
(b'M', None) => {
|
||||
// TODO: if cursor is at the top, it should go down one
|
||||
self.move_cursor_up_with_scrolling(1);
|
||||
}
|
||||
(b'c', None) => {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
use crate::{common::SenderWithContext, pty_bus::VteBytes, tab::Pane, wasm_vm::PluginInstruction};
|
||||
|
||||
use crate::panes::{PaneId, PositionAndSize};
|
||||
|
||||
use std::sync::mpsc::channel;
|
||||
use std::time::Instant;
|
||||
use std::{sync::mpsc::channel, unimplemented};
|
||||
use std::unimplemented;
|
||||
|
||||
use crate::common::thread_bus::SenderWithContext;
|
||||
use crate::panes::{PaneId, PositionAndSize};
|
||||
use crate::pty::VteBytes;
|
||||
use crate::tab::Pane;
|
||||
use crate::wasm_vm::PluginInstruction;
|
||||
|
||||
pub struct PluginPane {
|
||||
pub pid: u32,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use crate::utils::logging::debug_log_to_file;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::ops::{Index, IndexMut};
|
||||
use vte::ParamsIter;
|
||||
|
||||
pub const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter {
|
||||
character: ' ',
|
||||
|
|
@ -327,138 +329,123 @@ impl CharacterStyles {
|
|||
self.hidden = Some(AnsiCode::Reset);
|
||||
self.strike = Some(AnsiCode::Reset);
|
||||
}
|
||||
pub fn add_style_from_ansi_params(&mut self, ansi_params: &[i64]) {
|
||||
let mut params_used = 1; // if there's a parameter, it is always used
|
||||
match ansi_params {
|
||||
[] | [0, ..] => self.reset_all(),
|
||||
[1, ..] => *self = self.bold(Some(AnsiCode::On)),
|
||||
[2, ..] => *self = self.dim(Some(AnsiCode::On)),
|
||||
[3, ..] => *self = self.italic(Some(AnsiCode::On)),
|
||||
[4, ..] => *self = self.underline(Some(AnsiCode::On)),
|
||||
[5, ..] => *self = self.blink_slow(Some(AnsiCode::On)),
|
||||
[6, ..] => *self = self.blink_fast(Some(AnsiCode::On)),
|
||||
[7, ..] => *self = self.reverse(Some(AnsiCode::On)),
|
||||
[8, ..] => *self = self.hidden(Some(AnsiCode::On)),
|
||||
[9, ..] => *self = self.strike(Some(AnsiCode::On)),
|
||||
[21, ..] => *self = self.bold(Some(AnsiCode::Reset)),
|
||||
[22, ..] => {
|
||||
*self = self.bold(Some(AnsiCode::Reset));
|
||||
*self = self.dim(Some(AnsiCode::Reset));
|
||||
}
|
||||
[23, ..] => *self = self.italic(Some(AnsiCode::Reset)),
|
||||
[24, ..] => *self = self.underline(Some(AnsiCode::Reset)),
|
||||
[25, ..] => {
|
||||
*self = self.blink_slow(Some(AnsiCode::Reset));
|
||||
*self = self.blink_fast(Some(AnsiCode::Reset));
|
||||
}
|
||||
[27, ..] => *self = self.reverse(Some(AnsiCode::Reset)),
|
||||
[28, ..] => *self = self.hidden(Some(AnsiCode::Reset)),
|
||||
[29, ..] => *self = self.strike(Some(AnsiCode::Reset)),
|
||||
[30, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Black))),
|
||||
[31, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Red))),
|
||||
[32, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Green))),
|
||||
[33, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Yellow))),
|
||||
[34, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Blue))),
|
||||
[35, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Magenta))),
|
||||
[36, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Cyan))),
|
||||
[37, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::White))),
|
||||
[38, 2, ..] => {
|
||||
let ansi_code = AnsiCode::RgbCode((
|
||||
*ansi_params.get(2).unwrap() as u8,
|
||||
*ansi_params.get(3).unwrap() as u8,
|
||||
*ansi_params.get(4).unwrap() as u8,
|
||||
));
|
||||
*self = self.foreground(Some(ansi_code));
|
||||
params_used += 4 // one for the indicator (2 in this case) and three for the rgb code
|
||||
}
|
||||
[38, 5, ..] => {
|
||||
let ansi_code = AnsiCode::ColorIndex(*ansi_params.get(2).unwrap() as u8);
|
||||
*self = self.foreground(Some(ansi_code));
|
||||
params_used += 2 // one for the indicator (5 in this case) and one for the color index
|
||||
}
|
||||
[38, ..] => {
|
||||
// this is a bug
|
||||
// it means we got a color encoding we don't know how to handle (or is invalid)
|
||||
params_used += 1; // even if it's a bug, let's not create an endless loop, eh?
|
||||
}
|
||||
[39, ..] => *self = self.foreground(Some(AnsiCode::Reset)),
|
||||
[40, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Black))),
|
||||
[41, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Red))),
|
||||
[42, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Green))),
|
||||
[43, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Yellow))),
|
||||
[44, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Blue))),
|
||||
[45, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Magenta))),
|
||||
[46, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Cyan))),
|
||||
[47, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::White))),
|
||||
[48, 2, ..] => {
|
||||
let ansi_code = AnsiCode::RgbCode((
|
||||
*ansi_params.get(2).unwrap() as u8,
|
||||
*ansi_params.get(3).unwrap() as u8,
|
||||
*ansi_params.get(4).unwrap() as u8,
|
||||
));
|
||||
*self = self.background(Some(ansi_code));
|
||||
params_used += 4 // one for the indicator (2 in this case) and three for the rgb code
|
||||
}
|
||||
[48, 5, ..] => {
|
||||
let ansi_code = AnsiCode::ColorIndex(*ansi_params.get(2).unwrap() as u8);
|
||||
*self = self.background(Some(ansi_code));
|
||||
params_used += 2 // one for the indicator (5 in this case) and one for the color index
|
||||
}
|
||||
[48, ..] => {
|
||||
// this is a bug
|
||||
// it means we got a color encoding we don't know how to handle (or is invalid)
|
||||
params_used += 1; // even if it's a bug, let's not create an endless loop, eh?
|
||||
}
|
||||
[49, ..] => *self = self.background(Some(AnsiCode::Reset)),
|
||||
[90, ..] => {
|
||||
*self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightBlack)))
|
||||
}
|
||||
[91, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightRed))),
|
||||
[92, ..] => {
|
||||
*self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightGreen)))
|
||||
}
|
||||
[93, ..] => {
|
||||
*self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightYellow)))
|
||||
}
|
||||
[94, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightBlue))),
|
||||
[95, ..] => {
|
||||
*self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightMagenta)))
|
||||
}
|
||||
[96, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightCyan))),
|
||||
[97, ..] => {
|
||||
*self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightWhite)))
|
||||
}
|
||||
[100, ..] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightBlack)))
|
||||
}
|
||||
[101, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightRed))),
|
||||
[102, ..] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightGreen)))
|
||||
}
|
||||
[103, ..] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightYellow)))
|
||||
}
|
||||
[104, ..] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightBlue)))
|
||||
}
|
||||
[105, ..] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightMagenta)))
|
||||
}
|
||||
[106, ..] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightCyan)))
|
||||
}
|
||||
[107, ..] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightWhite)))
|
||||
}
|
||||
_ => {
|
||||
// if this happens, it's a bug
|
||||
let _ = debug_log_to_file(format!("unhandled csi m code {:?}", ansi_params));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let Some(next_params) = ansi_params.get(params_used..) {
|
||||
if !next_params.is_empty() {
|
||||
self.add_style_from_ansi_params(next_params);
|
||||
pub fn add_style_from_ansi_params(&mut self, params: &mut ParamsIter) {
|
||||
while let Some(param) = params.next() {
|
||||
match param {
|
||||
[] | [0] => self.reset_all(),
|
||||
[1] => *self = self.bold(Some(AnsiCode::On)),
|
||||
[2] => *self = self.dim(Some(AnsiCode::On)),
|
||||
[3] => *self = self.italic(Some(AnsiCode::On)),
|
||||
[4] => *self = self.underline(Some(AnsiCode::On)),
|
||||
[5] => *self = self.blink_slow(Some(AnsiCode::On)),
|
||||
[6] => *self = self.blink_fast(Some(AnsiCode::On)),
|
||||
[7] => *self = self.reverse(Some(AnsiCode::On)),
|
||||
[8] => *self = self.hidden(Some(AnsiCode::On)),
|
||||
[9] => *self = self.strike(Some(AnsiCode::On)),
|
||||
[21] => *self = self.bold(Some(AnsiCode::Reset)),
|
||||
[22] => {
|
||||
*self = self.bold(Some(AnsiCode::Reset));
|
||||
*self = self.dim(Some(AnsiCode::Reset));
|
||||
}
|
||||
[23] => *self = self.italic(Some(AnsiCode::Reset)),
|
||||
[24] => *self = self.underline(Some(AnsiCode::Reset)),
|
||||
[25] => {
|
||||
*self = self.blink_slow(Some(AnsiCode::Reset));
|
||||
*self = self.blink_fast(Some(AnsiCode::Reset));
|
||||
}
|
||||
[27] => *self = self.reverse(Some(AnsiCode::Reset)),
|
||||
[28] => *self = self.hidden(Some(AnsiCode::Reset)),
|
||||
[29] => *self = self.strike(Some(AnsiCode::Reset)),
|
||||
[30] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Black))),
|
||||
[31] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Red))),
|
||||
[32] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Green))),
|
||||
[33] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Yellow))),
|
||||
[34] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Blue))),
|
||||
[35] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Magenta))),
|
||||
[36] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Cyan))),
|
||||
[37] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::White))),
|
||||
[38] => {
|
||||
let mut iter = params.map(|param| param[0]);
|
||||
if let Some(ansi_code) = parse_sgr_color(&mut iter) {
|
||||
*self = self.foreground(Some(ansi_code));
|
||||
}
|
||||
}
|
||||
[38, params @ ..] => {
|
||||
let rgb_start = if params.len() > 4 { 2 } else { 1 };
|
||||
let rgb_iter = params[rgb_start..].iter().copied();
|
||||
let mut iter = std::iter::once(params[0]).chain(rgb_iter);
|
||||
if let Some(ansi_code) = parse_sgr_color(&mut iter) {
|
||||
*self = self.foreground(Some(ansi_code));
|
||||
}
|
||||
}
|
||||
[39] => *self = self.foreground(Some(AnsiCode::Reset)),
|
||||
[40] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Black))),
|
||||
[41] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Red))),
|
||||
[42] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Green))),
|
||||
[43] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Yellow))),
|
||||
[44] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Blue))),
|
||||
[45] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Magenta))),
|
||||
[46] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Cyan))),
|
||||
[47] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::White))),
|
||||
[48] => {
|
||||
let mut iter = params.map(|param| param[0]);
|
||||
if let Some(ansi_code) = parse_sgr_color(&mut iter) {
|
||||
*self = self.background(Some(ansi_code));
|
||||
}
|
||||
}
|
||||
[48, params @ ..] => {
|
||||
let rgb_start = if params.len() > 4 { 2 } else { 1 };
|
||||
let rgb_iter = params[rgb_start..].iter().copied();
|
||||
let mut iter = std::iter::once(params[0]).chain(rgb_iter);
|
||||
if let Some(ansi_code) = parse_sgr_color(&mut iter) {
|
||||
*self = self.background(Some(ansi_code));
|
||||
}
|
||||
}
|
||||
[49] => *self = self.background(Some(AnsiCode::Reset)),
|
||||
[90] => {
|
||||
*self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightBlack)))
|
||||
}
|
||||
[91] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightRed))),
|
||||
[92] => {
|
||||
*self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightGreen)))
|
||||
}
|
||||
[93] => {
|
||||
*self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightYellow)))
|
||||
}
|
||||
[94] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightBlue))),
|
||||
[95] => {
|
||||
*self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightMagenta)))
|
||||
}
|
||||
[96] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightCyan))),
|
||||
[97] => {
|
||||
*self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightWhite)))
|
||||
}
|
||||
[100] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightBlack)))
|
||||
}
|
||||
[101] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightRed))),
|
||||
[102] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightGreen)))
|
||||
}
|
||||
[103] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightYellow)))
|
||||
}
|
||||
[104] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightBlue)))
|
||||
}
|
||||
[105] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightMagenta)))
|
||||
}
|
||||
[106] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightCyan)))
|
||||
}
|
||||
[107] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightWhite)))
|
||||
}
|
||||
_ => {
|
||||
let _ = debug_log_to_file(format!("unhandled csi m code {:?}", param));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -714,6 +701,16 @@ impl IndexMut<CharsetIndex> for Charsets {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum CursorShape {
|
||||
Block,
|
||||
BlinkingBlock,
|
||||
Underline,
|
||||
BlinkingUnderline,
|
||||
Beam,
|
||||
BlinkingBeam,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Cursor {
|
||||
pub x: usize,
|
||||
|
|
@ -721,6 +718,7 @@ pub struct Cursor {
|
|||
pub is_hidden: bool,
|
||||
pub pending_styles: CharacterStyles,
|
||||
pub charsets: Charsets,
|
||||
shape: CursorShape,
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
|
|
@ -731,8 +729,15 @@ impl Cursor {
|
|||
is_hidden: false,
|
||||
pending_styles: CharacterStyles::new(),
|
||||
charsets: Default::default(),
|
||||
shape: CursorShape::Block,
|
||||
}
|
||||
}
|
||||
pub fn change_shape(&mut self, shape: CursorShape) {
|
||||
self.shape = shape;
|
||||
}
|
||||
pub fn get_shape(&self) -> CursorShape {
|
||||
self.shape
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
|
@ -756,3 +761,15 @@ impl TerminalCharacter {
|
|||
self.width() > 1
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_sgr_color(params: &mut dyn Iterator<Item = u16>) -> Option<AnsiCode> {
|
||||
match params.next() {
|
||||
Some(2) => Some(AnsiCode::RgbCode((
|
||||
u8::try_from(params.next()?).ok()?,
|
||||
u8::try_from(params.next()?).ok()?,
|
||||
u8::try_from(params.next()?).ok()?,
|
||||
))),
|
||||
Some(5) => Some(AnsiCode::ColorIndex(u8::try_from(params.next()?).ok()?)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
use crate::tab::Pane;
|
||||
use ::nix::pty::Winsize;
|
||||
use ::std::os::unix::io::RawFd;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::time::Instant;
|
||||
|
||||
use nix::pty::Winsize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::panes::grid::Grid;
|
||||
use crate::panes::terminal_character::{
|
||||
CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
|
||||
CharacterStyles, CursorShape, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
|
||||
};
|
||||
use crate::pty_bus::VteBytes;
|
||||
use crate::pty::VteBytes;
|
||||
use crate::tab::Pane;
|
||||
|
||||
#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub enum PaneId {
|
||||
|
|
@ -290,6 +291,16 @@ impl Pane for TerminalPane {
|
|||
fn set_active_at(&mut self, time: Instant) {
|
||||
self.active_at = time;
|
||||
}
|
||||
fn cursor_shape_csi(&self) -> String {
|
||||
match self.grid.cursor_shape() {
|
||||
CursorShape::Block => "\u{1b}[0 q".to_string(),
|
||||
CursorShape::BlinkingBlock => "\u{1b}[1 q".to_string(),
|
||||
CursorShape::Underline => "\u{1b}[4 q".to_string(),
|
||||
CursorShape::BlinkingUnderline => "\u{1b}[3 q".to_string(),
|
||||
CursorShape::Beam => "\u{1b}[6 q".to_string(),
|
||||
CursorShape::BlinkingBeam => "\u{1b}[5 q".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TerminalPane {
|
||||
|
|
|
|||
|
|
@ -347,3 +347,39 @@ fn vttest8_5() {
|
|||
}
|
||||
assert_snapshot!(format!("{:?}", grid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn csi_b() {
|
||||
let mut vte_parser = vte::Parser::new();
|
||||
let mut grid = Grid::new(51, 97);
|
||||
let fixture_name = "csi-b";
|
||||
let content = read_fixture(fixture_name);
|
||||
for byte in content {
|
||||
vte_parser.advance(&mut grid, byte);
|
||||
}
|
||||
assert_snapshot!(format!("{:?}", grid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn csi_capital_i() {
|
||||
let mut vte_parser = vte::Parser::new();
|
||||
let mut grid = Grid::new(51, 97);
|
||||
let fixture_name = "csi-capital-i";
|
||||
let content = read_fixture(fixture_name);
|
||||
for byte in content {
|
||||
vte_parser.advance(&mut grid, byte);
|
||||
}
|
||||
assert_snapshot!(format!("{:?}", grid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn csi_capital_z() {
|
||||
let mut vte_parser = vte::Parser::new();
|
||||
let mut grid = Grid::new(51, 97);
|
||||
let fixture_name = "csi-capital-z";
|
||||
let content = read_fixture(fixture_name);
|
||||
for byte in content {
|
||||
vte_parser.advance(&mut grid, byte);
|
||||
}
|
||||
assert_snapshot!(format!("{:?}", grid));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
source: src/client/panes/./unit/grid_tests.rs
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): ffffff
|
||||
01 (C):
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
source: src/client/panes/./unit/grid_tests.rs
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): foo
|
||||
01 (C):
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
source: src/client/panes/./unit/grid_tests.rs
|
||||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (C): 12345678foo234567890
|
||||
01 (C):
|
||||
|
||||
|
|
@ -25,4 +25,23 @@ expression: "format!(\"{:?}\", grid)"
|
|||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -25,4 +25,23 @@ expression: "format!(\"{:?}\", grid)"
|
|||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -3,28 +3,45 @@ source: src/client/panes/./unit/grid_tests.rs
|
|||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (W):
|
||||
01 (W):
|
||||
02 (W):
|
||||
03 (W):
|
||||
04 (W):
|
||||
05 (W):
|
||||
06 (W):
|
||||
07 (W):
|
||||
08 (W):
|
||||
09 (W):
|
||||
10 (W):
|
||||
11 (W):
|
||||
12 (W):
|
||||
13 (W):
|
||||
14 (W):
|
||||
15 (W):
|
||||
16 (W):
|
||||
17 (W):
|
||||
18 (W):
|
||||
19 (W):
|
||||
20 (W):
|
||||
21 (W):
|
||||
22 (W): This line should be the one above the bottom of the screen. Push <RETURN>
|
||||
23 (W): Origin mode test. This line should be at the bottom of the screen.
|
||||
00 (C):
|
||||
01 (C):
|
||||
02 (C):
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C): This line should be the one above the bottom of the screen. Push <RETURN>
|
||||
23 (C): Origin mode test. This line should be at the bottom of the screen.
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -3,28 +3,45 @@ source: src/client/panes/./unit/grid_tests.rs
|
|||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (W): This line should be at the top of the screen. Push <RETURN>
|
||||
01 (W):
|
||||
02 (W):
|
||||
03 (W):
|
||||
04 (W):
|
||||
05 (W):
|
||||
06 (W):
|
||||
07 (W):
|
||||
08 (W):
|
||||
09 (W):
|
||||
10 (W):
|
||||
11 (W):
|
||||
12 (W):
|
||||
13 (W):
|
||||
14 (W):
|
||||
15 (W):
|
||||
16 (W):
|
||||
17 (W):
|
||||
18 (W):
|
||||
19 (W):
|
||||
20 (W):
|
||||
21 (W):
|
||||
22 (W):
|
||||
23 (W): Origin mode test. This line should be at the bottom of the screen.
|
||||
00 (C): This line should be at the top of the screen. Push <RETURN>
|
||||
01 (C):
|
||||
02 (C):
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (C):
|
||||
12 (C):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C): Origin mode test. This line should be at the bottom of the screen.
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -3,28 +3,45 @@ source: src/client/panes/./unit/grid_tests.rs
|
|||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (W): Graphic rendition test pattern:
|
||||
01 (W):
|
||||
02 (W):
|
||||
03 (W): vanilla bold
|
||||
04 (W):
|
||||
05 (W): underline bold underline
|
||||
06 (W):
|
||||
07 (W): blink bold blink
|
||||
08 (W):
|
||||
09 (W): underline blink bold underline blink
|
||||
10 (W):
|
||||
11 (W): negative bold negative
|
||||
12 (W):
|
||||
13 (W): underline negative bold underline negative
|
||||
14 (W):
|
||||
15 (W): blink negative bold blink negative
|
||||
16 (W):
|
||||
17 (W): underline blink negative bold underline blink negative
|
||||
18 (W):
|
||||
19 (W):
|
||||
20 (W):
|
||||
21 (W):
|
||||
22 (W): Dark background. Push <RETURN>
|
||||
23 (W):
|
||||
00 (C): Graphic rendition test pattern:
|
||||
01 (C):
|
||||
02 (C):
|
||||
03 (C): vanilla bold
|
||||
04 (C):
|
||||
05 (C): underline bold underline
|
||||
06 (C):
|
||||
07 (C): blink bold blink
|
||||
08 (C):
|
||||
09 (C): underline blink bold underline blink
|
||||
10 (C):
|
||||
11 (C): negative bold negative
|
||||
12 (C):
|
||||
13 (C): underline negative bold underline negative
|
||||
14 (C):
|
||||
15 (C): blink negative bold blink negative
|
||||
16 (C):
|
||||
17 (C): underline blink negative bold underline blink negative
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C): Dark background. Push <RETURN>
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -3,28 +3,45 @@ source: src/client/panes/./unit/grid_tests.rs
|
|||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (W): Graphic rendition test pattern:
|
||||
01 (W):
|
||||
02 (W):
|
||||
03 (W): vanilla bold
|
||||
04 (W):
|
||||
05 (W): underline bold underline
|
||||
06 (W):
|
||||
07 (W): blink bold blink
|
||||
08 (W):
|
||||
09 (W): underline blink bold underline blink
|
||||
10 (W):
|
||||
11 (W): negative bold negative
|
||||
12 (W):
|
||||
13 (W): underline negative bold underline negative
|
||||
14 (W):
|
||||
15 (W): blink negative bold blink negative
|
||||
16 (W):
|
||||
17 (W): underline blink negative bold underline blink negative
|
||||
18 (W):
|
||||
19 (W):
|
||||
20 (W):
|
||||
21 (W):
|
||||
22 (W): Light background. Push <RETURN>
|
||||
23 (W):
|
||||
00 (C): Graphic rendition test pattern:
|
||||
01 (C):
|
||||
02 (C):
|
||||
03 (C): vanilla bold
|
||||
04 (C):
|
||||
05 (C): underline bold underline
|
||||
06 (C):
|
||||
07 (C): blink bold blink
|
||||
08 (C):
|
||||
09 (C): underline blink bold underline blink
|
||||
10 (C):
|
||||
11 (C): negative bold negative
|
||||
12 (C):
|
||||
13 (C): underline negative bold underline negative
|
||||
14 (C):
|
||||
15 (C): blink negative bold blink negative
|
||||
16 (C):
|
||||
17 (C): underline blink negative bold underline blink negative
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C): Light background. Push <RETURN>
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -3,28 +3,45 @@ source: src/client/panes/./unit/grid_tests.rs
|
|||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (W): AAAAA
|
||||
01 (W): AAAAA
|
||||
02 (W): AAAAA
|
||||
03 (W): AAAAA
|
||||
04 (W):
|
||||
05 (W):
|
||||
06 (W):
|
||||
07 (W): normal bold underscored blinking reversed
|
||||
08 (W):
|
||||
09 (W): stars: ********** ********** ********** ********** **********
|
||||
10 (W):
|
||||
11 (W): line: ────────── ────────── ────────── ────────── ──────────
|
||||
12 (W):
|
||||
13 (W): x'es: xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx
|
||||
14 (W):
|
||||
15 (W): diamonds: ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆
|
||||
16 (W):
|
||||
17 (W):
|
||||
18 (W):
|
||||
19 (W):
|
||||
20 (W): Test of the SAVE/RESTORE CURSOR feature. There should
|
||||
21 (W): be ten characters of each flavour, and a rectangle
|
||||
22 (W): of 5 x 4 A's filling the top left of the screen.
|
||||
23 (W): Push <RETURN>
|
||||
00 (C): AAAAA
|
||||
01 (C): AAAAA
|
||||
02 (C): AAAAA
|
||||
03 (C): AAAAA
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C): normal bold underscored blinking reversed
|
||||
08 (C):
|
||||
09 (C): stars: ********** ********** ********** ********** **********
|
||||
10 (C):
|
||||
11 (C): line: ────────── ────────── ────────── ────────── ──────────
|
||||
12 (C):
|
||||
13 (C): x'es: xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx
|
||||
14 (C):
|
||||
15 (C): diamonds: ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C): Test of the SAVE/RESTORE CURSOR feature. There should
|
||||
21 (C): be ten characters of each flavour, and a rectangle
|
||||
22 (C): of 5 x 4 A's filling the top left of the screen.
|
||||
23 (C): Push <RETURN>
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -25,4 +25,23 @@ expression: "format!(\"{:?}\", grid)"
|
|||
19 (C): This is 132 column mode, light background.Push <RETURN>
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -25,4 +25,23 @@ expression: "format!(\"{:?}\", grid)"
|
|||
19 (C): This is 80 column mode, light background.Push <RETURN>
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -25,4 +25,23 @@ expression: "format!(\"{:?}\", grid)"
|
|||
19 (C): This is 132 column mode, dark background.Push <RETURN>
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -25,4 +25,23 @@ expression: "format!(\"{:?}\", grid)"
|
|||
19 (C): This is 80 column mode, dark background.Push <RETURN>
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -25,4 +25,23 @@ expression: "format!(\"{:?}\", grid)"
|
|||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -27,4 +27,21 @@ expression: "format!(\"{:?}\", grid)"
|
|||
21 (W): Soft scroll down region [1..24] size 24 Line 9
|
||||
22 (W): Soft scroll down region [1..24] size 24 Line 8
|
||||
23 (W): Soft scroll down region [1..24] size 24 Line 7
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -3,28 +3,45 @@ source: src/client/panes/./unit/grid_tests.rs
|
|||
expression: "format!(\"{:?}\", grid)"
|
||||
|
||||
---
|
||||
00 (W):
|
||||
01 (W):
|
||||
02 (W):
|
||||
03 (W):
|
||||
04 (W):
|
||||
05 (W):
|
||||
06 (W):
|
||||
07 (W):
|
||||
08 (W):
|
||||
09 (W):
|
||||
10 (W):
|
||||
00 (C):
|
||||
01 (C):
|
||||
02 (C):
|
||||
03 (C):
|
||||
04 (C):
|
||||
05 (C):
|
||||
06 (C):
|
||||
07 (C):
|
||||
08 (C):
|
||||
09 (C):
|
||||
10 (C):
|
||||
11 (W): Push <RETURN>
|
||||
12 (W): Jump scroll down region [12..13] size 2 Line 29
|
||||
13 (W):
|
||||
14 (W):
|
||||
15 (W):
|
||||
16 (W):
|
||||
17 (W):
|
||||
18 (W):
|
||||
19 (W):
|
||||
20 (W):
|
||||
21 (W):
|
||||
22 (W):
|
||||
23 (W):
|
||||
13 (C):
|
||||
14 (C):
|
||||
15 (C):
|
||||
16 (C):
|
||||
17 (C):
|
||||
18 (C):
|
||||
19 (C):
|
||||
20 (C):
|
||||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -27,4 +27,21 @@ expression: "format!(\"{:?}\", grid)"
|
|||
21 (W): Jump scroll down region [1..24] size 24 Line 9
|
||||
22 (W): Jump scroll down region [1..24] size 24 Line 8
|
||||
23 (W): Jump scroll down region [1..24] size 24 Line 7
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -27,4 +27,21 @@ expression: "format!(\"{:?}\", grid)"
|
|||
21 (C): `abcdefghijklmnopqrstuvwxyz{|}~ `abcdefghijklmnopqrstuvwxyz{|}~
|
||||
22 (C):
|
||||
23 (C): These are the installed character sets. Push <RETURN>
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -27,4 +27,31 @@ expression: "format!(\"{:?}\", grid)"
|
|||
21 (C):
|
||||
22 (C):
|
||||
23 (C): XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
41 (C):
|
||||
42 (C):
|
||||
43 (C):
|
||||
44 (C):
|
||||
45 (C):
|
||||
46 (C):
|
||||
47 (C):
|
||||
48 (C):
|
||||
49 (C):
|
||||
50 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -27,4 +27,31 @@ expression: "format!(\"{:?}\", grid)"
|
|||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
41 (C):
|
||||
42 (C):
|
||||
43 (C):
|
||||
44 (C):
|
||||
45 (C):
|
||||
46 (C):
|
||||
47 (C):
|
||||
48 (C):
|
||||
49 (C):
|
||||
50 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -27,4 +27,31 @@ expression: "format!(\"{:?}\", grid)"
|
|||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
41 (C):
|
||||
42 (C):
|
||||
43 (C):
|
||||
44 (C):
|
||||
45 (C):
|
||||
46 (C):
|
||||
47 (C):
|
||||
48 (C):
|
||||
49 (C):
|
||||
50 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -27,4 +27,31 @@ expression: "format!(\"{:?}\", grid)"
|
|||
21 (C): VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
||||
22 (C): WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW
|
||||
23 (C): XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
41 (C):
|
||||
42 (C):
|
||||
43 (C):
|
||||
44 (C):
|
||||
45 (C):
|
||||
46 (C):
|
||||
47 (C):
|
||||
48 (C):
|
||||
49 (C):
|
||||
50 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -27,4 +27,31 @@ expression: "format!(\"{:?}\", grid)"
|
|||
21 (C): VVVVVVVVVVVVVVVVVV
|
||||
22 (C): WWWWWWWWWWWWWWWWW
|
||||
23 (C): XXXXXXXXXXXXXXXX
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
41 (C):
|
||||
42 (C):
|
||||
43 (C):
|
||||
44 (C):
|
||||
45 (C):
|
||||
46 (C):
|
||||
47 (C):
|
||||
48 (C):
|
||||
49 (C):
|
||||
50 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -27,4 +27,31 @@ expression: "format!(\"{:?}\", grid)"
|
|||
21 (C):
|
||||
22 (C):
|
||||
23 (C):
|
||||
24 (C):
|
||||
25 (C):
|
||||
26 (C):
|
||||
27 (C):
|
||||
28 (C):
|
||||
29 (C):
|
||||
30 (C):
|
||||
31 (C):
|
||||
32 (C):
|
||||
33 (C):
|
||||
34 (C):
|
||||
35 (C):
|
||||
36 (C):
|
||||
37 (C):
|
||||
38 (C):
|
||||
39 (C):
|
||||
40 (C):
|
||||
41 (C):
|
||||
42 (C):
|
||||
43 (C):
|
||||
44 (C):
|
||||
45 (C):
|
||||
46 (C):
|
||||
47 (C):
|
||||
48 (C):
|
||||
49 (C):
|
||||
50 (C):
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@
|
|||
//! as well as how they should be resized
|
||||
|
||||
use crate::client::pane_resizer::PaneResizer;
|
||||
use crate::common::{input::handler::parse_keys, SenderWithContext};
|
||||
use crate::common::input::handler::parse_keys;
|
||||
use crate::common::thread_bus::ThreadSenders;
|
||||
use crate::layout::Layout;
|
||||
use crate::os_input_output::ServerOsApi;
|
||||
use crate::panes::{PaneId, PositionAndSize, TerminalPane};
|
||||
use crate::pty_bus::{PtyInstruction, VteBytes};
|
||||
use crate::pty::{PtyInstruction, VteBytes};
|
||||
use crate::server::ServerInstruction;
|
||||
use crate::utils::shared::adjust_to_size;
|
||||
use crate::wasm_vm::PluginInstruction;
|
||||
|
|
@ -69,9 +70,7 @@ pub struct Tab {
|
|||
full_screen_ws: PositionAndSize,
|
||||
fullscreen_is_active: bool,
|
||||
os_api: Box<dyn ServerOsApi>,
|
||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||
pub senders: ThreadSenders,
|
||||
synchronize_is_active: bool,
|
||||
should_clear_display_before_rendering: bool,
|
||||
pub mode_info: ModeInfo,
|
||||
|
|
@ -133,6 +132,9 @@ pub trait Pane {
|
|||
fn clear_scroll(&mut self);
|
||||
fn active_at(&self) -> Instant;
|
||||
fn set_active_at(&mut self, instant: Instant);
|
||||
fn cursor_shape_csi(&self) -> String {
|
||||
"\u{1b}[0 q".to_string() // default to non blinking block
|
||||
}
|
||||
|
||||
fn right_boundary_x_coords(&self) -> usize {
|
||||
self.x() + self.columns()
|
||||
|
|
@ -219,7 +221,7 @@ pub trait Pane {
|
|||
}
|
||||
|
||||
impl Tab {
|
||||
// FIXME: Too many arguments here! Maybe bundle all of the senders for the whole program in a struct?
|
||||
// FIXME: Still too many arguments for clippy to be happy...
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
index: usize,
|
||||
|
|
@ -227,9 +229,7 @@ impl Tab {
|
|||
name: String,
|
||||
full_screen_ws: &PositionAndSize,
|
||||
mut os_api: Box<dyn ServerOsApi>,
|
||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||
senders: ThreadSenders,
|
||||
max_panes: Option<usize>,
|
||||
pane_id: Option<PaneId>,
|
||||
mode_info: ModeInfo,
|
||||
|
|
@ -261,9 +261,7 @@ impl Tab {
|
|||
fullscreen_is_active: false,
|
||||
synchronize_is_active: false,
|
||||
os_api,
|
||||
send_plugin_instructions,
|
||||
send_pty_instructions,
|
||||
send_server_instructions,
|
||||
senders,
|
||||
should_clear_display_before_rendering: false,
|
||||
mode_info,
|
||||
input_mode,
|
||||
|
|
@ -315,14 +313,14 @@ impl Tab {
|
|||
// Just a regular terminal
|
||||
if let Some(plugin) = &layout.plugin {
|
||||
let (pid_tx, pid_rx) = channel();
|
||||
self.send_plugin_instructions
|
||||
.send(PluginInstruction::Load(pid_tx, plugin.clone()))
|
||||
self.senders
|
||||
.send_to_plugin(PluginInstruction::Load(pid_tx, plugin.clone()))
|
||||
.unwrap();
|
||||
let pid = pid_rx.recv().unwrap();
|
||||
let mut new_plugin = PluginPane::new(
|
||||
pid,
|
||||
*position_and_size,
|
||||
self.send_plugin_instructions.clone(),
|
||||
self.senders.to_plugin.as_ref().unwrap().clone(),
|
||||
);
|
||||
if let Some(max_rows) = position_and_size.max_rows {
|
||||
new_plugin.set_max_height(max_rows);
|
||||
|
|
@ -332,8 +330,8 @@ impl Tab {
|
|||
}
|
||||
self.panes.insert(PaneId::Plugin(pid), Box::new(new_plugin));
|
||||
// Send an initial mode update to the newly loaded plugin only!
|
||||
self.send_plugin_instructions
|
||||
.send(PluginInstruction::Update(
|
||||
self.senders
|
||||
.send_to_plugin(PluginInstruction::Update(
|
||||
Some(pid),
|
||||
Event::ModeUpdate(self.mode_info.clone()),
|
||||
))
|
||||
|
|
@ -355,8 +353,8 @@ impl Tab {
|
|||
// this is a bit of a hack and happens because we don't have any central location that
|
||||
// can query the screen as to how many panes it needs to create a layout
|
||||
// fixing this will require a bit of an architecture change
|
||||
self.send_pty_instructions
|
||||
.send(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid)))
|
||||
self.senders
|
||||
.send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid)))
|
||||
.unwrap();
|
||||
}
|
||||
self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next();
|
||||
|
|
@ -400,9 +398,9 @@ impl Tab {
|
|||
},
|
||||
);
|
||||
if terminal_id_to_split.is_none() {
|
||||
self.send_pty_instructions
|
||||
.send(PtyInstruction::ClosePane(pid))
|
||||
.unwrap(); // we can't open this pane, close the pty
|
||||
self.senders
|
||||
.send_to_pty(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty
|
||||
.unwrap();
|
||||
return; // likely no terminal large enough to split
|
||||
}
|
||||
let terminal_id_to_split = terminal_id_to_split.unwrap();
|
||||
|
|
@ -481,9 +479,9 @@ impl Tab {
|
|||
let active_pane_id = &self.get_active_pane_id().unwrap();
|
||||
let active_pane = self.panes.get_mut(active_pane_id).unwrap();
|
||||
if active_pane.rows() < MIN_TERMINAL_HEIGHT * 2 + 1 {
|
||||
self.send_pty_instructions
|
||||
.send(PtyInstruction::ClosePane(pid))
|
||||
.unwrap(); // we can't open this pane, close the pty
|
||||
self.senders
|
||||
.send_to_pty(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
let terminal_ws = PositionAndSize {
|
||||
|
|
@ -538,9 +536,9 @@ impl Tab {
|
|||
let active_pane_id = &self.get_active_pane_id().unwrap();
|
||||
let active_pane = self.panes.get_mut(active_pane_id).unwrap();
|
||||
if active_pane.columns() < MIN_TERMINAL_WIDTH * 2 + 1 {
|
||||
self.send_pty_instructions
|
||||
.send(PtyInstruction::ClosePane(pid))
|
||||
.unwrap(); // we can't open this pane, close the pty
|
||||
self.senders
|
||||
.send_to_pty(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
let terminal_ws = PositionAndSize {
|
||||
|
|
@ -597,7 +595,7 @@ impl Tab {
|
|||
}
|
||||
pub fn handle_pty_bytes(&mut self, pid: RawFd, bytes: VteBytes) {
|
||||
// if we don't have the terminal in self.terminals it's probably because
|
||||
// of a race condition where the terminal was created in pty_bus but has not
|
||||
// of a race condition where the terminal was created in pty but has not
|
||||
// yet been created in Screen. These events are currently not buffered, so
|
||||
// if you're debugging seemingly randomly missing stdout data, this is
|
||||
// the reason
|
||||
|
|
@ -607,23 +605,17 @@ impl Tab {
|
|||
}
|
||||
pub fn write_to_terminals_on_current_tab(&mut self, input_bytes: Vec<u8>) {
|
||||
let pane_ids = self.get_pane_ids();
|
||||
pane_ids.iter().for_each(|pane_id| match pane_id {
|
||||
PaneId::Terminal(pid) => {
|
||||
self.write_to_pane_id(input_bytes.clone(), *pid);
|
||||
}
|
||||
PaneId::Plugin(_) => {}
|
||||
pane_ids.iter().for_each(|&pane_id| {
|
||||
self.write_to_pane_id(input_bytes.clone(), pane_id);
|
||||
});
|
||||
}
|
||||
pub fn write_to_pane_id(&mut self, mut input_bytes: Vec<u8>, pid: RawFd) {
|
||||
self.os_api
|
||||
.write_to_tty_stdin(pid, &mut input_bytes)
|
||||
.expect("failed to write to terminal");
|
||||
self.os_api.tcdrain(pid).expect("failed to drain terminal");
|
||||
}
|
||||
pub fn write_to_active_terminal(&mut self, input_bytes: Vec<u8>) {
|
||||
match self.get_active_pane_id() {
|
||||
Some(PaneId::Terminal(active_terminal_id)) => {
|
||||
let active_terminal = self.get_active_pane().unwrap();
|
||||
self.write_to_pane_id(input_bytes, self.get_active_pane_id().unwrap());
|
||||
}
|
||||
pub fn write_to_pane_id(&mut self, input_bytes: Vec<u8>, pane_id: PaneId) {
|
||||
match pane_id {
|
||||
PaneId::Terminal(active_terminal_id) => {
|
||||
let active_terminal = self.panes.get(&pane_id).unwrap();
|
||||
let mut adjusted_input = active_terminal.adjust_input_to_terminal(input_bytes);
|
||||
self.os_api
|
||||
.write_to_tty_stdin(active_terminal_id, &mut adjusted_input)
|
||||
|
|
@ -632,14 +624,13 @@ impl Tab {
|
|||
.tcdrain(active_terminal_id)
|
||||
.expect("failed to drain terminal");
|
||||
}
|
||||
Some(PaneId::Plugin(pid)) => {
|
||||
PaneId::Plugin(pid) => {
|
||||
for key in parse_keys(&input_bytes) {
|
||||
self.send_plugin_instructions
|
||||
.send(PluginInstruction::Update(Some(pid), Event::KeyPress(key)))
|
||||
self.senders
|
||||
.send_to_plugin(PluginInstruction::Update(Some(pid), Event::KeyPress(key)))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
pub fn get_active_terminal_cursor_position(&self) -> Option<(usize, usize)> {
|
||||
|
|
@ -774,10 +765,12 @@ impl Tab {
|
|||
match self.get_active_terminal_cursor_position() {
|
||||
Some((cursor_position_x, cursor_position_y)) => {
|
||||
let show_cursor = "\u{1b}[?25h";
|
||||
let change_cursor_shape = self.get_active_pane().unwrap().cursor_shape_csi();
|
||||
let goto_cursor_position = &format!(
|
||||
"\u{1b}[{};{}H\u{1b}[m",
|
||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||
cursor_position_y + 1,
|
||||
cursor_position_x + 1
|
||||
cursor_position_x + 1,
|
||||
change_cursor_shape
|
||||
); // goto row/col
|
||||
output.push_str(show_cursor);
|
||||
output.push_str(goto_cursor_position);
|
||||
|
|
@ -788,8 +781,8 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
|
||||
self.send_server_instructions
|
||||
.send(ServerInstruction::Render(Some(output)))
|
||||
self.senders
|
||||
.send_to_server(ServerInstruction::Render(Some(output)))
|
||||
.unwrap();
|
||||
}
|
||||
fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> {
|
||||
|
|
@ -1860,12 +1853,13 @@ impl Tab {
|
|||
}
|
||||
self.render();
|
||||
}
|
||||
pub fn move_focus_left(&mut self) {
|
||||
// returns a boolean that indicates whether the focus moved
|
||||
pub fn move_focus_left(&mut self) -> bool {
|
||||
if !self.has_selectable_panes() {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if self.fullscreen_is_active {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
let active_terminal = self.get_active_pane();
|
||||
if let Some(active) = active_terminal {
|
||||
|
|
@ -1880,6 +1874,8 @@ impl Tab {
|
|||
match next_index {
|
||||
Some(&p) => {
|
||||
self.active_terminal = Some(p);
|
||||
self.render();
|
||||
return true;
|
||||
}
|
||||
None => {
|
||||
self.active_terminal = Some(active.pid());
|
||||
|
|
@ -1888,7 +1884,7 @@ impl Tab {
|
|||
} else {
|
||||
self.active_terminal = Some(active_terminal.unwrap().pid());
|
||||
}
|
||||
self.render();
|
||||
false
|
||||
}
|
||||
pub fn move_focus_down(&mut self) {
|
||||
if !self.has_selectable_panes() {
|
||||
|
|
@ -1950,12 +1946,13 @@ impl Tab {
|
|||
}
|
||||
self.render();
|
||||
}
|
||||
pub fn move_focus_right(&mut self) {
|
||||
// returns a boolean that indicates whether the focus moved
|
||||
pub fn move_focus_right(&mut self) -> bool {
|
||||
if !self.has_selectable_panes() {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if self.fullscreen_is_active {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
let active_terminal = self.get_active_pane();
|
||||
if let Some(active) = active_terminal {
|
||||
|
|
@ -1970,6 +1967,8 @@ impl Tab {
|
|||
match next_index {
|
||||
Some(&p) => {
|
||||
self.active_terminal = Some(p);
|
||||
self.render();
|
||||
return true;
|
||||
}
|
||||
None => {
|
||||
self.active_terminal = Some(active.pid());
|
||||
|
|
@ -1978,7 +1977,7 @@ impl Tab {
|
|||
} else {
|
||||
self.active_terminal = Some(active_terminal.unwrap().pid());
|
||||
}
|
||||
self.render();
|
||||
false
|
||||
}
|
||||
fn horizontal_borders(&self, terminals: &[PaneId]) -> HashSet<usize> {
|
||||
terminals.iter().fold(HashSet::new(), |mut borders, t| {
|
||||
|
|
@ -2093,8 +2092,8 @@ impl Tab {
|
|||
if let Some(max_panes) = self.max_panes {
|
||||
let terminals = self.get_pane_ids();
|
||||
for &pid in terminals.iter().skip(max_panes - 1) {
|
||||
self.send_pty_instructions
|
||||
.send(PtyInstruction::ClosePane(pid))
|
||||
self.senders
|
||||
.send_to_pty(PtyInstruction::ClosePane(pid))
|
||||
.unwrap();
|
||||
self.close_pane_without_rerender(pid);
|
||||
}
|
||||
|
|
@ -2205,8 +2204,8 @@ impl Tab {
|
|||
pub fn close_focused_pane(&mut self) {
|
||||
if let Some(active_pane_id) = self.get_active_pane_id() {
|
||||
self.close_pane(active_pane_id);
|
||||
self.send_pty_instructions
|
||||
.send(PtyInstruction::ClosePane(active_pane_id))
|
||||
self.senders
|
||||
.send_to_pty(PtyInstruction::ClosePane(active_pane_id))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,22 @@
|
|||
//! Error context system based on a thread-local representation of the call stack, itself based on
|
||||
//! the instructions that are sent between threads.
|
||||
|
||||
use super::{ServerInstruction, ASYNCOPENCALLS, OPENCALLS};
|
||||
use crate::client::ClientInstruction;
|
||||
use crate::pty_bus::PtyInstruction;
|
||||
use crate::screen::ScreenInstruction;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::fmt::{Display, Error, Formatter};
|
||||
|
||||
use crate::client::ClientInstruction;
|
||||
use crate::common::thread_bus::{ASYNCOPENCALLS, OPENCALLS};
|
||||
use crate::pty::PtyInstruction;
|
||||
use crate::screen::ScreenInstruction;
|
||||
use crate::server::ServerInstruction;
|
||||
|
||||
/// The maximum amount of calls an [`ErrorContext`] will keep track
|
||||
/// of in its stack representation. This is a per-thread maximum.
|
||||
const MAX_THREAD_CALL_STACK: usize = 6;
|
||||
|
||||
#[cfg(not(test))]
|
||||
use super::SenderWithContext;
|
||||
use super::thread_bus::SenderWithContext;
|
||||
#[cfg(not(test))]
|
||||
use std::panic::PanicInfo;
|
||||
/// Custom panic handler/hook. Prints the [`ErrorContext`].
|
||||
|
|
@ -190,9 +192,11 @@ pub enum ScreenContext {
|
|||
FocusNextPane,
|
||||
FocusPreviousPane,
|
||||
MoveFocusLeft,
|
||||
MoveFocusLeftOrPreviousTab,
|
||||
MoveFocusDown,
|
||||
MoveFocusUp,
|
||||
MoveFocusRight,
|
||||
MoveFocusRightOrNextTab,
|
||||
Exit,
|
||||
ScrollUp,
|
||||
ScrollDown,
|
||||
|
|
@ -200,7 +204,7 @@ pub enum ScreenContext {
|
|||
PageScrollDown,
|
||||
ClearScroll,
|
||||
CloseFocusedPane,
|
||||
ToggleActiveSyncPanes,
|
||||
ToggleActiveSyncTab,
|
||||
ToggleActiveTerminalFullscreen,
|
||||
SetSelectable,
|
||||
SetInvisibleBorders,
|
||||
|
|
@ -235,9 +239,13 @@ impl From<&ScreenInstruction> for ScreenContext {
|
|||
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,
|
||||
|
|
@ -261,7 +269,7 @@ impl From<&ScreenInstruction> for ScreenContext {
|
|||
ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName,
|
||||
ScreenInstruction::TerminalResize(_) => ScreenContext::TerminalResize,
|
||||
ScreenInstruction::ChangeMode(_) => ScreenContext::ChangeMode,
|
||||
ScreenInstruction::ToggleActiveSyncPanes => ScreenContext::ToggleActiveSyncPanes,
|
||||
ScreenInstruction::ToggleActiveSyncTab => ScreenContext::ToggleActiveSyncTab,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ pub enum Action {
|
|||
/// Move the focus pane in specified direction.
|
||||
SwitchFocus,
|
||||
MoveFocus(Direction),
|
||||
/// Tries to move the focus pane in specified direction.
|
||||
/// If there is no pane in the direction, move to previous/next Tab.
|
||||
MoveFocusOrTab(Direction),
|
||||
/// Scroll up in focus pane.
|
||||
ScrollUp,
|
||||
/// Scroll down in focus pane.
|
||||
|
|
@ -39,8 +42,8 @@ pub enum Action {
|
|||
PageScrollDown,
|
||||
/// Toggle between fullscreen focus pane and normal layout.
|
||||
ToggleFocusFullscreen,
|
||||
/// Toggle between sending text commands to all panes and normal mode.
|
||||
ToggleActiveSyncPanes,
|
||||
/// Toggle between sending text commands to all panes on the current tab and normal mode.
|
||||
ToggleActiveSyncTab,
|
||||
/// Open a new pane in the specified direction (relative to focus).
|
||||
/// If no direction is specified, will try to use the biggest available space.
|
||||
NewPane(Option<Direction>),
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ use std::io::{self, Read};
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use super::keybinds::{Keybinds, KeybindsFromYaml};
|
||||
use super::options::Options;
|
||||
use crate::cli::{CliArgs, ConfigCli};
|
||||
use crate::common::setup;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
const DEFAULT_CONFIG_FILE_NAME: &str = "config.yaml";
|
||||
|
|
@ -19,13 +20,16 @@ type ConfigResult = Result<Config, ConfigError>;
|
|||
/// Intermediate deserialization config struct
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ConfigFromYaml {
|
||||
#[serde(flatten)]
|
||||
pub options: Option<Options>,
|
||||
pub keybinds: Option<KeybindsFromYaml>,
|
||||
}
|
||||
|
||||
/// Main configuration.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
pub keybinds: Keybinds,
|
||||
pub options: Options,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -43,7 +47,8 @@ pub enum ConfigError {
|
|||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
let keybinds = Keybinds::default();
|
||||
Config { keybinds }
|
||||
let options = Options::default();
|
||||
Config { keybinds, options }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -55,7 +60,7 @@ impl TryFrom<&CliArgs> for Config {
|
|||
return Config::new(&path);
|
||||
}
|
||||
|
||||
if let Some(ConfigCli::Config { clean, .. }) = opts.option {
|
||||
if let Some(ConfigCli::Setup { clean, .. }) = opts.option {
|
||||
if clean {
|
||||
return Config::from_default_assets();
|
||||
}
|
||||
|
|
@ -84,7 +89,8 @@ impl Config {
|
|||
pub fn from_yaml(yaml_config: &str) -> ConfigResult {
|
||||
let config_from_yaml: ConfigFromYaml = serde_yaml::from_str(&yaml_config)?;
|
||||
let keybinds = Keybinds::get_default_keybinds_with_config(config_from_yaml.keybinds);
|
||||
Ok(Config { keybinds })
|
||||
let options = Options::from_yaml(config_from_yaml.options);
|
||||
Ok(Config { keybinds, options })
|
||||
}
|
||||
|
||||
/// Deserializes from given path.
|
||||
|
|
@ -172,7 +178,10 @@ mod config_test {
|
|||
#[test]
|
||||
fn try_from_cli_args_with_option_clean() {
|
||||
let mut opts = CliArgs::default();
|
||||
opts.option = Some(ConfigCli::Config { clean: true });
|
||||
opts.option = Some(ConfigCli::Setup {
|
||||
clean: true,
|
||||
dump_config: false,
|
||||
});
|
||||
let result = Config::try_from(&opts);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ use super::actions::Action;
|
|||
use super::keybinds::Keybinds;
|
||||
use crate::client::ClientInstruction;
|
||||
use crate::common::input::config::Config;
|
||||
use crate::common::{SenderWithContext, OPENCALLS};
|
||||
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 zellij_tile::data::{InputMode, Key, ModeInfo, Palette};
|
||||
use zellij_tile::data::{InputMode, Key, ModeInfo, Palette, PluginCapabilities};
|
||||
|
||||
/// Handles the dispatching of [`Action`]s according to the current
|
||||
/// [`InputMode`], and keep tracks of the current [`InputMode`].
|
||||
|
|
@ -23,6 +23,7 @@ struct InputHandler {
|
|||
command_is_executing: CommandIsExecuting,
|
||||
send_client_instructions: SenderWithContext<ClientInstruction>,
|
||||
should_exit: bool,
|
||||
pasting: bool,
|
||||
}
|
||||
|
||||
impl InputHandler {
|
||||
|
|
@ -40,6 +41,7 @@ impl InputHandler {
|
|||
command_is_executing,
|
||||
send_client_instructions,
|
||||
should_exit: false,
|
||||
pasting: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -49,6 +51,8 @@ impl InputHandler {
|
|||
let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
|
||||
err_ctx.add_call(ContextType::StdinHandler);
|
||||
let alt_left_bracket = vec![27, 91];
|
||||
let bracketed_paste_start = vec![27, 91, 50, 48, 48, 126]; // \u{1b}[200~
|
||||
let bracketed_paste_end = vec![27, 91, 50, 48, 49, 126]; // \u{1b}[201
|
||||
loop {
|
||||
if self.should_exit {
|
||||
break;
|
||||
|
|
@ -67,6 +71,10 @@ impl InputHandler {
|
|||
if unsupported_key == alt_left_bracket {
|
||||
let key = Key::Alt('[');
|
||||
self.handle_key(&key, raw_bytes);
|
||||
} else if unsupported_key == bracketed_paste_start {
|
||||
self.pasting = true;
|
||||
} else if unsupported_key == bracketed_paste_end {
|
||||
self.pasting = false;
|
||||
}
|
||||
}
|
||||
termion::event::Event::Mouse(_) => {
|
||||
|
|
@ -81,10 +89,20 @@ impl InputHandler {
|
|||
}
|
||||
fn handle_key(&mut self, key: &Key, raw_bytes: Vec<u8>) {
|
||||
let keybinds = &self.config.keybinds;
|
||||
for action in Keybinds::key_to_actions(&key, raw_bytes, &self.mode, keybinds) {
|
||||
let should_exit = self.dispatch_action(action);
|
||||
if should_exit {
|
||||
self.should_exit = true;
|
||||
if self.pasting {
|
||||
// we're inside a paste block, if we're in a mode that allows sending text to the
|
||||
// terminal, send all text directly without interpreting it
|
||||
// otherwise, just discard the input
|
||||
if self.mode == InputMode::Normal || self.mode == InputMode::Locked {
|
||||
let action = Action::Write(raw_bytes);
|
||||
self.dispatch_action(action);
|
||||
}
|
||||
} else {
|
||||
for action in Keybinds::key_to_actions(&key, raw_bytes, &self.mode, keybinds) {
|
||||
let should_exit = self.dispatch_action(action);
|
||||
if should_exit {
|
||||
self.should_exit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -119,7 +137,8 @@ impl InputHandler {
|
|||
| Action::GoToNextTab
|
||||
| Action::GoToPreviousTab
|
||||
| Action::CloseTab
|
||||
| Action::GoToTab(_) => {
|
||||
| Action::GoToTab(_)
|
||||
| Action::MoveFocusOrTab(_) => {
|
||||
self.command_is_executing.blocking_input_thread();
|
||||
self.os_input
|
||||
.send_to_server(ServerInstruction::Action(action));
|
||||
|
|
@ -146,7 +165,11 @@ impl InputHandler {
|
|||
/// 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) -> ModeInfo {
|
||||
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 => {}
|
||||
|
|
@ -160,7 +183,6 @@ pub fn get_mode_info(mode: InputMode, palette: Palette) -> ModeInfo {
|
|||
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(("s".to_string(), "Sync".to_string()));
|
||||
keybinds.push(("f".to_string(), "Fullscreen".to_string()));
|
||||
}
|
||||
InputMode::Tab => {
|
||||
|
|
@ -168,6 +190,7 @@ pub fn get_mode_info(mode: InputMode, palette: Palette) -> ModeInfo {
|
|||
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()));
|
||||
|
|
@ -181,6 +204,7 @@ pub fn get_mode_info(mode: InputMode, palette: Palette) -> ModeInfo {
|
|||
mode,
|
||||
keybinds,
|
||||
palette,
|
||||
capabilities,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ use std::collections::HashMap;
|
|||
use super::actions::Action;
|
||||
use super::config;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
use zellij_tile::data::*;
|
||||
|
||||
/// Used in the config struct
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct Keybinds(HashMap<InputMode, ModeKeybinds>);
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ModeKeybinds(HashMap<Key, Vec<Action>>);
|
||||
|
||||
/// Intermediate struct used for deserialisation
|
||||
|
|
@ -29,8 +29,14 @@ pub struct KeybindsFromYaml {
|
|||
#[serde(untagged)]
|
||||
enum KeyActionUnbind {
|
||||
KeyAction(KeyActionFromYaml),
|
||||
// TODO: use the enum
|
||||
//Unbind(UnbindFromYaml),
|
||||
Unbind(UnbindFromYaml),
|
||||
}
|
||||
|
||||
/// Intermediate struct used for deserialisation
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||
struct KeyActionUnbindFromYaml {
|
||||
keybinds: Vec<KeyActionFromYaml>,
|
||||
unbind: Unbind,
|
||||
}
|
||||
|
||||
/// Intermediate struct used for deserialisation
|
||||
|
|
@ -41,7 +47,7 @@ pub struct KeyActionFromYaml {
|
|||
}
|
||||
|
||||
/// Intermediate struct used for deserialisation
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||
struct UnbindFromYaml {
|
||||
unbind: Unbind,
|
||||
}
|
||||
|
|
@ -51,16 +57,19 @@ struct UnbindFromYaml {
|
|||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum Unbind {
|
||||
// This is the correct order, don't rearrange!
|
||||
// Suspected Bug in the untagged macro.
|
||||
// 1. Keys
|
||||
Keys(Vec<Key>),
|
||||
// 2. All
|
||||
All(bool),
|
||||
// TODO@a-kenji: use the enum
|
||||
//Keys(Vec<Key>),
|
||||
}
|
||||
|
||||
impl Default for Keybinds {
|
||||
// Use once per codepath
|
||||
// TODO investigate why
|
||||
fn default() -> Keybinds {
|
||||
config::Config::from_default_assets()
|
||||
.expect("Keybinds from default assets Error")
|
||||
.keybinds
|
||||
Self::from_default_assets()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -69,11 +78,18 @@ impl Keybinds {
|
|||
Keybinds(HashMap::<InputMode, ModeKeybinds>::new())
|
||||
}
|
||||
|
||||
fn from_default_assets() -> Keybinds {
|
||||
config::Config::from_default_assets()
|
||||
.expect("Keybinds from default assets Error!")
|
||||
.keybinds
|
||||
}
|
||||
|
||||
/// Entrypoint from the config module
|
||||
pub fn get_default_keybinds_with_config(from_yaml: Option<KeybindsFromYaml>) -> Keybinds {
|
||||
let default_keybinds = match from_yaml.clone() {
|
||||
Some(keybinds) => match keybinds.unbind {
|
||||
Unbind::All(true) => Keybinds::new(),
|
||||
Unbind::All(false) => Keybinds::default(),
|
||||
Unbind::All(false) | Unbind::Keys(_) => Keybinds::unbind(keybinds),
|
||||
},
|
||||
None => Keybinds::default(),
|
||||
};
|
||||
|
|
@ -85,6 +101,71 @@ impl Keybinds {
|
|||
}
|
||||
}
|
||||
|
||||
/// Unbinds the default keybindings in relation to their mode
|
||||
fn unbind(from_yaml: KeybindsFromYaml) -> Keybinds {
|
||||
let mut keybind_config = Self::new();
|
||||
let mut unbind_config: HashMap<InputMode, Unbind> = HashMap::new();
|
||||
let keybinds_from_yaml = from_yaml.keybinds;
|
||||
|
||||
for mode in InputMode::iter() {
|
||||
if let Some(keybinds) = keybinds_from_yaml.get(&mode) {
|
||||
for keybind in keybinds.iter() {
|
||||
match keybind {
|
||||
KeyActionUnbind::Unbind(unbind) => {
|
||||
unbind_config.insert(mode, unbind.unbind.clone());
|
||||
}
|
||||
KeyActionUnbind::KeyAction(key_action_from_yaml) => {
|
||||
keybind_config
|
||||
.0
|
||||
.insert(mode, ModeKeybinds::from(key_action_from_yaml.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut default = Self::default().unbind_mode(unbind_config);
|
||||
|
||||
// Toplevel Unbinds
|
||||
if let Unbind::Keys(_) = from_yaml.unbind {
|
||||
let mut unbind_config: HashMap<InputMode, Unbind> = HashMap::new();
|
||||
for mode in InputMode::iter() {
|
||||
unbind_config.insert(mode, from_yaml.unbind.clone());
|
||||
}
|
||||
default = default.unbind_mode(unbind_config);
|
||||
};
|
||||
|
||||
default.merge_keybinds(keybind_config)
|
||||
}
|
||||
|
||||
/// Unbind [`Key`] bindings respective to their mode
|
||||
fn unbind_mode(&self, unbind: HashMap<InputMode, Unbind>) -> Keybinds {
|
||||
let mut keybinds = Keybinds::new();
|
||||
|
||||
for mode in InputMode::iter() {
|
||||
if let Some(unbind) = unbind.get(&mode) {
|
||||
match unbind {
|
||||
Unbind::All(true) => {}
|
||||
Unbind::Keys(keys) => {
|
||||
if let Some(defaults) = self.0.get(&mode) {
|
||||
keybinds
|
||||
.0
|
||||
.insert(mode, defaults.clone().unbind_keys(keys.to_vec()));
|
||||
}
|
||||
}
|
||||
Unbind::All(false) => {
|
||||
if let Some(defaults) = self.0.get(&mode) {
|
||||
keybinds.0.insert(mode, defaults.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(defaults) = self.0.get(&mode) {
|
||||
keybinds.0.insert(mode, defaults.clone());
|
||||
}
|
||||
}
|
||||
keybinds
|
||||
}
|
||||
|
||||
/// Merges two Keybinds structs into one Keybinds struct
|
||||
/// `other` overrides the ModeKeybinds of `self`.
|
||||
fn merge_keybinds(&self, other: Keybinds) -> Keybinds {
|
||||
|
|
@ -142,6 +223,15 @@ impl ModeKeybinds {
|
|||
merged.0.extend(other.0);
|
||||
merged
|
||||
}
|
||||
|
||||
/// Remove [`Key`]'s from [`ModeKeybinds`]
|
||||
fn unbind_keys(self, unbind: Vec<Key>) -> Self {
|
||||
let mut keymap = self;
|
||||
for key in unbind {
|
||||
keymap.0.remove(&key);
|
||||
}
|
||||
keymap
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeybindsFromYaml> for Keybinds {
|
||||
|
|
@ -161,30 +251,44 @@ impl From<KeybindsFromYaml> for Keybinds {
|
|||
}
|
||||
}
|
||||
|
||||
/// For each `Key` assigned to `Action`s,
|
||||
/// map the `Action`s to the key
|
||||
/// For each [`Key`] assigned to [`Action`]s,
|
||||
/// map the [`Action`]s to the [`Key`]
|
||||
impl From<KeyActionFromYaml> for ModeKeybinds {
|
||||
fn from(key_action: KeyActionFromYaml) -> ModeKeybinds {
|
||||
let keys = key_action.key;
|
||||
let actions = key_action.action;
|
||||
|
||||
ModeKeybinds(
|
||||
keys.into_iter()
|
||||
key_action
|
||||
.key
|
||||
.into_iter()
|
||||
.map(|k| (k, actions.clone()))
|
||||
.collect::<HashMap<Key, Vec<Action>>>(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Currently an enum for future use
|
||||
impl From<KeyActionUnbind> for ModeKeybinds {
|
||||
fn from(key_action_unbind: KeyActionUnbind) -> ModeKeybinds {
|
||||
match key_action_unbind {
|
||||
KeyActionUnbind::KeyAction(key_action) => ModeKeybinds::from(key_action),
|
||||
KeyActionUnbind::Unbind(_) => ModeKeybinds::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<KeyActionFromYaml>> for ModeKeybinds {
|
||||
fn from(key_action_from_yaml: Vec<KeyActionFromYaml>) -> ModeKeybinds {
|
||||
let mut mode_keybinds = ModeKeybinds::new();
|
||||
|
||||
for keybind in key_action_from_yaml {
|
||||
for key in keybind.key {
|
||||
mode_keybinds.0.insert(key, keybind.action.clone());
|
||||
}
|
||||
}
|
||||
mode_keybinds
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Unbind {
|
||||
fn default() -> Unbind {
|
||||
Unbind::All(false)
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@ pub mod actions;
|
|||
pub mod config;
|
||||
pub mod handler;
|
||||
pub mod keybinds;
|
||||
pub mod options;
|
||||
|
|
|
|||
45
src/common/input/options.rs
Normal file
45
src/common/input/options.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
//! Handles cli and configuration options
|
||||
use crate::cli::ConfigCli;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq, Deserialize, Serialize, StructOpt)]
|
||||
/// Options that can be set either through the config file,
|
||||
/// or cli flags
|
||||
pub struct Options {
|
||||
/// Allow plugins to use a more simplified layout
|
||||
/// that is compatible with more fonts
|
||||
#[structopt(long)]
|
||||
pub simplified_ui: bool,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
pub fn from_yaml(from_yaml: Option<Options>) -> Options {
|
||||
if let Some(opts) = from_yaml {
|
||||
opts
|
||||
} else {
|
||||
Options::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Merges two [`Options`] structs, a `Some` in `other`
|
||||
/// will supercede a `Some` in `self`
|
||||
// TODO: Maybe a good candidate for a macro?
|
||||
pub fn merge(&self, other: Options) -> Options {
|
||||
let simplified_ui = if other.simplified_ui {
|
||||
true
|
||||
} else {
|
||||
self.simplified_ui
|
||||
};
|
||||
|
||||
Options { simplified_ui }
|
||||
}
|
||||
|
||||
pub fn from_cli(&self, other: Option<ConfigCli>) -> Options {
|
||||
if let Some(ConfigCli::Options(options)) = other {
|
||||
Options::merge(&self, options)
|
||||
} else {
|
||||
self.to_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -136,6 +136,7 @@ fn toplevel_unbind_unbinds_all() {
|
|||
assert_eq!(keybinds_from_yaml, Keybinds::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_unbind_unbinds_none() {
|
||||
let from_yaml = KeybindsFromYaml {
|
||||
unbind: Unbind::All(false),
|
||||
|
|
@ -144,5 +145,657 @@ fn no_unbind_unbinds_none() {
|
|||
|
||||
let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
|
||||
|
||||
assert_eq!(keybinds_from_yaml, Keybinds::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_keybind_is_taken() {
|
||||
let actions_1 = vec![Action::NoOp, Action::NewTab];
|
||||
let keyaction_1 = KeyActionFromYaml {
|
||||
action: actions_1.clone(),
|
||||
key: vec![Key::F(1), Key::Backspace, Key::Char('t')],
|
||||
};
|
||||
let actions_2 = vec![Action::GoToTab(1)];
|
||||
let keyaction_2 = KeyActionFromYaml {
|
||||
action: actions_2.clone(),
|
||||
key: vec![Key::F(1), Key::Backspace, Key::Char('t')],
|
||||
};
|
||||
|
||||
let mut expected = ModeKeybinds::new();
|
||||
expected.0.insert(Key::F(1), actions_2.clone());
|
||||
expected.0.insert(Key::Backspace, actions_2.clone());
|
||||
expected.0.insert(Key::Char('t'), actions_2);
|
||||
|
||||
assert_eq!(expected, ModeKeybinds::from(vec![keyaction_1, keyaction_2]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_keybind_overwrites() {
|
||||
let actions_1 = vec![Action::NoOp, Action::NewTab];
|
||||
let keyaction_1 = KeyActionFromYaml {
|
||||
action: actions_1.clone(),
|
||||
key: vec![Key::F(1), Key::Backspace, Key::Char('t')],
|
||||
};
|
||||
let actions_2 = vec![Action::GoToTab(1)];
|
||||
let keyaction_2 = KeyActionFromYaml {
|
||||
action: actions_2.clone(),
|
||||
key: vec![Key::F(1), Key::Char('t')],
|
||||
};
|
||||
|
||||
let mut expected = ModeKeybinds::new();
|
||||
expected.0.insert(Key::F(1), actions_2.clone());
|
||||
expected.0.insert(Key::Backspace, actions_1.clone());
|
||||
expected.0.insert(Key::Char('t'), actions_2);
|
||||
|
||||
assert_eq!(expected, ModeKeybinds::from(vec![keyaction_1, keyaction_2]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbind_single_mode() {
|
||||
let unbind = Unbind::All(true);
|
||||
let unbind_from_yaml = UnbindFromYaml { unbind };
|
||||
let key_action_unbinds = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
|
||||
|
||||
let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
|
||||
keys.insert(InputMode::Normal, key_action_unbinds);
|
||||
|
||||
let keybinds_from_yaml = KeybindsFromYaml {
|
||||
keybinds: keys,
|
||||
unbind: Unbind::All(false),
|
||||
};
|
||||
|
||||
let keybinds = Keybinds::unbind(keybinds_from_yaml);
|
||||
let result = keybinds.0.get(&InputMode::Normal);
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbind_multiple_modes() {
|
||||
let unbind = Unbind::All(true);
|
||||
let unbind_from_yaml = UnbindFromYaml { unbind };
|
||||
let key_action_unbinds = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
|
||||
|
||||
let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
|
||||
keys.insert(InputMode::Normal, key_action_unbinds.clone());
|
||||
keys.insert(InputMode::Pane, key_action_unbinds);
|
||||
|
||||
let keybinds_from_yaml = KeybindsFromYaml {
|
||||
keybinds: keys,
|
||||
unbind: Unbind::All(false),
|
||||
};
|
||||
|
||||
let keybinds = Keybinds::unbind(keybinds_from_yaml);
|
||||
let normal = keybinds.0.get(&InputMode::Normal);
|
||||
let pane = keybinds.0.get(&InputMode::Pane);
|
||||
assert!(normal.is_none());
|
||||
assert!(pane.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbind_single_keybind_single_mode() {
|
||||
let unbind = Unbind::Keys(vec![Key::Alt('n')]);
|
||||
let unbind_from_yaml = UnbindFromYaml { unbind };
|
||||
let key_action_unbinds = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
|
||||
|
||||
let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
|
||||
keys.insert(InputMode::Normal, key_action_unbinds);
|
||||
|
||||
let keybinds_from_yaml = KeybindsFromYaml {
|
||||
keybinds: keys,
|
||||
unbind: Unbind::All(false),
|
||||
};
|
||||
|
||||
let keybinds = Keybinds::unbind(keybinds_from_yaml);
|
||||
let mode_keybinds = keybinds.0.get(&InputMode::Normal);
|
||||
let result = mode_keybinds
|
||||
.expect("Mode shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbind_single_keybind_multiple_modes() {
|
||||
let unbind_n = Unbind::Keys(vec![Key::Alt('n')]);
|
||||
let unbind_h = Unbind::Keys(vec![Key::Alt('h')]);
|
||||
let unbind_from_yaml_n = UnbindFromYaml { unbind: unbind_n };
|
||||
let unbind_from_yaml_h = UnbindFromYaml { unbind: unbind_h };
|
||||
let key_action_unbinds_n = vec![KeyActionUnbind::Unbind(unbind_from_yaml_n)];
|
||||
let key_action_unbinds_h = vec![KeyActionUnbind::Unbind(unbind_from_yaml_h)];
|
||||
|
||||
let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
|
||||
keys.insert(InputMode::Normal, key_action_unbinds_n);
|
||||
keys.insert(InputMode::Pane, key_action_unbinds_h);
|
||||
|
||||
let keybinds_from_yaml = KeybindsFromYaml {
|
||||
keybinds: keys,
|
||||
unbind: Unbind::All(false),
|
||||
};
|
||||
|
||||
let keybinds = Keybinds::unbind(keybinds_from_yaml);
|
||||
let normal = keybinds.0.get(&InputMode::Normal);
|
||||
let pane = keybinds.0.get(&InputMode::Pane);
|
||||
let result_normal = normal
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
let result_pane = pane.expect("Mode shouldn't be empty").0.get(&Key::Alt('h'));
|
||||
assert!(result_normal.is_none());
|
||||
assert!(result_pane.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbind_multiple_keybinds_single_mode() {
|
||||
let unbind = Unbind::Keys(vec![Key::Alt('n'), Key::Ctrl('p')]);
|
||||
let unbind_from_yaml = UnbindFromYaml { unbind };
|
||||
let key_action_unbinds = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
|
||||
|
||||
let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
|
||||
keys.insert(InputMode::Normal, key_action_unbinds);
|
||||
|
||||
let keybinds_from_yaml = KeybindsFromYaml {
|
||||
keybinds: keys,
|
||||
unbind: Unbind::All(false),
|
||||
};
|
||||
|
||||
let keybinds = Keybinds::unbind(keybinds_from_yaml);
|
||||
let mode_keybinds = keybinds.0.get(&InputMode::Normal);
|
||||
let result_n = mode_keybinds
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
let result_p = mode_keybinds
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Ctrl('p'));
|
||||
assert!(result_n.is_none());
|
||||
assert!(result_p.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbind_multiple_keybinds_multiple_modes() {
|
||||
let unbind_normal = Unbind::Keys(vec![Key::Alt('n'), Key::Ctrl('p')]);
|
||||
let unbind_resize = Unbind::Keys(vec![Key::Char('h'), Key::Ctrl('r')]);
|
||||
let unbind_from_yaml_normal = UnbindFromYaml {
|
||||
unbind: unbind_normal,
|
||||
};
|
||||
let unbind_from_yaml_resize = UnbindFromYaml {
|
||||
unbind: unbind_resize,
|
||||
};
|
||||
let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml_normal)];
|
||||
let key_action_unbinds_resize = vec![KeyActionUnbind::Unbind(unbind_from_yaml_resize)];
|
||||
|
||||
let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
|
||||
keys.insert(InputMode::Normal, key_action_unbinds_normal);
|
||||
keys.insert(InputMode::Resize, key_action_unbinds_resize);
|
||||
|
||||
let keybinds_from_yaml = KeybindsFromYaml {
|
||||
keybinds: keys,
|
||||
unbind: Unbind::All(false),
|
||||
};
|
||||
|
||||
let keybinds = Keybinds::unbind(keybinds_from_yaml);
|
||||
let mode_keybinds_normal = keybinds.0.get(&InputMode::Normal);
|
||||
let mode_keybinds_resize = keybinds.0.get(&InputMode::Resize);
|
||||
let result_normal_1 = mode_keybinds_normal
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
let result_normal_2 = mode_keybinds_normal
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Ctrl('p'));
|
||||
let result_resize_1 = mode_keybinds_resize
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Char('h'));
|
||||
let result_resize_2 = mode_keybinds_resize
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Ctrl('r'));
|
||||
assert!(result_normal_1.is_none());
|
||||
assert!(result_resize_1.is_none());
|
||||
assert!(result_normal_2.is_none());
|
||||
assert!(result_resize_2.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbind_multiple_keybinds_all_modes() {
|
||||
let unbind = Unbind::Keys(vec![Key::Alt('n'), Key::Alt('h')]);
|
||||
let keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
|
||||
let keybinds_from_yaml = KeybindsFromYaml {
|
||||
keybinds: keys,
|
||||
unbind,
|
||||
};
|
||||
|
||||
let keybinds = Keybinds::unbind(keybinds_from_yaml);
|
||||
let mode_keybinds_normal = keybinds.0.get(&InputMode::Normal);
|
||||
let mode_keybinds_resize = keybinds.0.get(&InputMode::Resize);
|
||||
let result_normal_1 = mode_keybinds_normal
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
let result_normal_2 = mode_keybinds_normal
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Ctrl('h'));
|
||||
let result_resize_1 = mode_keybinds_resize
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Char('n'));
|
||||
let result_resize_2 = mode_keybinds_resize
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Ctrl('h'));
|
||||
assert!(result_normal_1.is_none());
|
||||
assert!(result_resize_1.is_none());
|
||||
assert!(result_normal_2.is_none());
|
||||
assert!(result_resize_2.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbind_all_toplevel_single_key_single_mode() {
|
||||
let unbind = Unbind::Keys(vec![Key::Alt('h')]);
|
||||
let unbind_from_yaml = UnbindFromYaml { unbind };
|
||||
let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
|
||||
let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
|
||||
keys.insert(InputMode::Normal, key_action_unbinds_normal);
|
||||
let from_yaml = KeybindsFromYaml {
|
||||
keybinds: keys,
|
||||
unbind: Unbind::All(true),
|
||||
};
|
||||
|
||||
let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
|
||||
assert_eq!(keybinds_from_yaml, Keybinds::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbind_all_toplevel_single_key_multiple_modes() {
|
||||
let unbind_n = Unbind::Keys(vec![Key::Alt('n')]);
|
||||
let unbind_h = Unbind::Keys(vec![Key::Alt('h')]);
|
||||
let unbind_from_yaml_n = UnbindFromYaml { unbind: unbind_n };
|
||||
let unbind_from_yaml_h = UnbindFromYaml { unbind: unbind_h };
|
||||
let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml_n)];
|
||||
let key_action_unbinds_pane = vec![KeyActionUnbind::Unbind(unbind_from_yaml_h)];
|
||||
let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
|
||||
keys.insert(InputMode::Normal, key_action_unbinds_normal);
|
||||
keys.insert(InputMode::Pane, key_action_unbinds_pane);
|
||||
let from_yaml = KeybindsFromYaml {
|
||||
keybinds: keys,
|
||||
unbind: Unbind::All(true),
|
||||
};
|
||||
|
||||
let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
|
||||
assert_eq!(keybinds_from_yaml, Keybinds::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbind_all_toplevel_multiple_key_multiple_modes() {
|
||||
let unbind_n = Unbind::Keys(vec![Key::Alt('n'), Key::Ctrl('p')]);
|
||||
let unbind_h = Unbind::Keys(vec![Key::Alt('h'), Key::Ctrl('t')]);
|
||||
let unbind_from_yaml_n = UnbindFromYaml { unbind: unbind_n };
|
||||
let unbind_from_yaml_h = UnbindFromYaml { unbind: unbind_h };
|
||||
let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml_n)];
|
||||
let key_action_unbinds_pane = vec![KeyActionUnbind::Unbind(unbind_from_yaml_h)];
|
||||
let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
|
||||
keys.insert(InputMode::Normal, key_action_unbinds_normal);
|
||||
keys.insert(InputMode::Pane, key_action_unbinds_pane);
|
||||
let from_yaml = KeybindsFromYaml {
|
||||
keybinds: keys,
|
||||
unbind: Unbind::All(true),
|
||||
};
|
||||
|
||||
let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
|
||||
assert_eq!(keybinds_from_yaml, Keybinds::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbind_all_toplevel_all_key_multiple_modes() {
|
||||
let unbind = Unbind::All(true);
|
||||
let unbind_from_yaml = UnbindFromYaml { unbind };
|
||||
let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml.clone())];
|
||||
let key_action_unbinds_pane = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
|
||||
let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
|
||||
keys.insert(InputMode::Normal, key_action_unbinds_normal);
|
||||
keys.insert(InputMode::Pane, key_action_unbinds_pane);
|
||||
let from_yaml = KeybindsFromYaml {
|
||||
keybinds: keys,
|
||||
unbind: Unbind::All(true),
|
||||
};
|
||||
|
||||
let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
|
||||
assert_eq!(keybinds_from_yaml, Keybinds::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbind_single_keybind_all_modes() {
|
||||
let keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
|
||||
let from_yaml = KeybindsFromYaml {
|
||||
keybinds: keys,
|
||||
unbind: Unbind::Keys(vec![Key::Alt('n')]),
|
||||
};
|
||||
|
||||
let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
|
||||
|
||||
let result_normal = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Normal)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
let result_pane = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Pane)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
let result_resize = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Resize)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
let result_tab = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Tab)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
|
||||
assert!(result_normal.is_none());
|
||||
assert!(result_pane.is_none());
|
||||
assert!(result_resize.is_none());
|
||||
assert!(result_tab.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbind_single_toplevel_single_key_single_mode_identical() {
|
||||
let unbind = Unbind::Keys(vec![Key::Alt('n')]);
|
||||
let unbind_from_yaml = UnbindFromYaml { unbind };
|
||||
let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
|
||||
let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
|
||||
keys.insert(InputMode::Normal, key_action_unbind);
|
||||
let from_yaml = KeybindsFromYaml {
|
||||
keybinds: keys,
|
||||
unbind: Unbind::Keys(vec![Key::Alt('n')]),
|
||||
};
|
||||
|
||||
let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
|
||||
|
||||
let result_normal = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Normal)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
let result_pane = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Pane)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
let result_resize = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Resize)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
let result_tab = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Tab)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
|
||||
assert!(result_normal.is_none());
|
||||
assert!(result_pane.is_none());
|
||||
assert!(result_resize.is_none());
|
||||
assert!(result_tab.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbind_single_toplevel_single_key_single_mode_differing() {
|
||||
let unbind = Unbind::Keys(vec![Key::Alt('l')]);
|
||||
let unbind_from_yaml = UnbindFromYaml { unbind };
|
||||
let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
|
||||
let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
|
||||
keys.insert(InputMode::Normal, key_action_unbind);
|
||||
let from_yaml = KeybindsFromYaml {
|
||||
keybinds: keys,
|
||||
unbind: Unbind::Keys(vec![Key::Alt('n')]),
|
||||
};
|
||||
|
||||
let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
|
||||
|
||||
let result_normal_n = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Normal)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
let result_normal_l = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Normal)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('l'));
|
||||
let result_resize_n = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Resize)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
let result_resize_l = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Resize)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('l'));
|
||||
|
||||
assert!(result_normal_n.is_none());
|
||||
assert!(result_normal_l.is_none());
|
||||
assert!(result_resize_n.is_none());
|
||||
assert!(result_resize_l.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbind_single_toplevel_single_key_multiple_modes() {
|
||||
let unbind = Unbind::Keys(vec![Key::Alt('l')]);
|
||||
let unbind_from_yaml = UnbindFromYaml { unbind };
|
||||
let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
|
||||
let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
|
||||
keys.insert(InputMode::Normal, key_action_unbind.clone());
|
||||
keys.insert(InputMode::Pane, key_action_unbind);
|
||||
let from_yaml = KeybindsFromYaml {
|
||||
keybinds: keys,
|
||||
unbind: Unbind::Keys(vec![Key::Alt('n')]),
|
||||
};
|
||||
|
||||
let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
|
||||
|
||||
let result_normal_n = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Normal)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
let result_normal_l = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Normal)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('l'));
|
||||
let result_pane_n = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Pane)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
let result_pane_l = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Pane)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('l'));
|
||||
|
||||
assert!(result_normal_n.is_none());
|
||||
assert!(result_normal_l.is_none());
|
||||
assert!(result_pane_n.is_none());
|
||||
assert!(result_pane_l.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbind_single_toplevel_multiple_keys_single_mode() {
|
||||
let unbind = Unbind::Keys(vec![
|
||||
Key::Alt('l'),
|
||||
Key::Alt('h'),
|
||||
Key::Alt('j'),
|
||||
Key::Alt('k'),
|
||||
]);
|
||||
let unbind_from_yaml = UnbindFromYaml { unbind };
|
||||
let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
|
||||
let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
|
||||
keys.insert(InputMode::Normal, key_action_unbind.clone());
|
||||
keys.insert(InputMode::Pane, key_action_unbind);
|
||||
let from_yaml = KeybindsFromYaml {
|
||||
keybinds: keys,
|
||||
unbind: Unbind::Keys(vec![Key::Alt('n')]),
|
||||
};
|
||||
|
||||
let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
|
||||
|
||||
let result_normal_n = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Normal)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
let result_normal_l = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Normal)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('l'));
|
||||
let result_normal_k = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Pane)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('k'));
|
||||
let result_normal_h = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Pane)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('h'));
|
||||
|
||||
assert!(result_normal_n.is_none());
|
||||
assert!(result_normal_l.is_none());
|
||||
assert!(result_normal_h.is_none());
|
||||
assert!(result_normal_k.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbind_single_toplevel_multiple_keys_multiple_modes() {
|
||||
let unbind_normal = Unbind::Keys(vec![Key::Alt('l'), Key::Ctrl('p')]);
|
||||
let unbind_from_yaml_normal = UnbindFromYaml {
|
||||
unbind: unbind_normal,
|
||||
};
|
||||
let key_action_unbind_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml_normal)];
|
||||
let unbind = Unbind::Keys(vec![Key::Alt('l'), Key::Alt('k')]);
|
||||
let unbind_from_yaml = UnbindFromYaml { unbind };
|
||||
let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
|
||||
let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
|
||||
keys.insert(InputMode::Normal, key_action_unbind_normal);
|
||||
keys.insert(InputMode::Pane, key_action_unbind);
|
||||
let from_yaml = KeybindsFromYaml {
|
||||
keybinds: keys,
|
||||
unbind: Unbind::Keys(vec![Key::Alt('n')]),
|
||||
};
|
||||
|
||||
let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
|
||||
|
||||
let result_normal_n = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Normal)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
let result_normal_p = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Normal)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Ctrl('p'));
|
||||
let result_normal_l = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Normal)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('l'));
|
||||
let result_pane_p = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Pane)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Ctrl('p'));
|
||||
let result_pane_n = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Pane)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('n'));
|
||||
let result_pane_l = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Pane)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Alt('l'));
|
||||
|
||||
assert!(result_normal_n.is_none());
|
||||
assert!(result_normal_l.is_none());
|
||||
assert!(result_normal_p.is_none());
|
||||
assert!(result_pane_n.is_none());
|
||||
assert!(result_pane_p.is_some());
|
||||
assert!(result_pane_l.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uppercase_and_lowercase_are_distinct() {
|
||||
let key_action_n = KeyActionFromYaml {
|
||||
key: vec![Key::Char('n')],
|
||||
action: vec![Action::NewTab],
|
||||
};
|
||||
let key_action_large_n = KeyActionFromYaml {
|
||||
key: vec![Key::Char('N')],
|
||||
action: vec![Action::NewPane(None)],
|
||||
};
|
||||
|
||||
let key_action_unbind = vec![
|
||||
KeyActionUnbind::KeyAction(key_action_n),
|
||||
KeyActionUnbind::KeyAction(key_action_large_n),
|
||||
];
|
||||
let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
|
||||
keys.insert(InputMode::Normal, key_action_unbind);
|
||||
let from_yaml = KeybindsFromYaml {
|
||||
keybinds: keys,
|
||||
unbind: Unbind::All(false),
|
||||
};
|
||||
|
||||
let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
|
||||
let result_n = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Normal)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Char('n'));
|
||||
let result_large_n = keybinds_from_yaml
|
||||
.0
|
||||
.get(&InputMode::Normal)
|
||||
.expect("ModeKeybinds shouldn't be empty")
|
||||
.0
|
||||
.get(&Key::Char('N'));
|
||||
|
||||
assert!(result_n.is_some());
|
||||
assert!(result_large_n.is_some());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,73 +3,12 @@ pub mod errors;
|
|||
pub mod input;
|
||||
pub mod ipc;
|
||||
pub mod os_input_output;
|
||||
pub mod pty_bus;
|
||||
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;
|
||||
use async_std::task_local;
|
||||
use errors::{get_current_ctx, ErrorContext};
|
||||
use std::cell::RefCell;
|
||||
use std::sync::mpsc;
|
||||
|
||||
/// 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`].
|
||||
static ASYNCOPENCALLS: RefCell<ErrorContext> = RefCell::default()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@ use zellij_tile::data::Palette;
|
|||
|
||||
const UNIX_PERMISSIONS: u32 = 0o700;
|
||||
|
||||
pub fn set_permissions(path: &Path) {
|
||||
let mut permissions = fs::metadata(path).unwrap().permissions();
|
||||
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).unwrap();
|
||||
fs::set_permissions(path, permissions)
|
||||
}
|
||||
|
||||
fn into_raw_mode(pid: RawFd) {
|
||||
|
|
|
|||
|
|
@ -1,23 +1,21 @@
|
|||
use ::async_std::stream::*;
|
||||
use ::async_std::task;
|
||||
use ::async_std::task::*;
|
||||
use ::std::collections::HashMap;
|
||||
use ::std::os::unix::io::RawFd;
|
||||
use ::std::pin::*;
|
||||
use ::std::sync::mpsc::Receiver;
|
||||
use ::std::time::{Duration, Instant};
|
||||
use async_std::stream::*;
|
||||
use async_std::task;
|
||||
use async_std::task::*;
|
||||
use std::collections::HashMap;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::path::PathBuf;
|
||||
use std::pin::*;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use super::SenderWithContext;
|
||||
use crate::client::panes::PaneId;
|
||||
use crate::common::errors::{get_current_ctx, ContextType, PtyContext};
|
||||
use crate::common::screen::ScreenInstruction;
|
||||
use crate::common::thread_bus::{Bus, ThreadSenders};
|
||||
use crate::layout::Layout;
|
||||
use crate::os_input_output::ServerOsApi;
|
||||
use crate::server::ServerInstruction;
|
||||
use crate::utils::logging::debug_to_file;
|
||||
use crate::{
|
||||
errors::{get_current_ctx, ContextType, ErrorContext},
|
||||
panes::PaneId,
|
||||
screen::ScreenInstruction,
|
||||
wasm_vm::PluginInstruction,
|
||||
};
|
||||
use crate::wasm_vm::PluginInstruction;
|
||||
|
||||
pub struct ReadFromPid {
|
||||
pid: RawFd,
|
||||
|
|
@ -79,19 +77,72 @@ pub enum PtyInstruction {
|
|||
Exit,
|
||||
}
|
||||
|
||||
pub struct PtyBus {
|
||||
pub receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>,
|
||||
pub struct Pty {
|
||||
pub bus: Bus<PtyInstruction>,
|
||||
pub id_to_child_pid: HashMap<RawFd, RawFd>,
|
||||
pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
os_input: Box<dyn ServerOsApi>,
|
||||
debug_to_file: bool,
|
||||
task_handles: HashMap<RawFd, JoinHandle<()>>,
|
||||
}
|
||||
|
||||
pub fn pty_thread_main(mut pty: Pty, maybe_layout: Option<Layout>) {
|
||||
loop {
|
||||
let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel");
|
||||
err_ctx.add_call(ContextType::Pty(PtyContext::from(&event)));
|
||||
match event {
|
||||
PtyInstruction::SpawnTerminal(file_to_open) => {
|
||||
let pid = pty.spawn_terminal(file_to_open);
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::NewPane(PaneId::Terminal(pid)))
|
||||
.unwrap();
|
||||
}
|
||||
PtyInstruction::SpawnTerminalVertically(file_to_open) => {
|
||||
let pid = pty.spawn_terminal(file_to_open);
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid)))
|
||||
.unwrap();
|
||||
}
|
||||
PtyInstruction::SpawnTerminalHorizontally(file_to_open) => {
|
||||
let pid = pty.spawn_terminal(file_to_open);
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid)))
|
||||
.unwrap();
|
||||
}
|
||||
PtyInstruction::NewTab => {
|
||||
if let Some(layout) = maybe_layout.clone() {
|
||||
pty.spawn_terminals_for_layout(layout);
|
||||
} else {
|
||||
let pid = pty.spawn_terminal(None);
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::NewTab(pid))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
PtyInstruction::ClosePane(id) => {
|
||||
pty.close_pane(id);
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_server(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
PtyInstruction::CloseTab(ids) => {
|
||||
pty.close_tab(ids);
|
||||
pty.bus
|
||||
.senders
|
||||
.send_to_server(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
PtyInstruction::Exit => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn stream_terminal_bytes(
|
||||
pid: RawFd,
|
||||
send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||
senders: ThreadSenders,
|
||||
os_input: Box<dyn ServerOsApi>,
|
||||
debug: bool,
|
||||
) -> JoinHandle<()> {
|
||||
|
|
@ -113,7 +164,7 @@ fn stream_terminal_bytes(
|
|||
}
|
||||
}
|
||||
if !bytes_is_empty {
|
||||
let _ = send_screen_instructions.send(ScreenInstruction::PtyBytes(pid, bytes));
|
||||
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
|
||||
|
|
@ -124,9 +175,7 @@ fn stream_terminal_bytes(
|
|||
Some(receive_time) => {
|
||||
if receive_time.elapsed() > max_render_pause {
|
||||
pending_render = false;
|
||||
send_screen_instructions
|
||||
.send(ScreenInstruction::Render)
|
||||
.unwrap();
|
||||
let _ = senders.send_to_screen(ScreenInstruction::Render);
|
||||
last_byte_receive_time = Some(Instant::now());
|
||||
} else {
|
||||
pending_render = true;
|
||||
|
|
@ -140,53 +189,44 @@ fn stream_terminal_bytes(
|
|||
} else {
|
||||
if pending_render {
|
||||
pending_render = false;
|
||||
send_screen_instructions
|
||||
.send(ScreenInstruction::Render)
|
||||
.unwrap();
|
||||
let _ = senders.send_to_screen(ScreenInstruction::Render);
|
||||
}
|
||||
last_byte_receive_time = None;
|
||||
task::sleep(::std::time::Duration::from_millis(10)).await;
|
||||
}
|
||||
}
|
||||
send_screen_instructions
|
||||
.send(ScreenInstruction::Render)
|
||||
.unwrap();
|
||||
senders.send_to_screen(ScreenInstruction::Render).unwrap();
|
||||
#[cfg(not(test))]
|
||||
// this is a little hacky, and is because the tests end the file as soon as
|
||||
// we read everything, rather than hanging until there is new data
|
||||
// a better solution would be to fix the test fakes, but this will do for now
|
||||
send_screen_instructions
|
||||
.send(ScreenInstruction::ClosePane(PaneId::Terminal(pid)))
|
||||
senders
|
||||
.send_to_screen(ScreenInstruction::ClosePane(PaneId::Terminal(pid)))
|
||||
.unwrap();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
impl PtyBus {
|
||||
pub fn new(
|
||||
receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>,
|
||||
send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
os_input: Box<dyn ServerOsApi>,
|
||||
debug_to_file: bool,
|
||||
) -> Self {
|
||||
PtyBus {
|
||||
receive_pty_instructions,
|
||||
os_input,
|
||||
impl Pty {
|
||||
pub fn new(bus: Bus<PtyInstruction>, debug_to_file: bool) -> Self {
|
||||
Pty {
|
||||
bus,
|
||||
id_to_child_pid: HashMap::new(),
|
||||
send_screen_instructions,
|
||||
send_plugin_instructions,
|
||||
debug_to_file,
|
||||
task_handles: HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> RawFd {
|
||||
let (pid_primary, pid_secondary): (RawFd, RawFd) =
|
||||
self.os_input.spawn_terminal(file_to_open);
|
||||
let (pid_primary, pid_secondary): (RawFd, RawFd) = self
|
||||
.bus
|
||||
.os_input
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.spawn_terminal(file_to_open);
|
||||
let task_handle = stream_terminal_bytes(
|
||||
pid_primary,
|
||||
self.send_screen_instructions.clone(),
|
||||
self.os_input.clone(),
|
||||
self.bus.senders.clone(),
|
||||
self.bus.os_input.as_ref().unwrap().clone(),
|
||||
self.debug_to_file,
|
||||
);
|
||||
self.task_handles.insert(pid_primary, task_handle);
|
||||
|
|
@ -197,12 +237,14 @@ impl PtyBus {
|
|||
let total_panes = layout.total_terminal_panes();
|
||||
let mut new_pane_pids = vec![];
|
||||
for _ in 0..total_panes {
|
||||
let (pid_primary, pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal(None);
|
||||
let (pid_primary, pid_secondary): (RawFd, RawFd) =
|
||||
self.bus.os_input.as_mut().unwrap().spawn_terminal(None);
|
||||
self.id_to_child_pid.insert(pid_primary, pid_secondary);
|
||||
new_pane_pids.push(pid_primary);
|
||||
}
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::ApplyLayout(
|
||||
self.bus
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::ApplyLayout(
|
||||
layout,
|
||||
new_pane_pids.clone(),
|
||||
))
|
||||
|
|
@ -210,8 +252,8 @@ impl PtyBus {
|
|||
for id in new_pane_pids {
|
||||
let task_handle = stream_terminal_bytes(
|
||||
id,
|
||||
self.send_screen_instructions.clone(),
|
||||
self.os_input.clone(),
|
||||
self.bus.senders.clone(),
|
||||
self.bus.os_input.as_ref().unwrap().clone(),
|
||||
self.debug_to_file,
|
||||
);
|
||||
self.task_handles.insert(id, task_handle);
|
||||
|
|
@ -222,15 +264,16 @@ impl PtyBus {
|
|||
PaneId::Terminal(id) => {
|
||||
let child_pid = self.id_to_child_pid.remove(&id).unwrap();
|
||||
let handle = self.task_handles.remove(&id).unwrap();
|
||||
self.os_input.kill(child_pid).unwrap();
|
||||
self.bus.os_input.as_mut().unwrap().kill(child_pid).unwrap();
|
||||
task::block_on(async {
|
||||
handle.cancel().await;
|
||||
});
|
||||
}
|
||||
PaneId::Plugin(pid) => self
|
||||
.send_plugin_instructions
|
||||
.send(PluginInstruction::Unload(pid))
|
||||
.unwrap(),
|
||||
PaneId::Plugin(pid) => drop(
|
||||
self.bus
|
||||
.senders
|
||||
.send_to_plugin(PluginInstruction::Unload(pid)),
|
||||
),
|
||||
}
|
||||
}
|
||||
pub fn close_tab(&mut self, ids: Vec<PaneId>) {
|
||||
|
|
@ -240,7 +283,7 @@ impl PtyBus {
|
|||
}
|
||||
}
|
||||
|
||||
impl Drop for PtyBus {
|
||||
impl Drop for Pty {
|
||||
fn drop(&mut self) {
|
||||
let child_ids: Vec<RawFd> = self.id_to_child_pid.keys().copied().collect();
|
||||
for id in child_ids {
|
||||
|
|
@ -3,18 +3,19 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::str;
|
||||
use std::sync::mpsc::Receiver;
|
||||
|
||||
use crate::common::SenderWithContext;
|
||||
use crate::os_input_output::ServerOsApi;
|
||||
use crate::common::input::options::Options;
|
||||
use crate::common::pty::{PtyInstruction, VteBytes};
|
||||
use crate::common::thread_bus::Bus;
|
||||
use crate::errors::{ContextType, ScreenContext};
|
||||
use crate::layout::Layout;
|
||||
use crate::panes::PaneId;
|
||||
use crate::panes::PositionAndSize;
|
||||
use crate::pty_bus::{PtyInstruction, VteBytes};
|
||||
use crate::server::ServerInstruction;
|
||||
use crate::tab::Tab;
|
||||
use crate::{errors::ErrorContext, wasm_vm::PluginInstruction};
|
||||
use crate::{layout::Layout, panes::PaneId};
|
||||
use crate::wasm_vm::PluginInstruction;
|
||||
|
||||
use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, TabInfo};
|
||||
use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, PluginCapabilities, TabInfo};
|
||||
|
||||
/// Instructions that can be sent to the [`Screen`].
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -33,9 +34,11 @@ pub enum ScreenInstruction {
|
|||
FocusNextPane,
|
||||
FocusPreviousPane,
|
||||
MoveFocusLeft,
|
||||
MoveFocusLeftOrPreviousTab,
|
||||
MoveFocusDown,
|
||||
MoveFocusUp,
|
||||
MoveFocusRight,
|
||||
MoveFocusRightOrNextTab,
|
||||
Exit,
|
||||
ScrollUp,
|
||||
ScrollDown,
|
||||
|
|
@ -52,7 +55,7 @@ pub enum ScreenInstruction {
|
|||
NewTab(RawFd),
|
||||
SwitchTabNext,
|
||||
SwitchTabPrev,
|
||||
ToggleActiveSyncPanes,
|
||||
ToggleActiveSyncTab,
|
||||
CloseTab,
|
||||
GoToTab(u32),
|
||||
UpdateTabName(Vec<u8>),
|
||||
|
|
@ -63,55 +66,37 @@ pub enum ScreenInstruction {
|
|||
/// A [`Screen`] holds multiple [`Tab`]s, each one holding multiple [`panes`](crate::client::panes).
|
||||
/// It only directly controls which tab is active, delegating the rest to the individual `Tab`.
|
||||
pub struct Screen {
|
||||
/// A [`ScreenInstruction`] and [`ErrorContext`] receiver.
|
||||
pub receiver: Receiver<(ScreenInstruction, ErrorContext)>,
|
||||
/// A Bus for sending and receiving messages with the other threads.
|
||||
pub bus: Bus<ScreenInstruction>,
|
||||
/// An optional maximal amount of panes allowed per [`Tab`] in this [`Screen`] instance.
|
||||
max_panes: Option<usize>,
|
||||
/// A map between this [`Screen`]'s tabs and their ID/key.
|
||||
tabs: BTreeMap<usize, Tab>,
|
||||
/// A [`PluginInstruction`] and [`ErrorContext`] sender.
|
||||
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
/// An [`PtyInstruction`] and [`ErrorContext`] sender.
|
||||
pub send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
/// An [`ServerInstruction`] and [`ErrorContext`] sender.
|
||||
pub send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||
/// The full size of this [`Screen`].
|
||||
full_screen_ws: PositionAndSize,
|
||||
/// The index of this [`Screen`]'s active [`Tab`].
|
||||
active_tab_index: Option<usize>,
|
||||
/// The [`ServerOsApi`] this [`Screen`] uses.
|
||||
os_api: Box<dyn ServerOsApi>,
|
||||
mode_info: ModeInfo,
|
||||
input_mode: InputMode,
|
||||
colors: Palette,
|
||||
}
|
||||
|
||||
impl Screen {
|
||||
// FIXME: This lint needs actual fixing! Maybe by bundling the Senders
|
||||
/// Creates and returns a new [`Screen`].
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
receive_screen_instructions: Receiver<(ScreenInstruction, ErrorContext)>,
|
||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||
bus: Bus<ScreenInstruction>,
|
||||
full_screen_ws: &PositionAndSize,
|
||||
os_api: Box<dyn ServerOsApi>,
|
||||
max_panes: Option<usize>,
|
||||
mode_info: ModeInfo,
|
||||
input_mode: InputMode,
|
||||
colors: Palette,
|
||||
) -> Self {
|
||||
Screen {
|
||||
receiver: receive_screen_instructions,
|
||||
bus,
|
||||
max_panes,
|
||||
send_plugin_instructions,
|
||||
send_pty_instructions,
|
||||
send_server_instructions,
|
||||
full_screen_ws: *full_screen_ws,
|
||||
active_tab_index: None,
|
||||
tabs: BTreeMap::new(),
|
||||
os_api,
|
||||
mode_info,
|
||||
input_mode,
|
||||
colors,
|
||||
|
|
@ -128,10 +113,8 @@ impl Screen {
|
|||
position,
|
||||
String::new(),
|
||||
&self.full_screen_ws,
|
||||
self.os_api.clone(),
|
||||
self.send_plugin_instructions.clone(),
|
||||
self.send_pty_instructions.clone(),
|
||||
self.send_server_instructions.clone(),
|
||||
self.bus.os_input.as_ref().unwrap().clone(),
|
||||
self.bus.senders.clone(),
|
||||
self.max_panes,
|
||||
Some(PaneId::Terminal(pane_id)),
|
||||
self.mode_info.clone(),
|
||||
|
|
@ -215,13 +198,15 @@ impl Screen {
|
|||
// below we don't check the result of sending the CloseTab instruction to the pty thread
|
||||
// because this might be happening when the app is closing, at which point the pty thread
|
||||
// has already closed and this would result in an error
|
||||
self.send_pty_instructions
|
||||
.send(PtyInstruction::CloseTab(pane_ids))
|
||||
self.bus
|
||||
.senders
|
||||
.send_to_pty(PtyInstruction::CloseTab(pane_ids))
|
||||
.unwrap();
|
||||
if self.tabs.is_empty() {
|
||||
self.active_tab_index = None;
|
||||
self.send_server_instructions
|
||||
.send(ServerInstruction::Render(None))
|
||||
self.bus
|
||||
.senders
|
||||
.send_to_server(ServerInstruction::Render(None))
|
||||
.unwrap();
|
||||
} else {
|
||||
for t in self.tabs.values_mut() {
|
||||
|
|
@ -284,10 +269,8 @@ impl Screen {
|
|||
position,
|
||||
String::new(),
|
||||
&self.full_screen_ws,
|
||||
self.os_api.clone(),
|
||||
self.send_plugin_instructions.clone(),
|
||||
self.send_pty_instructions.clone(),
|
||||
self.send_server_instructions.clone(),
|
||||
self.bus.os_input.as_ref().unwrap().clone(),
|
||||
self.bus.senders.clone(),
|
||||
self.max_panes,
|
||||
None,
|
||||
self.mode_info.clone(),
|
||||
|
|
@ -311,8 +294,9 @@ impl Screen {
|
|||
is_sync_panes_active: tab.is_sync_panes_active(),
|
||||
});
|
||||
}
|
||||
self.send_plugin_instructions
|
||||
.send(PluginInstruction::Update(None, Event::TabUpdate(tab_data)))
|
||||
self.bus
|
||||
.senders
|
||||
.send_to_plugin(PluginInstruction::Update(None, Event::TabUpdate(tab_data)))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
|
|
@ -340,3 +324,272 @@ impl Screen {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn screen_thread_main(
|
||||
bus: Bus<ScreenInstruction>,
|
||||
max_panes: Option<usize>,
|
||||
full_screen_ws: PositionAndSize,
|
||||
config_options: Options,
|
||||
) {
|
||||
let colors = bus.os_input.as_ref().unwrap().load_palette();
|
||||
let capabilities = config_options.simplified_ui;
|
||||
|
||||
let mut screen = Screen::new(
|
||||
bus,
|
||||
&full_screen_ws,
|
||||
max_panes,
|
||||
ModeInfo {
|
||||
palette: colors,
|
||||
capabilities: PluginCapabilities {
|
||||
arrow_fonts: capabilities,
|
||||
},
|
||||
..ModeInfo::default()
|
||||
},
|
||||
InputMode::Normal,
|
||||
colors,
|
||||
);
|
||||
loop {
|
||||
let (event, mut err_ctx) = screen
|
||||
.bus
|
||||
.recv()
|
||||
.expect("failed to receive event on channel");
|
||||
err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event)));
|
||||
match event {
|
||||
ScreenInstruction::PtyBytes(pid, vte_bytes) => {
|
||||
let active_tab = screen.get_active_tab_mut().unwrap();
|
||||
if active_tab.has_terminal_pid(pid) {
|
||||
// it's most likely that this event is directed at the active tab
|
||||
// look there first
|
||||
active_tab.handle_pty_bytes(pid, vte_bytes);
|
||||
} else {
|
||||
// if this event wasn't directed at the active tab, start looking
|
||||
// in other tabs
|
||||
let all_tabs = screen.get_tabs_mut();
|
||||
for tab in all_tabs.values_mut() {
|
||||
if tab.has_terminal_pid(pid) {
|
||||
tab.handle_pty_bytes(pid, vte_bytes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ScreenInstruction::Render => {
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::NewPane(pid) => {
|
||||
screen.get_active_tab_mut().unwrap().new_pane(pid);
|
||||
screen
|
||||
.bus
|
||||
.senders
|
||||
.send_to_server(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::HorizontalSplit(pid) => {
|
||||
screen.get_active_tab_mut().unwrap().horizontal_split(pid);
|
||||
screen
|
||||
.bus
|
||||
.senders
|
||||
.send_to_server(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::VerticalSplit(pid) => {
|
||||
screen.get_active_tab_mut().unwrap().vertical_split(pid);
|
||||
screen
|
||||
.bus
|
||||
.senders
|
||||
.send_to_server(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::WriteCharacter(bytes) => {
|
||||
let active_tab = screen.get_active_tab_mut().unwrap();
|
||||
match active_tab.is_sync_panes_active() {
|
||||
true => active_tab.write_to_terminals_on_current_tab(bytes),
|
||||
false => active_tab.write_to_active_terminal(bytes),
|
||||
}
|
||||
}
|
||||
ScreenInstruction::ResizeLeft => {
|
||||
screen.get_active_tab_mut().unwrap().resize_left();
|
||||
}
|
||||
ScreenInstruction::ResizeRight => {
|
||||
screen.get_active_tab_mut().unwrap().resize_right();
|
||||
}
|
||||
ScreenInstruction::ResizeDown => {
|
||||
screen.get_active_tab_mut().unwrap().resize_down();
|
||||
}
|
||||
ScreenInstruction::ResizeUp => {
|
||||
screen.get_active_tab_mut().unwrap().resize_up();
|
||||
}
|
||||
ScreenInstruction::SwitchFocus => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus();
|
||||
}
|
||||
ScreenInstruction::FocusNextPane => {
|
||||
screen.get_active_tab_mut().unwrap().focus_next_pane();
|
||||
}
|
||||
ScreenInstruction::FocusPreviousPane => {
|
||||
screen.get_active_tab_mut().unwrap().focus_previous_pane();
|
||||
}
|
||||
ScreenInstruction::MoveFocusLeft => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus_left();
|
||||
}
|
||||
ScreenInstruction::MoveFocusLeftOrPreviousTab => {
|
||||
if !screen.get_active_tab_mut().unwrap().move_focus_left() {
|
||||
screen.switch_tab_prev();
|
||||
}
|
||||
screen
|
||||
.bus
|
||||
.senders
|
||||
.send_to_server(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::MoveFocusDown => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus_down();
|
||||
}
|
||||
ScreenInstruction::MoveFocusRight => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus_right();
|
||||
}
|
||||
ScreenInstruction::MoveFocusRightOrNextTab => {
|
||||
if !screen.get_active_tab_mut().unwrap().move_focus_right() {
|
||||
screen.switch_tab_next();
|
||||
}
|
||||
screen
|
||||
.bus
|
||||
.senders
|
||||
.send_to_server(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::MoveFocusUp => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus_up();
|
||||
}
|
||||
ScreenInstruction::ScrollUp => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.scroll_active_terminal_up();
|
||||
}
|
||||
ScreenInstruction::ScrollDown => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.scroll_active_terminal_down();
|
||||
}
|
||||
ScreenInstruction::PageScrollUp => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.scroll_active_terminal_up_page();
|
||||
}
|
||||
ScreenInstruction::PageScrollDown => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.scroll_active_terminal_down_page();
|
||||
}
|
||||
ScreenInstruction::ClearScroll => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.clear_active_terminal_scroll();
|
||||
}
|
||||
ScreenInstruction::CloseFocusedPane => {
|
||||
screen.get_active_tab_mut().unwrap().close_focused_pane();
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::SetSelectable(id, selectable) => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.set_pane_selectable(id, selectable);
|
||||
}
|
||||
ScreenInstruction::SetMaxHeight(id, max_height) => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.set_pane_max_height(id, max_height);
|
||||
}
|
||||
ScreenInstruction::SetInvisibleBorders(id, invisible_borders) => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.set_pane_invisible_borders(id, invisible_borders);
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::ClosePane(id) => {
|
||||
screen.get_active_tab_mut().unwrap().close_pane(id);
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::ToggleActiveTerminalFullscreen => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.toggle_active_pane_fullscreen();
|
||||
}
|
||||
ScreenInstruction::NewTab(pane_id) => {
|
||||
screen.new_tab(pane_id);
|
||||
screen
|
||||
.bus
|
||||
.senders
|
||||
.send_to_server(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::SwitchTabNext => {
|
||||
screen.switch_tab_next();
|
||||
screen
|
||||
.bus
|
||||
.senders
|
||||
.send_to_server(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::SwitchTabPrev => {
|
||||
screen.switch_tab_prev();
|
||||
screen
|
||||
.bus
|
||||
.senders
|
||||
.send_to_server(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::CloseTab => {
|
||||
screen.close_tab();
|
||||
screen
|
||||
.bus
|
||||
.senders
|
||||
.send_to_server(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::ApplyLayout(layout, new_pane_pids) => {
|
||||
screen.apply_layout(layout, new_pane_pids);
|
||||
screen
|
||||
.bus
|
||||
.senders
|
||||
.send_to_server(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::GoToTab(tab_index) => {
|
||||
screen.go_to_tab(tab_index as usize);
|
||||
screen
|
||||
.bus
|
||||
.senders
|
||||
.send_to_server(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::UpdateTabName(c) => {
|
||||
screen.update_active_tab_name(c);
|
||||
}
|
||||
ScreenInstruction::TerminalResize(new_size) => {
|
||||
screen.resize_to_screen(new_size);
|
||||
}
|
||||
ScreenInstruction::ChangeMode(mode_info) => {
|
||||
screen.change_mode(mode_info);
|
||||
}
|
||||
ScreenInstruction::ToggleActiveSyncTab => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.toggle_sync_panes_is_active();
|
||||
screen.update_tabs();
|
||||
}
|
||||
ScreenInstruction::Exit => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use directories_next::BaseDirs;
|
|||
use std::io::Write;
|
||||
use std::{fs, path::Path, path::PathBuf};
|
||||
|
||||
const CONFIG_LOCATION: &str = "/.config/zellij";
|
||||
const CONFIG_LOCATION: &str = ".config/zellij";
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! asset_map {
|
||||
|
|
@ -44,7 +44,7 @@ pub mod install {
|
|||
let path = data_dir.join(path);
|
||||
let parent_path = path.parent().unwrap();
|
||||
fs::create_dir_all(parent_path).unwrap();
|
||||
set_permissions(parent_path);
|
||||
set_permissions(parent_path).unwrap();
|
||||
if out_of_date || !path.exists() {
|
||||
fs::write(path, bytes).expect("Failed to install default assets!");
|
||||
}
|
||||
|
|
@ -57,8 +57,8 @@ pub mod install {
|
|||
/// existing config directory, returns the first match
|
||||
pub fn find_default_config_dir() -> Option<PathBuf> {
|
||||
vec![
|
||||
Some(xdg_config_dir()),
|
||||
home_config_dir(),
|
||||
Some(xdg_config_dir()),
|
||||
Some(Path::new(SYSTEM_DEFAULT_CONFIG_DIR).to_path_buf()),
|
||||
]
|
||||
.into_iter()
|
||||
|
|
|
|||
142
src/common/thread_bus.rs
Normal file
142
src/common/thread_bus.rs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
//! 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ lazy_static! {
|
|||
);
|
||||
ipc_dir.push(VERSION);
|
||||
fs::create_dir_all(&ipc_dir).unwrap();
|
||||
set_permissions(&ipc_dir);
|
||||
set_permissions(&ipc_dir).unwrap();
|
||||
ipc_dir.push(&*SESSION_NAME);
|
||||
ipc_dir
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@ use std::{
|
|||
use crate::os_input_output::set_permissions;
|
||||
use crate::utils::consts::{ZELLIJ_TMP_LOG_DIR, ZELLIJ_TMP_LOG_FILE};
|
||||
|
||||
pub fn atomic_create_file(file_name: &Path) {
|
||||
let _ = fs::OpenOptions::new().create(true).open(file_name);
|
||||
#[cfg(not(test))]
|
||||
set_permissions(file_name);
|
||||
pub fn atomic_create_file(file_name: &Path) -> io::Result<()> {
|
||||
let _ = fs::OpenOptions::new()
|
||||
.append(true)
|
||||
.create(true)
|
||||
.open(file_name)?;
|
||||
set_permissions(file_name)
|
||||
}
|
||||
|
||||
pub fn atomic_create_dir(dir_name: &Path) -> io::Result<()> {
|
||||
|
|
@ -27,7 +29,7 @@ pub fn atomic_create_dir(dir_name: &Path) -> io::Result<()> {
|
|||
Ok(())
|
||||
};
|
||||
if result.is_ok() {
|
||||
set_permissions(dir_name);
|
||||
set_permissions(dir_name)?;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
|
@ -38,10 +40,9 @@ pub fn debug_log_to_file(mut message: String) -> io::Result<()> {
|
|||
}
|
||||
|
||||
pub fn debug_log_to_file_without_newline(message: String) -> io::Result<()> {
|
||||
atomic_create_file(&*ZELLIJ_TMP_LOG_FILE);
|
||||
atomic_create_file(&*ZELLIJ_TMP_LOG_FILE)?;
|
||||
let mut file = fs::OpenOptions::new()
|
||||
.append(true)
|
||||
.create(true)
|
||||
.open(&*ZELLIJ_TMP_LOG_FILE)?;
|
||||
file.write_all(message.as_bytes())
|
||||
}
|
||||
|
|
@ -78,6 +79,7 @@ pub fn debug_to_file(message: u8, pid: RawFd) -> io::Result<()> {
|
|||
let mut file = fs::OpenOptions::new()
|
||||
.append(true)
|
||||
.create(true)
|
||||
.open(path)?;
|
||||
.open(&path)?;
|
||||
set_permissions(&path)?;
|
||||
file.write_all(&[message])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,25 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{mpsc::Sender, Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
path::PathBuf,
|
||||
process,
|
||||
sync::{mpsc::Sender, Arc, Mutex},
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
use wasmer::{
|
||||
imports, ChainableNamedResolver, Function, ImportObject, Instance, Module, Store, Value,
|
||||
WasmerEnv,
|
||||
};
|
||||
use wasmer::{imports, Function, ImportObject, Store, WasmerEnv};
|
||||
use wasmer_wasi::WasiEnv;
|
||||
use wasmer_wasi::{Pipe, WasiEnv, WasiState};
|
||||
use zellij_tile::data::{Event, EventType, PluginIds};
|
||||
|
||||
use super::{pty_bus::PtyInstruction, screen::ScreenInstruction, PaneId, SenderWithContext};
|
||||
use crate::common::errors::{ContextType, PluginContext};
|
||||
use crate::common::pty::PtyInstruction;
|
||||
use crate::common::screen::ScreenInstruction;
|
||||
use crate::common::thread_bus::{Bus, ThreadSenders};
|
||||
use crate::common::PaneId;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PluginInstruction {
|
||||
|
|
@ -25,14 +33,97 @@ pub enum PluginInstruction {
|
|||
#[derive(WasmerEnv, Clone)]
|
||||
pub struct PluginEnv {
|
||||
pub plugin_id: u32,
|
||||
// FIXME: This should be a big bundle of all of the channels
|
||||
pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||
pub send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
pub senders: ThreadSenders,
|
||||
pub wasi_env: WasiEnv,
|
||||
pub subscriptions: Arc<Mutex<HashSet<EventType>>>,
|
||||
}
|
||||
|
||||
// Thread main --------------------------------------------------------------------------------------------------------
|
||||
pub fn wasm_thread_main(bus: Bus<PluginInstruction>, store: Store, data_dir: PathBuf) {
|
||||
let mut plugin_id = 0;
|
||||
let mut plugin_map = HashMap::new();
|
||||
loop {
|
||||
let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel");
|
||||
err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event)));
|
||||
match event {
|
||||
PluginInstruction::Load(pid_tx, path) => {
|
||||
let plugin_dir = data_dir.join("plugins/");
|
||||
let wasm_bytes = fs::read(&path)
|
||||
.or_else(|_| fs::read(&path.with_extension("wasm")))
|
||||
.or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm")))
|
||||
.unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display()));
|
||||
|
||||
// FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that
|
||||
let module = Module::new(&store, &wasm_bytes).unwrap();
|
||||
|
||||
let output = Pipe::new();
|
||||
let input = Pipe::new();
|
||||
let mut wasi_env = WasiState::new("Zellij")
|
||||
.env("CLICOLOR_FORCE", "1")
|
||||
.preopen(|p| {
|
||||
p.directory(".") // FIXME: Change this to a more meaningful dir
|
||||
.alias(".")
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
})
|
||||
.unwrap()
|
||||
.stdin(Box::new(input))
|
||||
.stdout(Box::new(output))
|
||||
.finalize()
|
||||
.unwrap();
|
||||
|
||||
let wasi = wasi_env.import_object(&module).unwrap();
|
||||
|
||||
let plugin_env = PluginEnv {
|
||||
plugin_id,
|
||||
senders: bus.senders.clone(),
|
||||
wasi_env,
|
||||
subscriptions: Arc::new(Mutex::new(HashSet::new())),
|
||||
};
|
||||
|
||||
let zellij = zellij_exports(&store, &plugin_env);
|
||||
let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap();
|
||||
|
||||
let start = instance.exports.get_function("_start").unwrap();
|
||||
|
||||
// This eventually calls the `.load()` method
|
||||
start.call(&[]).unwrap();
|
||||
|
||||
plugin_map.insert(plugin_id, (instance, plugin_env));
|
||||
pid_tx.send(plugin_id).unwrap();
|
||||
plugin_id += 1;
|
||||
}
|
||||
PluginInstruction::Update(pid, event) => {
|
||||
for (&i, (instance, plugin_env)) in &plugin_map {
|
||||
let subs = plugin_env.subscriptions.lock().unwrap();
|
||||
// FIXME: This is very janky... Maybe I should write my own macro for Event -> EventType?
|
||||
let event_type = EventType::from_str(&event.to_string()).unwrap();
|
||||
if (pid.is_none() || pid == Some(i)) && subs.contains(&event_type) {
|
||||
let update = instance.exports.get_function("update").unwrap();
|
||||
wasi_write_object(&plugin_env.wasi_env, &event);
|
||||
update.call(&[]).unwrap();
|
||||
}
|
||||
}
|
||||
drop(bus.senders.send_to_screen(ScreenInstruction::Render));
|
||||
}
|
||||
PluginInstruction::Render(buf_tx, pid, rows, cols) => {
|
||||
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
|
||||
|
||||
let render = instance.exports.get_function("render").unwrap();
|
||||
|
||||
render
|
||||
.call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
|
||||
.unwrap();
|
||||
|
||||
buf_tx.send(wasi_read_string(&plugin_env.wasi_env)).unwrap();
|
||||
}
|
||||
PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
|
||||
PluginInstruction::Exit => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin API ---------------------------------------------------------------------------------------------------------
|
||||
|
||||
pub fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObject {
|
||||
|
|
@ -74,8 +165,8 @@ fn host_unsubscribe(plugin_env: &PluginEnv) {
|
|||
fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) {
|
||||
let selectable = selectable != 0;
|
||||
plugin_env
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::SetSelectable(
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::SetSelectable(
|
||||
PaneId::Plugin(plugin_env.plugin_id),
|
||||
selectable,
|
||||
))
|
||||
|
|
@ -85,8 +176,8 @@ fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) {
|
|||
fn host_set_max_height(plugin_env: &PluginEnv, max_height: i32) {
|
||||
let max_height = max_height as usize;
|
||||
plugin_env
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::SetMaxHeight(
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::SetMaxHeight(
|
||||
PaneId::Plugin(plugin_env.plugin_id),
|
||||
max_height,
|
||||
))
|
||||
|
|
@ -96,8 +187,8 @@ fn host_set_max_height(plugin_env: &PluginEnv, max_height: i32) {
|
|||
fn host_set_invisible_borders(plugin_env: &PluginEnv, invisible_borders: i32) {
|
||||
let invisible_borders = invisible_borders != 0;
|
||||
plugin_env
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::SetInvisibleBorders(
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::SetInvisibleBorders(
|
||||
PaneId::Plugin(plugin_env.plugin_id),
|
||||
invisible_borders,
|
||||
))
|
||||
|
|
@ -115,8 +206,8 @@ fn host_get_plugin_ids(plugin_env: &PluginEnv) {
|
|||
fn host_open_file(plugin_env: &PluginEnv) {
|
||||
let path: PathBuf = wasi_read_object(&plugin_env.wasi_env);
|
||||
plugin_env
|
||||
.send_pty_instructions
|
||||
.send(PtyInstruction::SpawnTerminal(Some(path)))
|
||||
.senders
|
||||
.send_to_pty(PtyInstruction::SpawnTerminal(Some(path)))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
|
|
@ -130,7 +221,7 @@ fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) {
|
|||
// timers as we'd like.
|
||||
//
|
||||
// But that's a lot of code, and this is a few lines:
|
||||
let send_plugin_instructions = plugin_env.send_plugin_instructions.clone();
|
||||
let send_plugin_instructions = plugin_env.senders.to_plugin.clone();
|
||||
let update_target = Some(plugin_env.plugin_id);
|
||||
thread::spawn(move || {
|
||||
let start_time = Instant::now();
|
||||
|
|
@ -140,6 +231,7 @@ fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) {
|
|||
let elapsed_time = Instant::now().duration_since(start_time).as_secs_f64();
|
||||
|
||||
send_plugin_instructions
|
||||
.unwrap()
|
||||
.send(PluginInstruction::Update(
|
||||
update_target,
|
||||
Event::Timer(elapsed_time),
|
||||
|
|
|
|||
21
src/main.rs
21
src/main.rs
|
|
@ -6,15 +6,13 @@ mod server;
|
|||
mod tests;
|
||||
|
||||
use client::{boundaries, layout, panes, start_client, tab};
|
||||
use common::{
|
||||
command_is_executing, errors, os_input_output, pty_bus, screen, setup, utils, wasm_vm,
|
||||
};
|
||||
use common::{command_is_executing, errors, os_input_output, pty, screen, 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;
|
||||
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},
|
||||
|
|
@ -31,6 +29,8 @@ pub fn main() {
|
|||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let config_options = Options::from_cli(&config.options, opts.option.clone());
|
||||
|
||||
if let Some(crate::cli::ConfigCli::GenerateCompletion { shell }) = opts.option {
|
||||
let shell = match shell.as_ref() {
|
||||
"bash" => structopt::clap::Shell::Bash,
|
||||
|
|
@ -47,13 +47,19 @@ pub fn main() {
|
|||
CliArgs::clap().gen_completions_to("zellij", shell, &mut out);
|
||||
} else if let Some(crate::cli::ConfigCli::Setup { .. }) = opts.option {
|
||||
setup::dump_default_config().expect("Failed to print to stdout");
|
||||
std::process::exit(1);
|
||||
std::process::exit(0);
|
||||
} else {
|
||||
atomic_create_dir(&*ZELLIJ_TMP_DIR).unwrap();
|
||||
atomic_create_dir(&*ZELLIJ_TMP_LOG_DIR).unwrap();
|
||||
let server_os_input = get_server_os_input();
|
||||
let os_input = get_client_os_input();
|
||||
start(Box::new(os_input), opts, Box::new(server_os_input), config);
|
||||
start(
|
||||
Box::new(os_input),
|
||||
opts,
|
||||
Box::new(server_os_input),
|
||||
config,
|
||||
config_options,
|
||||
);
|
||||
}
|
||||
}
|
||||
pub fn start(
|
||||
|
|
@ -61,8 +67,9 @@ pub fn start(
|
|||
opts: CliArgs,
|
||||
server_os_input: Box<dyn ServerOsApi>,
|
||||
config: Config,
|
||||
config_options: Options,
|
||||
) {
|
||||
let ipc_thread = start_server(server_os_input);
|
||||
let ipc_thread = start_server(server_os_input, config_options);
|
||||
start_client(client_os_input, opts, config);
|
||||
drop(ipc_thread.join());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,52 +1,45 @@
|
|||
pub mod route;
|
||||
|
||||
use interprocess::local_socket::LocalSocketListener;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
use std::{collections::HashMap, fs};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
str::FromStr,
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
};
|
||||
use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
|
||||
use wasmer_wasi::{Pipe, WasiState};
|
||||
use zellij_tile::data::{Event, EventType, InputMode, ModeInfo};
|
||||
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, PluginContext, PtyContext, ScreenContext, ServerContext},
|
||||
input::actions::{Action, Direction},
|
||||
input::handler::get_mode_info,
|
||||
errors::{ContextType, ServerContext},
|
||||
input::{actions::Action, options::Options},
|
||||
os_input_output::{set_permissions, ServerOsApi},
|
||||
pty_bus::{PtyBus, PtyInstruction},
|
||||
screen::{Screen, ScreenInstruction},
|
||||
pty::{pty_thread_main, Pty, PtyInstruction},
|
||||
screen::{screen_thread_main, ScreenInstruction},
|
||||
setup::{get_default_data_dir, install::populate_data_dir},
|
||||
utils::consts::ZELLIJ_IPC_PIPE,
|
||||
wasm_vm::{wasi_read_string, wasi_write_object, zellij_exports, PluginEnv, PluginInstruction},
|
||||
ChannelWithContext, SenderType, SenderWithContext,
|
||||
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::PaneId;
|
||||
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),
|
||||
NewClient(PositionAndSize, CliArgs, Options),
|
||||
Action(Action),
|
||||
Render(Option<String>),
|
||||
UnblockInputThread,
|
||||
ClientExit,
|
||||
}
|
||||
|
||||
struct SessionMetaData {
|
||||
pub send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
pub struct SessionMetaData {
|
||||
pub senders: ThreadSenders,
|
||||
screen_thread: Option<thread::JoinHandle<()>>,
|
||||
pty_thread: Option<thread::JoinHandle<()>>,
|
||||
wasm_thread: Option<thread::JoinHandle<()>>,
|
||||
|
|
@ -54,48 +47,75 @@ struct SessionMetaData {
|
|||
|
||||
impl Drop for SessionMetaData {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.send_pty_instructions.send(PtyInstruction::Exit);
|
||||
let _ = self.send_screen_instructions.send(ScreenInstruction::Exit);
|
||||
let _ = self.send_plugin_instructions.send(PluginInstruction::Exit);
|
||||
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>) -> thread::JoinHandle<()> {
|
||||
let (send_server_instructions, receive_server_instructions): ChannelWithContext<
|
||||
ServerInstruction,
|
||||
> = channel();
|
||||
let send_server_instructions =
|
||||
SenderWithContext::new(SenderType::Sender(send_server_instructions));
|
||||
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)]
|
||||
handle_client(
|
||||
sessions.clone(),
|
||||
os_input.clone(),
|
||||
send_server_instructions.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();
|
||||
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 send_server_instructions = send_server_instructions.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);
|
||||
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 send_server_instructions = send_server_instructions.clone();
|
||||
handle_client(sessions, os_input, send_server_instructions);
|
||||
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);
|
||||
|
|
@ -109,14 +129,15 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>) -> thread::JoinHandle<()> {
|
|||
.name("server_thread".to_string())
|
||||
.spawn({
|
||||
move || loop {
|
||||
let (instruction, mut err_ctx) = receive_server_instructions.recv().unwrap();
|
||||
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) => {
|
||||
ServerInstruction::NewClient(full_screen_ws, opts, config_options) => {
|
||||
let session_data = init_session(
|
||||
os_input.clone(),
|
||||
opts,
|
||||
send_server_instructions.clone(),
|
||||
config_options,
|
||||
to_server.clone(),
|
||||
full_screen_ws,
|
||||
);
|
||||
*sessions.write().unwrap() = Some(session_data);
|
||||
|
|
@ -125,8 +146,8 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>) -> thread::JoinHandle<()> {
|
|||
.unwrap()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send_pty_instructions
|
||||
.send(PtyInstruction::NewTab)
|
||||
.senders
|
||||
.send_to_pty(PtyInstruction::NewTab)
|
||||
.unwrap();
|
||||
}
|
||||
ServerInstruction::UnblockInputThread => {
|
||||
|
|
@ -148,65 +169,20 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>) -> thread::JoinHandle<()> {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
fn handle_client(
|
||||
sessions: Arc<RwLock<Option<SessionMetaData>>>,
|
||||
mut os_input: Box<dyn ServerOsApi>,
|
||||
send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||
) {
|
||||
thread::Builder::new()
|
||||
.name("server_router".to_string())
|
||||
.spawn(move || loop {
|
||||
let (instruction, mut err_ctx) = os_input.recv_from_client();
|
||||
err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction)));
|
||||
let rlocked_sessions = sessions.read().unwrap();
|
||||
match instruction {
|
||||
ServerInstruction::ClientExit => {
|
||||
send_server_instructions.send(instruction).unwrap();
|
||||
break;
|
||||
}
|
||||
ServerInstruction::Action(action) => {
|
||||
route_action(action, rlocked_sessions.as_ref().unwrap(), &*os_input);
|
||||
}
|
||||
ServerInstruction::TerminalResize(new_size) => {
|
||||
rlocked_sessions
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::TerminalResize(new_size))
|
||||
.unwrap();
|
||||
}
|
||||
ServerInstruction::NewClient(..) => {
|
||||
os_input.add_client_sender();
|
||||
send_server_instructions.send(instruction).unwrap();
|
||||
}
|
||||
_ => {
|
||||
send_server_instructions.send(instruction).unwrap();
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn init_session(
|
||||
os_input: Box<dyn ServerOsApi>,
|
||||
opts: CliArgs,
|
||||
send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||
config_options: Options,
|
||||
to_server: SenderWithContext<ServerInstruction>,
|
||||
full_screen_ws: PositionAndSize,
|
||||
) -> SessionMetaData {
|
||||
let (send_screen_instructions, receive_screen_instructions): ChannelWithContext<
|
||||
ScreenInstruction,
|
||||
> = channel();
|
||||
let send_screen_instructions =
|
||||
SenderWithContext::new(SenderType::Sender(send_screen_instructions));
|
||||
let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = channel();
|
||||
let to_screen = SenderWithContext::new(SenderType::Sender(to_screen));
|
||||
|
||||
let (send_plugin_instructions, receive_plugin_instructions): ChannelWithContext<
|
||||
PluginInstruction,
|
||||
> = channel();
|
||||
let send_plugin_instructions =
|
||||
SenderWithContext::new(SenderType::Sender(send_plugin_instructions));
|
||||
let (send_pty_instructions, receive_pty_instructions): ChannelWithContext<PtyInstruction> =
|
||||
channel();
|
||||
let send_pty_instructions = SenderWithContext::new(SenderType::Sender(send_pty_instructions));
|
||||
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);
|
||||
|
|
@ -224,322 +200,41 @@ fn init_session(
|
|||
.map(|p| Layout::new(&p, &data_dir))
|
||||
.or_else(|| default_layout.map(|p| Layout::from_defaults(&p, &data_dir)));
|
||||
|
||||
let mut pty_bus = PtyBus::new(
|
||||
receive_pty_instructions,
|
||||
send_screen_instructions.clone(),
|
||||
send_plugin_instructions.clone(),
|
||||
os_input.clone(),
|
||||
opts.debug,
|
||||
);
|
||||
|
||||
let pty_thread = thread::Builder::new()
|
||||
.name("pty".to_string())
|
||||
.spawn({
|
||||
let send_server_instructions = send_server_instructions.clone();
|
||||
move || loop {
|
||||
let (event, mut err_ctx) = pty_bus
|
||||
.receive_pty_instructions
|
||||
.recv()
|
||||
.expect("failed to receive event on channel");
|
||||
err_ctx.add_call(ContextType::Pty(PtyContext::from(&event)));
|
||||
match event {
|
||||
PtyInstruction::SpawnTerminal(file_to_open) => {
|
||||
let pid = pty_bus.spawn_terminal(file_to_open);
|
||||
pty_bus
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::NewPane(PaneId::Terminal(pid)))
|
||||
.unwrap();
|
||||
}
|
||||
PtyInstruction::SpawnTerminalVertically(file_to_open) => {
|
||||
let pid = pty_bus.spawn_terminal(file_to_open);
|
||||
pty_bus
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid)))
|
||||
.unwrap();
|
||||
}
|
||||
PtyInstruction::SpawnTerminalHorizontally(file_to_open) => {
|
||||
let pid = pty_bus.spawn_terminal(file_to_open);
|
||||
pty_bus
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid)))
|
||||
.unwrap();
|
||||
}
|
||||
PtyInstruction::NewTab => {
|
||||
if let Some(layout) = maybe_layout.clone() {
|
||||
pty_bus.spawn_terminals_for_layout(layout);
|
||||
} else {
|
||||
let pid = pty_bus.spawn_terminal(None);
|
||||
pty_bus
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::NewTab(pid))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
PtyInstruction::ClosePane(id) => {
|
||||
pty_bus.close_pane(id);
|
||||
send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
PtyInstruction::CloseTab(ids) => {
|
||||
pty_bus.close_tab(ids);
|
||||
send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
PtyInstruction::Exit => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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 os_input = os_input.clone();
|
||||
let send_plugin_instructions = send_plugin_instructions.clone();
|
||||
let send_pty_instructions = send_pty_instructions.clone();
|
||||
let send_server_instructions = send_server_instructions;
|
||||
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 colors = os_input.load_palette();
|
||||
let config_options = config_options;
|
||||
|
||||
move || {
|
||||
let mut screen = Screen::new(
|
||||
receive_screen_instructions,
|
||||
send_plugin_instructions,
|
||||
send_pty_instructions,
|
||||
send_server_instructions,
|
||||
&full_screen_ws,
|
||||
os_input,
|
||||
max_panes,
|
||||
ModeInfo {
|
||||
palette: colors,
|
||||
..ModeInfo::default()
|
||||
},
|
||||
InputMode::Normal,
|
||||
colors,
|
||||
);
|
||||
loop {
|
||||
let (event, mut err_ctx) = screen
|
||||
.receiver
|
||||
.recv()
|
||||
.expect("failed to receive event on channel");
|
||||
err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event)));
|
||||
match event {
|
||||
ScreenInstruction::PtyBytes(pid, vte_bytes) => {
|
||||
let active_tab = screen.get_active_tab_mut().unwrap();
|
||||
if active_tab.has_terminal_pid(pid) {
|
||||
// it's most likely that this event is directed at the active tab
|
||||
// look there first
|
||||
active_tab.handle_pty_bytes(pid, vte_bytes);
|
||||
} else {
|
||||
// if this event wasn't directed at the active tab, start looking
|
||||
// in other tabs
|
||||
let all_tabs = screen.get_tabs_mut();
|
||||
for tab in all_tabs.values_mut() {
|
||||
if tab.has_terminal_pid(pid) {
|
||||
tab.handle_pty_bytes(pid, vte_bytes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ScreenInstruction::Render => {
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::NewPane(pid) => {
|
||||
screen.get_active_tab_mut().unwrap().new_pane(pid);
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::HorizontalSplit(pid) => {
|
||||
screen.get_active_tab_mut().unwrap().horizontal_split(pid);
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::VerticalSplit(pid) => {
|
||||
screen.get_active_tab_mut().unwrap().vertical_split(pid);
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::WriteCharacter(bytes) => {
|
||||
let active_tab = screen.get_active_tab_mut().unwrap();
|
||||
match active_tab.is_sync_panes_active() {
|
||||
true => active_tab.write_to_terminals_on_current_tab(bytes),
|
||||
false => active_tab.write_to_active_terminal(bytes),
|
||||
}
|
||||
}
|
||||
ScreenInstruction::ResizeLeft => {
|
||||
screen.get_active_tab_mut().unwrap().resize_left();
|
||||
}
|
||||
ScreenInstruction::ResizeRight => {
|
||||
screen.get_active_tab_mut().unwrap().resize_right();
|
||||
}
|
||||
ScreenInstruction::ResizeDown => {
|
||||
screen.get_active_tab_mut().unwrap().resize_down();
|
||||
}
|
||||
ScreenInstruction::ResizeUp => {
|
||||
screen.get_active_tab_mut().unwrap().resize_up();
|
||||
}
|
||||
ScreenInstruction::SwitchFocus => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus();
|
||||
}
|
||||
ScreenInstruction::FocusNextPane => {
|
||||
screen.get_active_tab_mut().unwrap().focus_next_pane();
|
||||
}
|
||||
ScreenInstruction::FocusPreviousPane => {
|
||||
screen.get_active_tab_mut().unwrap().focus_previous_pane();
|
||||
}
|
||||
ScreenInstruction::MoveFocusLeft => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus_left();
|
||||
}
|
||||
ScreenInstruction::MoveFocusDown => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus_down();
|
||||
}
|
||||
ScreenInstruction::MoveFocusRight => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus_right();
|
||||
}
|
||||
ScreenInstruction::MoveFocusUp => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus_up();
|
||||
}
|
||||
ScreenInstruction::ScrollUp => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.scroll_active_terminal_up();
|
||||
}
|
||||
ScreenInstruction::ScrollDown => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.scroll_active_terminal_down();
|
||||
}
|
||||
ScreenInstruction::PageScrollUp => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.scroll_active_terminal_up_page();
|
||||
}
|
||||
ScreenInstruction::PageScrollDown => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.scroll_active_terminal_down_page();
|
||||
}
|
||||
ScreenInstruction::ClearScroll => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.clear_active_terminal_scroll();
|
||||
}
|
||||
ScreenInstruction::CloseFocusedPane => {
|
||||
screen.get_active_tab_mut().unwrap().close_focused_pane();
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::SetSelectable(id, selectable) => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.set_pane_selectable(id, selectable);
|
||||
}
|
||||
ScreenInstruction::SetMaxHeight(id, max_height) => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.set_pane_max_height(id, max_height);
|
||||
}
|
||||
ScreenInstruction::SetInvisibleBorders(id, invisible_borders) => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.set_pane_invisible_borders(id, invisible_borders);
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::ClosePane(id) => {
|
||||
screen.get_active_tab_mut().unwrap().close_pane(id);
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::ToggleActiveTerminalFullscreen => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.toggle_active_pane_fullscreen();
|
||||
}
|
||||
ScreenInstruction::NewTab(pane_id) => {
|
||||
screen.new_tab(pane_id);
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::SwitchTabNext => {
|
||||
screen.switch_tab_next();
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::SwitchTabPrev => {
|
||||
screen.switch_tab_prev();
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::CloseTab => {
|
||||
screen.close_tab();
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::ApplyLayout(layout, new_pane_pids) => {
|
||||
screen.apply_layout(layout, new_pane_pids);
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::GoToTab(tab_index) => {
|
||||
screen.go_to_tab(tab_index as usize);
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::UpdateTabName(c) => {
|
||||
screen.update_active_tab_name(c);
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::TerminalResize(new_size) => {
|
||||
screen.resize_to_screen(new_size);
|
||||
}
|
||||
ScreenInstruction::ChangeMode(mode_info) => {
|
||||
screen.change_mode(mode_info);
|
||||
}
|
||||
ScreenInstruction::ToggleActiveSyncPanes => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.toggle_sync_panes_is_active();
|
||||
screen.update_tabs();
|
||||
}
|
||||
ScreenInstruction::Exit => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
screen_thread_main(screen_bus, max_panes, full_screen_ws, config_options);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
|
@ -547,265 +242,28 @@ fn init_session(
|
|||
let wasm_thread = thread::Builder::new()
|
||||
.name("wasm".to_string())
|
||||
.spawn({
|
||||
let send_screen_instructions = send_screen_instructions.clone();
|
||||
let send_pty_instructions = send_pty_instructions.clone();
|
||||
let send_plugin_instructions = send_plugin_instructions.clone();
|
||||
|
||||
let plugin_bus = Bus::new(
|
||||
plugin_receiver,
|
||||
Some(&to_screen),
|
||||
Some(&to_pty),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let store = Store::default();
|
||||
let mut plugin_id = 0;
|
||||
let mut plugin_map = HashMap::new();
|
||||
move || loop {
|
||||
let (event, mut err_ctx) = receive_plugin_instructions
|
||||
.recv()
|
||||
.expect("failed to receive event on channel");
|
||||
err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event)));
|
||||
match event {
|
||||
PluginInstruction::Load(pid_tx, path) => {
|
||||
let plugin_dir = data_dir.join("plugins/");
|
||||
let wasm_bytes = fs::read(&path)
|
||||
.or_else(|_| fs::read(&path.with_extension("wasm")))
|
||||
.or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm")))
|
||||
.unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display()));
|
||||
|
||||
// FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that
|
||||
let module = Module::new(&store, &wasm_bytes).unwrap();
|
||||
|
||||
let output = Pipe::new();
|
||||
let input = Pipe::new();
|
||||
let mut wasi_env = WasiState::new("Zellij")
|
||||
.env("CLICOLOR_FORCE", "1")
|
||||
.preopen(|p| {
|
||||
p.directory(".") // FIXME: Change this to a more meaningful dir
|
||||
.alias(".")
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
})
|
||||
.unwrap()
|
||||
.stdin(Box::new(input))
|
||||
.stdout(Box::new(output))
|
||||
.finalize()
|
||||
.unwrap();
|
||||
|
||||
let wasi = wasi_env.import_object(&module).unwrap();
|
||||
|
||||
let plugin_env = PluginEnv {
|
||||
plugin_id,
|
||||
send_screen_instructions: send_screen_instructions.clone(),
|
||||
send_pty_instructions: send_pty_instructions.clone(),
|
||||
send_plugin_instructions: send_plugin_instructions.clone(),
|
||||
wasi_env,
|
||||
subscriptions: Arc::new(Mutex::new(HashSet::new())),
|
||||
};
|
||||
|
||||
let zellij = zellij_exports(&store, &plugin_env);
|
||||
let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap();
|
||||
|
||||
let start = instance.exports.get_function("_start").unwrap();
|
||||
|
||||
// This eventually calls the `.init()` method
|
||||
start.call(&[]).unwrap();
|
||||
|
||||
plugin_map.insert(plugin_id, (instance, plugin_env));
|
||||
pid_tx.send(plugin_id).unwrap();
|
||||
plugin_id += 1;
|
||||
}
|
||||
PluginInstruction::Update(pid, event) => {
|
||||
for (&i, (instance, plugin_env)) in &plugin_map {
|
||||
let subs = plugin_env.subscriptions.lock().unwrap();
|
||||
// FIXME: This is very janky... Maybe I should write my own macro for Event -> EventType?
|
||||
let event_type = EventType::from_str(&event.to_string()).unwrap();
|
||||
if (pid.is_none() || pid == Some(i)) && subs.contains(&event_type) {
|
||||
let update = instance.exports.get_function("update").unwrap();
|
||||
wasi_write_object(&plugin_env.wasi_env, &event);
|
||||
update.call(&[]).unwrap();
|
||||
}
|
||||
}
|
||||
drop(send_screen_instructions.send(ScreenInstruction::Render));
|
||||
}
|
||||
PluginInstruction::Render(buf_tx, pid, rows, cols) => {
|
||||
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
|
||||
|
||||
let render = instance.exports.get_function("render").unwrap();
|
||||
|
||||
render
|
||||
.call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
|
||||
.unwrap();
|
||||
|
||||
buf_tx.send(wasi_read_string(&plugin_env.wasi_env)).unwrap();
|
||||
}
|
||||
PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
|
||||
PluginInstruction::Exit => break,
|
||||
}
|
||||
}
|
||||
move || wasm_thread_main(plugin_bus, store, data_dir)
|
||||
})
|
||||
.unwrap();
|
||||
SessionMetaData {
|
||||
send_plugin_instructions,
|
||||
send_screen_instructions,
|
||||
send_pty_instructions,
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn ServerOsApi) {
|
||||
match action {
|
||||
Action::Write(val) => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::ClearScroll)
|
||||
.unwrap();
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::WriteCharacter(val))
|
||||
.unwrap();
|
||||
}
|
||||
Action::SwitchToMode(mode) => {
|
||||
let palette = os_input.load_palette();
|
||||
session
|
||||
.send_plugin_instructions
|
||||
.send(PluginInstruction::Update(
|
||||
None,
|
||||
Event::ModeUpdate(get_mode_info(mode, palette)),
|
||||
))
|
||||
.unwrap();
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::ChangeMode(get_mode_info(mode, palette)))
|
||||
.unwrap();
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::Render)
|
||||
.unwrap();
|
||||
}
|
||||
Action::Resize(direction) => {
|
||||
let screen_instr = match direction {
|
||||
Direction::Left => ScreenInstruction::ResizeLeft,
|
||||
Direction::Right => ScreenInstruction::ResizeRight,
|
||||
Direction::Up => ScreenInstruction::ResizeUp,
|
||||
Direction::Down => ScreenInstruction::ResizeDown,
|
||||
};
|
||||
session.send_screen_instructions.send(screen_instr).unwrap();
|
||||
}
|
||||
Action::SwitchFocus => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::SwitchFocus)
|
||||
.unwrap();
|
||||
}
|
||||
Action::FocusNextPane => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::FocusNextPane)
|
||||
.unwrap();
|
||||
}
|
||||
Action::FocusPreviousPane => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::FocusPreviousPane)
|
||||
.unwrap();
|
||||
}
|
||||
Action::MoveFocus(direction) => {
|
||||
let screen_instr = match direction {
|
||||
Direction::Left => ScreenInstruction::MoveFocusLeft,
|
||||
Direction::Right => ScreenInstruction::MoveFocusRight,
|
||||
Direction::Up => ScreenInstruction::MoveFocusUp,
|
||||
Direction::Down => ScreenInstruction::MoveFocusDown,
|
||||
};
|
||||
session.send_screen_instructions.send(screen_instr).unwrap();
|
||||
}
|
||||
Action::ScrollUp => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::ScrollUp)
|
||||
.unwrap();
|
||||
}
|
||||
Action::ScrollDown => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::ScrollDown)
|
||||
.unwrap();
|
||||
}
|
||||
Action::PageScrollUp => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::PageScrollUp)
|
||||
.unwrap();
|
||||
}
|
||||
Action::PageScrollDown => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::PageScrollDown)
|
||||
.unwrap();
|
||||
}
|
||||
Action::ToggleFocusFullscreen => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::ToggleActiveTerminalFullscreen)
|
||||
.unwrap();
|
||||
}
|
||||
Action::NewPane(direction) => {
|
||||
let pty_instr = match direction {
|
||||
Some(Direction::Left) => PtyInstruction::SpawnTerminalVertically(None),
|
||||
Some(Direction::Right) => PtyInstruction::SpawnTerminalVertically(None),
|
||||
Some(Direction::Up) => PtyInstruction::SpawnTerminalHorizontally(None),
|
||||
Some(Direction::Down) => PtyInstruction::SpawnTerminalHorizontally(None),
|
||||
// No direction specified - try to put it in the biggest available spot
|
||||
None => PtyInstruction::SpawnTerminal(None),
|
||||
};
|
||||
session.send_pty_instructions.send(pty_instr).unwrap();
|
||||
}
|
||||
Action::CloseFocus => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::CloseFocusedPane)
|
||||
.unwrap();
|
||||
}
|
||||
Action::NewTab => {
|
||||
session
|
||||
.send_pty_instructions
|
||||
.send(PtyInstruction::NewTab)
|
||||
.unwrap();
|
||||
}
|
||||
Action::GoToNextTab => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::SwitchTabNext)
|
||||
.unwrap();
|
||||
}
|
||||
Action::GoToPreviousTab => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::SwitchTabPrev)
|
||||
.unwrap();
|
||||
}
|
||||
Action::ToggleActiveSyncPanes => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::ToggleActiveSyncPanes)
|
||||
.unwrap();
|
||||
}
|
||||
Action::CloseTab => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::CloseTab)
|
||||
.unwrap();
|
||||
}
|
||||
Action::GoToTab(i) => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::GoToTab(i))
|
||||
.unwrap();
|
||||
}
|
||||
Action::TabNameInput(c) => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::UpdateTabName(c))
|
||||
.unwrap();
|
||||
}
|
||||
Action::NoOp => {}
|
||||
Action::Quit => panic!("Received unexpected action"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
229
src/server/route.rs
Normal file
229
src/server/route.rs
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use zellij_tile::data::{Event, PluginCapabilities};
|
||||
|
||||
use crate::common::errors::{ContextType, ServerContext};
|
||||
use crate::common::input::actions::{Action, Direction};
|
||||
use crate::common::input::handler::get_mode_info;
|
||||
use crate::common::os_input_output::ServerOsApi;
|
||||
use crate::common::pty::PtyInstruction;
|
||||
use crate::common::screen::ScreenInstruction;
|
||||
use crate::common::thread_bus::SenderWithContext;
|
||||
use crate::common::wasm_vm::PluginInstruction;
|
||||
use crate::server::{ServerInstruction, SessionMetaData};
|
||||
|
||||
fn route_action(
|
||||
action: Action,
|
||||
session: &SessionMetaData,
|
||||
os_input: &dyn ServerOsApi,
|
||||
capabilities: PluginCapabilities,
|
||||
) {
|
||||
match action {
|
||||
Action::Write(val) => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::ClearScroll)
|
||||
.unwrap();
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::WriteCharacter(val))
|
||||
.unwrap();
|
||||
}
|
||||
Action::SwitchToMode(mode) => {
|
||||
let palette = os_input.load_palette();
|
||||
session
|
||||
.senders
|
||||
.send_to_plugin(PluginInstruction::Update(
|
||||
None,
|
||||
Event::ModeUpdate(get_mode_info(mode, palette, capabilities)),
|
||||
))
|
||||
.unwrap();
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::ChangeMode(get_mode_info(
|
||||
mode,
|
||||
palette,
|
||||
capabilities,
|
||||
)))
|
||||
.unwrap();
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::Render)
|
||||
.unwrap();
|
||||
}
|
||||
Action::Resize(direction) => {
|
||||
let screen_instr = match direction {
|
||||
Direction::Left => ScreenInstruction::ResizeLeft,
|
||||
Direction::Right => ScreenInstruction::ResizeRight,
|
||||
Direction::Up => ScreenInstruction::ResizeUp,
|
||||
Direction::Down => ScreenInstruction::ResizeDown,
|
||||
};
|
||||
session.senders.send_to_screen(screen_instr).unwrap();
|
||||
}
|
||||
Action::SwitchFocus => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::SwitchFocus)
|
||||
.unwrap();
|
||||
}
|
||||
Action::FocusNextPane => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::FocusNextPane)
|
||||
.unwrap();
|
||||
}
|
||||
Action::FocusPreviousPane => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::FocusPreviousPane)
|
||||
.unwrap();
|
||||
}
|
||||
Action::MoveFocus(direction) => {
|
||||
let screen_instr = match direction {
|
||||
Direction::Left => ScreenInstruction::MoveFocusLeft,
|
||||
Direction::Right => ScreenInstruction::MoveFocusRight,
|
||||
Direction::Up => ScreenInstruction::MoveFocusUp,
|
||||
Direction::Down => ScreenInstruction::MoveFocusDown,
|
||||
};
|
||||
session.senders.send_to_screen(screen_instr).unwrap();
|
||||
}
|
||||
Action::MoveFocusOrTab(direction) => {
|
||||
let screen_instr = match direction {
|
||||
Direction::Left => ScreenInstruction::MoveFocusLeftOrPreviousTab,
|
||||
Direction::Right => ScreenInstruction::MoveFocusRightOrNextTab,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
session.senders.send_to_screen(screen_instr).unwrap();
|
||||
}
|
||||
Action::ScrollUp => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::ScrollUp)
|
||||
.unwrap();
|
||||
}
|
||||
Action::ScrollDown => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::ScrollDown)
|
||||
.unwrap();
|
||||
}
|
||||
Action::PageScrollUp => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::PageScrollUp)
|
||||
.unwrap();
|
||||
}
|
||||
Action::PageScrollDown => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::PageScrollDown)
|
||||
.unwrap();
|
||||
}
|
||||
Action::ToggleFocusFullscreen => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::ToggleActiveTerminalFullscreen)
|
||||
.unwrap();
|
||||
}
|
||||
Action::NewPane(direction) => {
|
||||
let pty_instr = match direction {
|
||||
Some(Direction::Left) => PtyInstruction::SpawnTerminalVertically(None),
|
||||
Some(Direction::Right) => PtyInstruction::SpawnTerminalVertically(None),
|
||||
Some(Direction::Up) => PtyInstruction::SpawnTerminalHorizontally(None),
|
||||
Some(Direction::Down) => PtyInstruction::SpawnTerminalHorizontally(None),
|
||||
// No direction specified - try to put it in the biggest available spot
|
||||
None => PtyInstruction::SpawnTerminal(None),
|
||||
};
|
||||
session.senders.send_to_pty(pty_instr).unwrap();
|
||||
}
|
||||
Action::CloseFocus => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::CloseFocusedPane)
|
||||
.unwrap();
|
||||
}
|
||||
Action::NewTab => {
|
||||
session.senders.send_to_pty(PtyInstruction::NewTab).unwrap();
|
||||
}
|
||||
Action::GoToNextTab => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::SwitchTabNext)
|
||||
.unwrap();
|
||||
}
|
||||
Action::GoToPreviousTab => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::SwitchTabPrev)
|
||||
.unwrap();
|
||||
}
|
||||
Action::ToggleActiveSyncTab => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::ToggleActiveSyncTab)
|
||||
.unwrap();
|
||||
}
|
||||
Action::CloseTab => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::CloseTab)
|
||||
.unwrap();
|
||||
}
|
||||
Action::GoToTab(i) => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::GoToTab(i))
|
||||
.unwrap();
|
||||
}
|
||||
Action::TabNameInput(c) => {
|
||||
session
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::UpdateTabName(c))
|
||||
.unwrap();
|
||||
}
|
||||
Action::NoOp => {}
|
||||
Action::Quit => panic!("Received unexpected action"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn route_thread_main(
|
||||
sessions: Arc<RwLock<Option<SessionMetaData>>>,
|
||||
mut os_input: Box<dyn ServerOsApi>,
|
||||
to_server: SenderWithContext<ServerInstruction>,
|
||||
capabilities: PluginCapabilities,
|
||||
) {
|
||||
loop {
|
||||
let (instruction, mut err_ctx) = os_input.recv_from_client();
|
||||
err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction)));
|
||||
let rlocked_sessions = sessions.read().unwrap();
|
||||
match instruction {
|
||||
ServerInstruction::ClientExit => {
|
||||
to_server.send(instruction).unwrap();
|
||||
break;
|
||||
}
|
||||
ServerInstruction::Action(action) => {
|
||||
route_action(
|
||||
action,
|
||||
rlocked_sessions.as_ref().unwrap(),
|
||||
&*os_input,
|
||||
capabilities,
|
||||
);
|
||||
}
|
||||
ServerInstruction::TerminalResize(new_size) => {
|
||||
rlocked_sessions
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::TerminalResize(new_size))
|
||||
.unwrap();
|
||||
}
|
||||
ServerInstruction::NewClient(..) => {
|
||||
os_input.add_client_sender();
|
||||
to_server.send(instruction).unwrap();
|
||||
}
|
||||
_ => {
|
||||
to_server.send(instruction).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ use std::sync::{mpsc, Arc, Condvar, Mutex};
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::client::ClientInstruction;
|
||||
use crate::common::{ChannelWithContext, SenderType, SenderWithContext};
|
||||
use crate::common::thread_bus::{ChannelWithContext, SenderType, SenderWithContext};
|
||||
use crate::errors::ErrorContext;
|
||||
use crate::os_input_output::{ClientOsApi, ServerOsApi};
|
||||
use crate::server::ServerInstruction;
|
||||
|
|
|
|||
1
src/tests/fixtures/csi-b
vendored
Normal file
1
src/tests/fixtures/csi-b
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
f[5b
|
||||
1
src/tests/fixtures/csi-capital-i
vendored
Normal file
1
src/tests/fixtures/csi-capital-i
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
[5Ifoo
|
||||
1
src/tests/fixtures/csi-capital-z
vendored
Normal file
1
src/tests/fixtures/csi-capital-z
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
12345678901234567890[2Zfoo
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
use crate::panes::PositionAndSize;
|
||||
use ::insta::assert_snapshot;
|
||||
|
||||
use crate::common::input::config::Config;
|
||||
use crate::common::input::{config::Config, options::Options};
|
||||
use crate::tests::fakes::FakeInputOutput;
|
||||
use crate::tests::utils::commands::{
|
||||
PANE_MODE, QUIT, SCROLL_DOWN_IN_SCROLL_MODE, SCROLL_MODE, SCROLL_PAGE_DOWN_IN_SCROLL_MODE,
|
||||
SCROLL_PAGE_UP_IN_SCROLL_MODE, SCROLL_UP_IN_SCROLL_MODE, SPAWN_TERMINAL_IN_PANE_MODE,
|
||||
SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE,
|
||||
TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_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_UP_IN_SCROLL_MODE, SPAWN_TERMINAL_IN_PANE_MODE, SPLIT_DOWN_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::{start, CliArgs};
|
||||
|
|
@ -32,6 +32,7 @@ pub fn starts_with_one_terminal() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -60,6 +61,7 @@ pub fn split_terminals_vertically() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -88,6 +90,7 @@ pub fn split_terminals_horizontally() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -123,6 +126,7 @@ pub fn split_largest_terminal() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -151,6 +155,7 @@ pub fn cannot_split_terminals_vertically_when_active_terminal_is_too_small() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -179,6 +184,7 @@ pub fn cannot_split_terminals_horizontally_when_active_terminal_is_too_small() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -207,6 +213,7 @@ pub fn cannot_split_largest_terminal_when_there_is_no_room() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -243,6 +250,7 @@ pub fn scrolling_up_inside_a_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -281,6 +289,7 @@ pub fn scrolling_down_inside_a_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -316,6 +325,7 @@ pub fn scrolling_page_up_inside_a_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -354,6 +364,7 @@ pub fn scrolling_page_down_inside_a_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -393,6 +404,7 @@ pub fn max_panes() {
|
|||
opts,
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -430,6 +442,48 @@ pub fn toggle_focused_pane_fullscreen() {
|
|||
opts,
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
.output_frames
|
||||
.lock()
|
||||
.unwrap();
|
||||
let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
|
||||
let snapshot_before_quit =
|
||||
get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
|
||||
assert_snapshot!(snapshot_before_quit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn bracketed_paste() {
|
||||
// bracketed paste (https://xfree86.org/current/ctlseqs.html#Bracketed%20Paste%20Mode)
|
||||
// makes sure that text the user pastes is not interpreted as commands by the running program
|
||||
// (zellij in this case)
|
||||
// this tests makes sure the "SPLIT_RIGHT_IN_PANE_MODE" command is not interpreted as Zellij,
|
||||
// since it's inside a bracketed paste block, while the "QUIT" command is, since it is already
|
||||
// past the block
|
||||
let fake_win_size = PositionAndSize {
|
||||
columns: 121,
|
||||
rows: 20,
|
||||
x: 0,
|
||||
y: 0,
|
||||
..Default::default()
|
||||
};
|
||||
let mut fake_input_output = get_fake_os_input(&fake_win_size);
|
||||
fake_input_output.add_terminal_input(&[
|
||||
&PANE_MODE,
|
||||
&BRACKETED_PASTE_START,
|
||||
&SPLIT_RIGHT_IN_PANE_MODE,
|
||||
&BRACKETED_PASTE_END,
|
||||
&QUIT,
|
||||
]);
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::tests::fakes::FakeInputOutput;
|
|||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
||||
use crate::{start, CliArgs};
|
||||
|
||||
use crate::common::input::config::Config;
|
||||
use crate::common::input::{config::Config, options::Options};
|
||||
use crate::tests::utils::commands::{
|
||||
CLOSE_PANE_IN_PANE_MODE, ESC, MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT,
|
||||
RESIZE_DOWN_IN_RESIZE_MODE, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE,
|
||||
|
|
@ -45,6 +45,7 @@ pub fn close_pane_with_another_pane_above_it() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -88,6 +89,7 @@ pub fn close_pane_with_another_pane_below_it() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -128,6 +130,7 @@ pub fn close_pane_with_another_pane_to_the_left() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -169,6 +172,7 @@ pub fn close_pane_with_another_pane_to_the_right() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -215,6 +219,7 @@ pub fn close_pane_with_multiple_panes_above_it() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -259,6 +264,7 @@ pub fn close_pane_with_multiple_panes_below_it() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -305,6 +311,7 @@ pub fn close_pane_with_multiple_panes_to_the_left() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -349,6 +356,7 @@ pub fn close_pane_with_multiple_panes_to_the_right() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -415,6 +423,7 @@ pub fn close_pane_with_multiple_panes_above_it_away_from_screen_edges() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -477,6 +486,7 @@ pub fn close_pane_with_multiple_panes_below_it_away_from_screen_edges() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -541,6 +551,7 @@ pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -605,6 +616,7 @@ pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -642,6 +654,7 @@ pub fn closing_last_pane_exits_app() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::tests::possible_tty_inputs::Bytes;
|
|||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
||||
use crate::{start, CliArgs};
|
||||
|
||||
use crate::common::input::config::Config;
|
||||
use crate::common::input::{config::Config, options::Options};
|
||||
use crate::tests::utils::commands::QUIT;
|
||||
|
||||
/*
|
||||
|
|
@ -48,6 +48,7 @@ pub fn run_bandwhich_from_fish_shell() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -77,6 +78,7 @@ pub fn fish_tab_completion_options() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -111,6 +113,7 @@ pub fn fish_select_tab_completion_options() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -149,6 +152,7 @@ pub fn vim_scroll_region_down() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -184,6 +188,7 @@ pub fn vim_ctrl_d() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -218,6 +223,7 @@ pub fn vim_ctrl_u() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -247,6 +253,7 @@ pub fn htop() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -276,6 +283,7 @@ pub fn htop_scrolling() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -305,6 +313,7 @@ pub fn htop_right_scrolling() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -342,6 +351,7 @@ pub fn vim_overwrite() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -374,6 +384,7 @@ pub fn clear_scroll_region() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -403,6 +414,7 @@ pub fn display_tab_characters_properly() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -432,6 +444,7 @@ pub fn neovim_insert_mode() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -463,6 +476,7 @@ pub fn bash_cursor_linewrap() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -494,6 +508,7 @@ pub fn fish_paste_multiline() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -523,6 +538,7 @@ pub fn git_log() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -554,6 +570,7 @@ pub fn git_diff_scrollup() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -583,6 +600,7 @@ pub fn emacs_longbuf() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -612,6 +630,7 @@ pub fn top_and_quit() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -647,6 +666,7 @@ pub fn exa_plus_omf_theme() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use insta::assert_snapshot;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::common::input::config::Config;
|
||||
use crate::common::input::{config::Config, options::Options};
|
||||
use crate::panes::PositionAndSize;
|
||||
use crate::tests::fakes::FakeInputOutput;
|
||||
use crate::tests::utils::commands::QUIT;
|
||||
|
|
@ -33,6 +33,7 @@ pub fn accepts_basic_layout() {
|
|||
opts,
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::tests::fakes::FakeInputOutput;
|
|||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
||||
use crate::{start, CliArgs};
|
||||
|
||||
use crate::common::input::config::Config;
|
||||
use crate::common::input::{config::Config, options::Options};
|
||||
use crate::tests::utils::commands::{
|
||||
MOVE_FOCUS_DOWN_IN_PANE_MODE, MOVE_FOCUS_UP_IN_PANE_MODE, PANE_MODE, QUIT,
|
||||
SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE,
|
||||
|
|
@ -37,6 +37,7 @@ pub fn move_focus_down() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -74,6 +75,7 @@ pub fn move_focus_down_to_the_most_recently_used_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ use crate::tests::fakes::FakeInputOutput;
|
|||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
||||
use crate::{start, CliArgs};
|
||||
|
||||
use crate::common::input::config::Config;
|
||||
use crate::common::input::{config::Config, options::Options};
|
||||
use crate::tests::utils::commands::{
|
||||
MOVE_FOCUS_LEFT_IN_PANE_MODE, MOVE_FOCUS_RIGHT_IN_PANE_MODE, PANE_MODE, QUIT,
|
||||
SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_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,
|
||||
SPLIT_RIGHT_IN_PANE_MODE, TAB_MODE,
|
||||
};
|
||||
|
||||
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
||||
|
|
@ -36,6 +37,7 @@ pub fn move_focus_left() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -74,6 +76,46 @@ pub fn move_focus_left_to_the_most_recently_used_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
.output_frames
|
||||
.lock()
|
||||
.unwrap();
|
||||
let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
|
||||
let snapshot_before_quit =
|
||||
get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
|
||||
assert_snapshot!(snapshot_before_quit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn move_focus_left_changes_tab() {
|
||||
let fake_win_size = PositionAndSize {
|
||||
columns: 121,
|
||||
rows: 20,
|
||||
x: 0,
|
||||
y: 0,
|
||||
..Default::default()
|
||||
};
|
||||
let mut fake_input_output = get_fake_os_input(&fake_win_size);
|
||||
fake_input_output.add_terminal_input(&[
|
||||
&PANE_MODE,
|
||||
&SPLIT_DOWN_IN_PANE_MODE,
|
||||
&ENTER,
|
||||
&TAB_MODE,
|
||||
&NEW_TAB_IN_TAB_MODE,
|
||||
&ENTER,
|
||||
&MOVE_FOCUS_LEFT_IN_NORMAL_MODE,
|
||||
&QUIT,
|
||||
]);
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ use crate::tests::fakes::FakeInputOutput;
|
|||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
||||
use crate::{start, CliArgs};
|
||||
|
||||
use crate::common::input::config::Config;
|
||||
use crate::common::input::{config::Config, options::Options};
|
||||
use crate::tests::utils::commands::{
|
||||
MOVE_FOCUS_LEFT_IN_PANE_MODE, MOVE_FOCUS_RIGHT_IN_PANE_MODE, PANE_MODE, QUIT,
|
||||
SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_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,
|
||||
SPLIT_RIGHT_IN_PANE_MODE, TAB_MODE,
|
||||
};
|
||||
|
||||
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
||||
|
|
@ -37,6 +38,7 @@ pub fn move_focus_right() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -74,6 +76,46 @@ pub fn move_focus_right_to_the_most_recently_used_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
.output_frames
|
||||
.lock()
|
||||
.unwrap();
|
||||
let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
|
||||
let snapshot_before_quit =
|
||||
get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
|
||||
assert_snapshot!(snapshot_before_quit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn move_focus_right_changes_tab() {
|
||||
let fake_win_size = PositionAndSize {
|
||||
columns: 121,
|
||||
rows: 20,
|
||||
x: 0,
|
||||
y: 0,
|
||||
..Default::default()
|
||||
};
|
||||
let mut fake_input_output = get_fake_os_input(&fake_win_size);
|
||||
fake_input_output.add_terminal_input(&[
|
||||
&PANE_MODE,
|
||||
&SPLIT_DOWN_IN_PANE_MODE,
|
||||
&ENTER,
|
||||
&TAB_MODE,
|
||||
&NEW_TAB_IN_TAB_MODE,
|
||||
&ENTER,
|
||||
&MOVE_FOCUS_RIGHT_IN_NORMAL_MODE,
|
||||
&QUIT,
|
||||
]);
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::tests::fakes::FakeInputOutput;
|
|||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
||||
use crate::{start, CliArgs};
|
||||
|
||||
use crate::common::input::config::Config;
|
||||
use crate::common::input::{config::Config, options::Options};
|
||||
use crate::tests::utils::commands::{
|
||||
MOVE_FOCUS_DOWN_IN_PANE_MODE, MOVE_FOCUS_UP_IN_PANE_MODE, PANE_MODE, QUIT,
|
||||
SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE,
|
||||
|
|
@ -36,6 +36,7 @@ pub fn move_focus_up() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -74,6 +75,7 @@ pub fn move_focus_up_to_the_most_recently_used_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::tests::fakes::FakeInputOutput;
|
|||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
||||
use crate::{start, CliArgs};
|
||||
|
||||
use crate::common::input::config::Config;
|
||||
use crate::common::input::{config::Config, options::Options};
|
||||
use crate::tests::utils::commands::{
|
||||
MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_DOWN_IN_RESIZE_MODE,
|
||||
RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, SLEEP, SPLIT_DOWN_IN_PANE_MODE,
|
||||
|
|
@ -48,6 +48,7 @@ pub fn resize_down_with_pane_above() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -93,6 +94,7 @@ pub fn resize_down_with_pane_below() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -144,6 +146,7 @@ pub fn resize_down_with_panes_above_and_below() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -194,6 +197,7 @@ pub fn resize_down_with_multiple_panes_above() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -246,6 +250,7 @@ pub fn resize_down_with_panes_above_aligned_left_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -297,6 +302,7 @@ pub fn resize_down_with_panes_below_aligned_left_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -346,6 +352,7 @@ pub fn resize_down_with_panes_above_aligned_right_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -396,6 +403,7 @@ pub fn resize_down_with_panes_below_aligned_right_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -449,6 +457,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -504,6 +513,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -576,6 +586,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -650,6 +661,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -692,6 +704,7 @@ pub fn cannot_resize_down_when_pane_below_is_at_minimum_height() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::tests::fakes::FakeInputOutput;
|
|||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
||||
use crate::{start, CliArgs};
|
||||
|
||||
use crate::common::input::config::Config;
|
||||
use crate::common::input::{config::Config, options::Options};
|
||||
use crate::tests::utils::commands::{
|
||||
MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE,
|
||||
RESIZE_UP_IN_RESIZE_MODE, SLEEP, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE,
|
||||
|
|
@ -44,6 +44,7 @@ pub fn resize_left_with_pane_to_the_left() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -87,6 +88,7 @@ pub fn resize_left_with_pane_to_the_right() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -132,6 +134,7 @@ pub fn resize_left_with_panes_to_the_left_and_right() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -180,6 +183,7 @@ pub fn resize_left_with_multiple_panes_to_the_left() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -230,6 +234,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -277,6 +282,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -326,6 +332,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_bottom_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -374,6 +381,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_bottom_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -427,6 +435,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pa
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -482,6 +491,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_p
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -554,6 +564,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -629,6 +640,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -671,6 +683,7 @@ pub fn cannot_resize_left_when_pane_to_the_left_is_at_minimum_width() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::tests::fakes::FakeInputOutput;
|
|||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
||||
use crate::{start, CliArgs};
|
||||
|
||||
use crate::common::input::config::Config;
|
||||
use crate::common::input::{config::Config, options::Options};
|
||||
use crate::tests::utils::commands::{
|
||||
MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_MODE, RESIZE_RIGHT_IN_RESIZE_MODE,
|
||||
RESIZE_UP_IN_RESIZE_MODE, SLEEP, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE,
|
||||
|
|
@ -44,6 +44,7 @@ pub fn resize_right_with_pane_to_the_left() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -87,6 +88,7 @@ pub fn resize_right_with_pane_to_the_right() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -132,6 +134,7 @@ pub fn resize_right_with_panes_to_the_left_and_right() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -180,6 +183,7 @@ pub fn resize_right_with_multiple_panes_to_the_left() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -230,6 +234,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -277,6 +282,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -326,6 +332,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_bottom_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -374,6 +381,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_bottom_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -427,6 +435,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_p
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -482,6 +491,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -554,6 +564,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -628,6 +639,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -670,6 +682,7 @@ pub fn cannot_resize_right_when_pane_to_the_left_is_at_minimum_width() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::tests::fakes::FakeInputOutput;
|
|||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
||||
use crate::{start, CliArgs};
|
||||
|
||||
use crate::common::input::config::Config;
|
||||
use crate::common::input::{config::Config, options::Options};
|
||||
use crate::tests::utils::commands::{
|
||||
MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE,
|
||||
RESIZE_UP_IN_RESIZE_MODE, SLEEP, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE,
|
||||
|
|
@ -46,6 +46,7 @@ pub fn resize_up_with_pane_above() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -91,6 +92,7 @@ pub fn resize_up_with_pane_below() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -141,6 +143,7 @@ pub fn resize_up_with_panes_above_and_below() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -190,6 +193,7 @@ pub fn resize_up_with_multiple_panes_above() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -240,6 +244,7 @@ pub fn resize_up_with_panes_above_aligned_left_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -291,6 +296,7 @@ pub fn resize_up_with_panes_below_aligned_left_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -340,6 +346,7 @@ pub fn resize_up_with_panes_above_aligned_right_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -390,6 +397,7 @@ pub fn resize_up_with_panes_below_aligned_right_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -443,6 +451,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -498,6 +507,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_current_pane() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -570,6 +580,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -644,6 +655,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -686,6 +698,7 @@ pub fn cannot_resize_up_when_pane_above_is_at_minimum_height() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
source: src/tests/integration/basic.rs
|
||||
expression: snapshot_before_quit
|
||||
|
||||
---
|
||||
line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line3-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line4-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line5-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line6-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line7-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line8-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
prompt $ █
|
||||
|
|
@ -3,7 +3,7 @@ source: src/tests/integration/compatibility.rs
|
|||
expression: snapshot_before_quit
|
||||
|
||||
---
|
||||
█
|
||||
|
||||
1 [||||||||||||||||||||||||||||||||||||||||||100.0%] Tasks: 79, 382 thr; 1 running
|
||||
2 [ 0.0%] Load average: 1.40 1.43 1.38
|
||||
3 [ 0.0%] Uptime: 2 days, 07:33:50
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
source: src/tests/integration/move_focus_left.rs
|
||||
expression: snapshot_before_quit
|
||||
|
||||
---
|
||||
line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
prompt $
|
||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
prompt $ █
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
source: src/tests/integration/move_focus_right.rs
|
||||
expression: snapshot_before_quit
|
||||
|
||||
---
|
||||
line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
prompt $
|
||||
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
prompt $ █
|
||||
|
|
@ -5,7 +5,7 @@ use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}
|
|||
use crate::{panes::PositionAndSize, tests::utils::commands::CLOSE_PANE_IN_PANE_MODE};
|
||||
use crate::{start, CliArgs};
|
||||
|
||||
use crate::common::input::config::Config;
|
||||
use crate::common::input::{config::Config, options::Options};
|
||||
use crate::tests::utils::commands::{
|
||||
CLOSE_TAB_IN_TAB_MODE, NEW_TAB_IN_TAB_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE,
|
||||
SWITCH_NEXT_TAB_IN_TAB_MODE, SWITCH_PREV_TAB_IN_TAB_MODE, TAB_MODE,
|
||||
|
|
@ -38,6 +38,7 @@ pub fn open_new_tab() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -74,6 +75,7 @@ pub fn switch_to_prev_tab() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -110,6 +112,7 @@ pub fn switch_to_next_tab() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -146,6 +149,7 @@ pub fn close_tab() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -183,6 +187,7 @@ pub fn close_last_pane_in_a_tab() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -222,6 +227,7 @@ pub fn close_the_middle_tab() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -266,6 +272,7 @@ pub fn close_the_tab_that_has_a_pane_in_fullscreen() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -302,6 +309,7 @@ pub fn closing_last_tab_exits_the_app() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::panes::PositionAndSize;
|
||||
use ::insta::assert_snapshot;
|
||||
|
||||
use crate::common::input::config::Config;
|
||||
use crate::common::input::{config::Config, options::Options};
|
||||
use crate::tests::fakes::FakeInputOutput;
|
||||
use crate::tests::utils::commands::QUIT;
|
||||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
||||
|
|
@ -35,6 +35,7 @@ pub fn window_width_decrease_with_one_pane() {
|
|||
opts,
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -71,6 +72,7 @@ pub fn window_width_increase_with_one_pane() {
|
|||
opts,
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -107,6 +109,7 @@ pub fn window_height_increase_with_one_pane() {
|
|||
opts,
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
@ -143,6 +146,7 @@ pub fn window_width_and_height_decrease_with_one_pane() {
|
|||
opts,
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::tests::fakes::FakeInputOutput;
|
|||
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
||||
use crate::{start, CliArgs};
|
||||
|
||||
use crate::common::input::config::Config;
|
||||
use crate::common::input::{config::Config, options::Options};
|
||||
use crate::tests::utils::commands::{
|
||||
MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE,
|
||||
TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE,
|
||||
|
|
@ -37,6 +37,7 @@ pub fn adding_new_terminal_in_fullscreen() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
@ -72,6 +73,7 @@ pub fn move_focus_is_disabled_in_fullscreen() {
|
|||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
Options::default(),
|
||||
);
|
||||
|
||||
let output_frames = fake_input_output
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ pub fn get_next_to_last_snapshot(mut snapshots: Vec<String>) -> Option<String> {
|
|||
pub mod commands {
|
||||
pub const QUIT: [u8; 1] = [17]; // ctrl-q
|
||||
pub const ESC: [u8; 1] = [27];
|
||||
pub const ENTER: [u8; 1] = [10]; // char '\n'
|
||||
|
||||
pub const MOVE_FOCUS_LEFT_IN_NORMAL_MODE: [u8; 2] = [27, 104]; // alt-h
|
||||
pub const MOVE_FOCUS_RIGHT_IN_NORMAL_MODE: [u8; 2] = [27, 108]; // alt-l
|
||||
|
||||
pub const PANE_MODE: [u8; 1] = [16]; // ctrl-p
|
||||
pub const SPAWN_TERMINAL_IN_PANE_MODE: [u8; 1] = [110]; // n
|
||||
|
|
@ -77,5 +81,8 @@ pub mod commands {
|
|||
pub const SWITCH_NEXT_TAB_IN_TAB_MODE: [u8; 1] = [108]; // l
|
||||
pub const SWITCH_PREV_TAB_IN_TAB_MODE: [u8; 1] = [104]; // h
|
||||
pub const CLOSE_TAB_IN_TAB_MODE: [u8; 1] = [120]; // x
|
||||
|
||||
pub const BRACKETED_PASTE_START: [u8; 6] = [27, 91, 50, 48, 48, 126]; // \u{1b}[200~
|
||||
pub const BRACKETED_PASTE_END: [u8; 6] = [27, 91, 50, 48, 49, 126]; // \u{1b}[201
|
||||
pub const SLEEP: [u8; 0] = [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ pub struct ModeInfo {
|
|||
// FIXME: This should probably return Keys and Actions, then sort out strings plugin-side
|
||||
pub keybinds: Vec<(String, String)>, // <shortcut> => <shortcut description>
|
||||
pub palette: Palette,
|
||||
pub capabilities: PluginCapabilities,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||
|
|
@ -141,3 +142,14 @@ pub struct PluginIds {
|
|||
pub plugin_id: u32,
|
||||
pub zellij_pid: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||
pub struct PluginCapabilities {
|
||||
pub arrow_fonts: bool,
|
||||
}
|
||||
|
||||
impl Default for PluginCapabilities {
|
||||
fn default() -> PluginCapabilities {
|
||||
PluginCapabilities { arrow_fonts: true }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue