merge(main): update branch with changes from main
This commit is contained in:
commit
0e73227fe2
49 changed files with 2420 additions and 554 deletions
14
Cargo.lock
generated
14
Cargo.lock
generated
|
|
@ -132,7 +132,7 @@ dependencies = [
|
||||||
"event-listener",
|
"event-listener",
|
||||||
"futures-lite",
|
"futures-lite",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"signal-hook 0.3.8",
|
"signal-hook",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1406,16 +1406,6 @@ dependencies = [
|
||||||
"yaml-rust",
|
"yaml-rust",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "signal-hook"
|
|
||||||
version = "0.1.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"signal-hook-registry",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook"
|
name = "signal-hook"
|
||||||
version = "0.3.8"
|
version = "0.3.8"
|
||||||
|
|
@ -2221,7 +2211,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"signal-hook 0.1.17",
|
"signal-hook",
|
||||||
"strip-ansi-escapes",
|
"strip-ansi-escapes",
|
||||||
"structopt",
|
"structopt",
|
||||||
"strum",
|
"strum",
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ nom = "6.0.1"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_yaml = "0.8"
|
serde_yaml = "0.8"
|
||||||
signal-hook = "0.1.10"
|
signal-hook = "0.3"
|
||||||
strip-ansi-escapes = "0.1.0"
|
strip-ansi-escapes = "0.1.0"
|
||||||
structopt = "0.3"
|
structopt = "0.3"
|
||||||
termion = "1.5.0"
|
termion = "1.5.0"
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,6 @@ Once the organization reaches 10 members, a reasonable and achievable process mu
|
||||||
* Denis Maximov <denis_maxim0v@protonmail.com>
|
* Denis Maximov <denis_maxim0v@protonmail.com>
|
||||||
* Kunal Mohan <kunalmohan99@gmail.com>
|
* Kunal Mohan <kunalmohan99@gmail.com>
|
||||||
* Henil Dedania <dedaniahenil@gmail.com>
|
* Henil Dedania <dedaniahenil@gmail.com>
|
||||||
|
* Roee Shapira <ro33.sha@gmail.com>
|
||||||
|
* Alex Kenji Berthold <aks.kenji@protonmail.com>
|
||||||
|
* Kyle Sutherland-Cash <kyle.sutherlandcash@gmail.com>
|
||||||
|
|
|
||||||
38
README.md
38
README.md
|
|
@ -40,6 +40,44 @@ The status bar on the bottom should guide you through the possible keyboard shor
|
||||||
|
|
||||||
For more build commands, take a look at [`Contributing.md`](CONTRIBUTING.md).
|
For more build commands, take a look at [`Contributing.md`](CONTRIBUTING.md).
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
It is possible to configure keyboard shortcuts and their actions in a yaml file.
|
||||||
|
An example file can be found under `example/config.yaml`.
|
||||||
|
|
||||||
|
Zellij will look for a file `/zellij/config.yaml` in the default configuration location of your os.
|
||||||
|
|
||||||
|
To pass a config file directly to zellij run it either with:
|
||||||
|
`cargo run -- config [FILE]` or `zellij config [FILE]`.
|
||||||
|
|
||||||
|
The structure is as follows:
|
||||||
|
```
|
||||||
|
keybinds:
|
||||||
|
normal:
|
||||||
|
- action: []
|
||||||
|
key: []
|
||||||
|
```
|
||||||
|
`normal` is one of the `modes` zellij can be in.
|
||||||
|
It is possible to bind a sequence of actions to numerous keys at the same time.
|
||||||
|
Here a reference to the [Key](https://docs.rs/termion/1.5.6/termion/event/enum.Key.html) format that is used.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
```
|
||||||
|
keybinds:
|
||||||
|
normal:
|
||||||
|
- action: [ NewTab, GoToTab: 1,]
|
||||||
|
key: [ Char: 'c',]
|
||||||
|
```
|
||||||
|
Will create a new tab and then switch to tab number 1 on pressing the
|
||||||
|
`c` key.
|
||||||
|
Whereas:
|
||||||
|
```
|
||||||
|
keybinds:
|
||||||
|
normal:
|
||||||
|
- action: [ NewTab,]
|
||||||
|
key: [ Char: 'c', Char: 'd',]
|
||||||
|
```
|
||||||
|
Will create a new tab on pressing either the `c` or the `d` key.
|
||||||
|
|
||||||
# What is the current status of the project?
|
# What is the current status of the project?
|
||||||
|
|
||||||
Zellij is in the last stages of being VT compatible. As much as modern terminals are.
|
Zellij is in the last stages of being VT compatible. As much as modern terminals are.
|
||||||
|
|
|
||||||
|
|
@ -30,16 +30,93 @@ _zellij() {
|
||||||
'--help[Prints help information]' \
|
'--help[Prints help information]' \
|
||||||
'-V[Prints version information]' \
|
'-V[Prints version information]' \
|
||||||
'--version[Prints version information]' \
|
'--version[Prints version information]' \
|
||||||
|
":: :_zellij_commands" \
|
||||||
|
"*::: :->zellij" \
|
||||||
&& ret=0
|
&& ret=0
|
||||||
|
case $state in
|
||||||
|
(zellij)
|
||||||
|
words=($line[1] "${words[@]}")
|
||||||
|
(( CURRENT += 1 ))
|
||||||
|
curcontext="${curcontext%:*:*}:zellij-command-$line[1]:"
|
||||||
|
case $line[1] in
|
||||||
|
(c)
|
||||||
|
_arguments "${_arguments_options[@]}" \
|
||||||
|
'--clean[Disables loading of configuration file at default location]' \
|
||||||
|
'-h[Prints help information]' \
|
||||||
|
'--help[Prints help information]' \
|
||||||
|
'-V[Prints version information]' \
|
||||||
|
'--version[Prints version information]' \
|
||||||
|
'::path:_files' \
|
||||||
|
&& ret=0
|
||||||
|
;;
|
||||||
|
(c)
|
||||||
|
_arguments "${_arguments_options[@]}" \
|
||||||
|
'--clean[Disables loading of configuration file at default location]' \
|
||||||
|
'-h[Prints help information]' \
|
||||||
|
'--help[Prints help information]' \
|
||||||
|
'-V[Prints version information]' \
|
||||||
|
'--version[Prints version information]' \
|
||||||
|
'::path:_files' \
|
||||||
|
&& ret=0
|
||||||
|
;;
|
||||||
|
(config)
|
||||||
|
_arguments "${_arguments_options[@]}" \
|
||||||
|
'--clean[Disables loading of configuration file at default location]' \
|
||||||
|
'-h[Prints help information]' \
|
||||||
|
'--help[Prints help information]' \
|
||||||
|
'-V[Prints version information]' \
|
||||||
|
'--version[Prints version information]' \
|
||||||
|
'::path:_files' \
|
||||||
|
&& ret=0
|
||||||
|
;;
|
||||||
|
(help)
|
||||||
|
_arguments "${_arguments_options[@]}" \
|
||||||
|
'-h[Prints help information]' \
|
||||||
|
'--help[Prints help information]' \
|
||||||
|
'-V[Prints version information]' \
|
||||||
|
'--version[Prints version information]' \
|
||||||
|
&& ret=0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
(( $+functions[_zellij_commands] )) ||
|
(( $+functions[_zellij_commands] )) ||
|
||||||
_zellij_commands() {
|
_zellij_commands() {
|
||||||
local commands; commands=(
|
local commands; commands=(
|
||||||
|
"config:Path to the configuration yaml file" \
|
||||||
|
"help:Prints this message or the help of the given subcommand(s)" \
|
||||||
)
|
)
|
||||||
_describe -t commands 'zellij commands' commands "$@"
|
_describe -t commands 'zellij commands' commands "$@"
|
||||||
}
|
}
|
||||||
|
(( $+functions[_c_commands] )) ||
|
||||||
|
_c_commands() {
|
||||||
|
local commands; commands=(
|
||||||
|
|
||||||
|
)
|
||||||
|
_describe -t commands 'c commands' commands "$@"
|
||||||
|
}
|
||||||
|
(( $+functions[_zellij__c_commands] )) ||
|
||||||
|
_zellij__c_commands() {
|
||||||
|
local commands; commands=(
|
||||||
|
|
||||||
|
)
|
||||||
|
_describe -t commands 'zellij c commands' commands "$@"
|
||||||
|
}
|
||||||
|
(( $+functions[_zellij__config_commands] )) ||
|
||||||
|
_zellij__config_commands() {
|
||||||
|
local commands; commands=(
|
||||||
|
|
||||||
|
)
|
||||||
|
_describe -t commands 'zellij config commands' commands "$@"
|
||||||
|
}
|
||||||
|
(( $+functions[_zellij__help_commands] )) ||
|
||||||
|
_zellij__help_commands() {
|
||||||
|
local commands; commands=(
|
||||||
|
|
||||||
|
)
|
||||||
|
_describe -t commands 'zellij help commands' commands "$@"
|
||||||
|
}
|
||||||
|
|
||||||
_zellij "$@"
|
_zellij "$@"
|
||||||
|
|
@ -13,6 +13,15 @@ _zellij() {
|
||||||
cmd="zellij"
|
cmd="zellij"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
c)
|
||||||
|
cmd+="__c"
|
||||||
|
;;
|
||||||
|
config)
|
||||||
|
cmd+="__config"
|
||||||
|
;;
|
||||||
|
help)
|
||||||
|
cmd+="__help"
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
@ -20,7 +29,7 @@ _zellij() {
|
||||||
|
|
||||||
case "${cmd}" in
|
case "${cmd}" in
|
||||||
zellij)
|
zellij)
|
||||||
opts=" -m -d -h -V -s -o -l --move-focus --debug --help --version --split --open-file --max-panes --layout "
|
opts=" -m -d -h -V -s -o -l --move-focus --debug --help --version --split --open-file --max-panes --layout config help c c"
|
||||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
return 0
|
return 0
|
||||||
|
|
@ -63,6 +72,51 @@ _zellij() {
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
zellij__c)
|
||||||
|
opts=" -h -V --clean --help --version <path> "
|
||||||
|
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
case "${prev}" in
|
||||||
|
|
||||||
|
*)
|
||||||
|
COMPREPLY=()
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
zellij__config)
|
||||||
|
opts=" -h -V --clean --help --version <path> "
|
||||||
|
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
case "${prev}" in
|
||||||
|
|
||||||
|
*)
|
||||||
|
COMPREPLY=()
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
zellij__help)
|
||||||
|
opts=" -h -V --help --version "
|
||||||
|
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
case "${prev}" in
|
||||||
|
|
||||||
|
*)
|
||||||
|
COMPREPLY=()
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,10 @@ complete -c zellij -n "__fish_use_subcommand" -s m -l move-focus -d 'Send "move
|
||||||
complete -c zellij -n "__fish_use_subcommand" -s d -l debug
|
complete -c zellij -n "__fish_use_subcommand" -s d -l debug
|
||||||
complete -c zellij -n "__fish_use_subcommand" -s h -l help -d 'Prints help information'
|
complete -c zellij -n "__fish_use_subcommand" -s h -l help -d 'Prints help information'
|
||||||
complete -c zellij -n "__fish_use_subcommand" -s V -l version -d 'Prints version information'
|
complete -c zellij -n "__fish_use_subcommand" -s V -l version -d 'Prints version information'
|
||||||
|
complete -c zellij -n "__fish_use_subcommand" -f -a "config" -d 'Path to the configuration yaml file'
|
||||||
|
complete -c zellij -n "__fish_use_subcommand" -f -a "help" -d 'Prints this message or the help of the given subcommand(s)'
|
||||||
|
complete -c zellij -n "__fish_seen_subcommand_from config" -l clean -d 'Disables loading of configuration file at default location'
|
||||||
|
complete -c zellij -n "__fish_seen_subcommand_from config" -s h -l help -d 'Prints help information'
|
||||||
|
complete -c zellij -n "__fish_seen_subcommand_from config" -s V -l version -d 'Prints version information'
|
||||||
|
complete -c zellij -n "__fish_seen_subcommand_from help" -s h -l help -d 'Prints help information'
|
||||||
|
complete -c zellij -n "__fish_seen_subcommand_from help" -s V -l version -d 'Prints version information'
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -99,7 +99,7 @@ fn unselected_mode_shortcut(letter: char, text: &str) -> LinePart {
|
||||||
suffix_separator,
|
suffix_separator,
|
||||||
])
|
])
|
||||||
.to_string(),
|
.to_string(),
|
||||||
len: text.chars().count() + 6, // 2 for the arrows, 3 for the char separators, 1 for the character
|
len: text.chars().count() + 7, // 2 for the arrows, 3 for the char separators, 1 for the character, 1 for the text padding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,7 +129,7 @@ fn selected_mode_shortcut(letter: char, text: &str) -> LinePart {
|
||||||
suffix_separator,
|
suffix_separator,
|
||||||
])
|
])
|
||||||
.to_string(),
|
.to_string(),
|
||||||
len: text.chars().count() + 6, // 2 for the arrows, 3 for the char separators, 1 for the character
|
len: text.chars().count() + 7, // 2 for the arrows, 3 for the char separators, 1 for the character, 1 for the text padding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,8 @@ impl ZellijTile for State {
|
||||||
let second_line = keybinds(&self.mode_info, cols);
|
let second_line = keybinds(&self.mode_info, cols);
|
||||||
|
|
||||||
// [48;5;238m is gray background, [0K is so that it fills the rest of the line
|
// [48;5;238m is gray background, [0K is so that it fills the rest of the line
|
||||||
// [48;5;16m is black background, [0K is so that it fills the rest of the line
|
// [m is background reset, [0K is so that it clears the rest of the line
|
||||||
println!("{}\u{1b}[48;5;238m\u{1b}[0K", first_line);
|
println!("{}\u{1b}[48;5;238m\u{1b}[0K", first_line);
|
||||||
println!("{}\u{1b}[48;5;16m\u{1b}[0K", second_line);
|
println!("\u{1b}[m{}\u{1b}[0K", second_line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,122 @@ fn first_word_shortcut(is_first_shortcut: bool, letter: &str, description: &str)
|
||||||
len,
|
len,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn quicknav_full() -> LinePart {
|
||||||
|
let text_first_part = " Tip: ";
|
||||||
|
let alt = "Alt";
|
||||||
|
let text_second_part = " + ";
|
||||||
|
let new_pane_shortcut = "n";
|
||||||
|
let text_third_part = " => open new pane. ";
|
||||||
|
let second_alt = "Alt";
|
||||||
|
let text_fourth_part = " + ";
|
||||||
|
let brackets_navigation = "[]";
|
||||||
|
let text_fifth_part = " or ";
|
||||||
|
let hjkl_navigation = "hjkl";
|
||||||
|
let text_sixths_part = " => navigate between panes.";
|
||||||
|
let len = text_first_part.chars().count()
|
||||||
|
+ alt.chars().count()
|
||||||
|
+ text_second_part.chars().count()
|
||||||
|
+ new_pane_shortcut.chars().count()
|
||||||
|
+ text_third_part.chars().count()
|
||||||
|
+ second_alt.chars().count()
|
||||||
|
+ text_fourth_part.chars().count()
|
||||||
|
+ brackets_navigation.chars().count()
|
||||||
|
+ text_fifth_part.chars().count()
|
||||||
|
+ hjkl_navigation.chars().count()
|
||||||
|
+ text_sixths_part.chars().count();
|
||||||
|
LinePart {
|
||||||
|
part: format!(
|
||||||
|
"{}{}{}{}{}{}{}{}{}{}{}",
|
||||||
|
text_first_part,
|
||||||
|
Style::new().fg(ORANGE).bold().paint(alt),
|
||||||
|
text_second_part,
|
||||||
|
Style::new().fg(GREEN).bold().paint(new_pane_shortcut),
|
||||||
|
text_third_part,
|
||||||
|
Style::new().fg(ORANGE).bold().paint(second_alt),
|
||||||
|
text_fourth_part,
|
||||||
|
Style::new().fg(GREEN).bold().paint(brackets_navigation),
|
||||||
|
text_fifth_part,
|
||||||
|
Style::new().fg(GREEN).bold().paint(hjkl_navigation),
|
||||||
|
text_sixths_part,
|
||||||
|
),
|
||||||
|
len,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quicknav_medium() -> LinePart {
|
||||||
|
let text_first_part = " Tip: ";
|
||||||
|
let alt = "Alt";
|
||||||
|
let text_second_part = " + ";
|
||||||
|
let new_pane_shortcut = "n";
|
||||||
|
let text_third_part = " => new pane. ";
|
||||||
|
let second_alt = "Alt";
|
||||||
|
let text_fourth_part = " + ";
|
||||||
|
let brackets_navigation = "[]";
|
||||||
|
let text_fifth_part = " or ";
|
||||||
|
let hjkl_navigation = "hjkl";
|
||||||
|
let text_sixths_part = " => navigate.";
|
||||||
|
let len = text_first_part.chars().count()
|
||||||
|
+ alt.chars().count()
|
||||||
|
+ text_second_part.chars().count()
|
||||||
|
+ new_pane_shortcut.chars().count()
|
||||||
|
+ text_third_part.chars().count()
|
||||||
|
+ second_alt.chars().count()
|
||||||
|
+ text_fourth_part.chars().count()
|
||||||
|
+ brackets_navigation.chars().count()
|
||||||
|
+ text_fifth_part.chars().count()
|
||||||
|
+ hjkl_navigation.chars().count()
|
||||||
|
+ text_sixths_part.chars().count();
|
||||||
|
LinePart {
|
||||||
|
part: format!(
|
||||||
|
"{}{}{}{}{}{}{}{}{}{}{}",
|
||||||
|
text_first_part,
|
||||||
|
Style::new().fg(ORANGE).bold().paint(alt),
|
||||||
|
text_second_part,
|
||||||
|
Style::new().fg(GREEN).bold().paint(new_pane_shortcut),
|
||||||
|
text_third_part,
|
||||||
|
Style::new().fg(ORANGE).bold().paint(second_alt),
|
||||||
|
text_fourth_part,
|
||||||
|
Style::new().fg(GREEN).bold().paint(brackets_navigation),
|
||||||
|
text_fifth_part,
|
||||||
|
Style::new().fg(GREEN).bold().paint(hjkl_navigation),
|
||||||
|
text_sixths_part,
|
||||||
|
),
|
||||||
|
len,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quicknav_short() -> LinePart {
|
||||||
|
let text_first_part = " QuickNav: ";
|
||||||
|
let alt = "Alt";
|
||||||
|
let text_second_part = " + ";
|
||||||
|
let new_pane_shortcut = "n";
|
||||||
|
let text_third_part = "/";
|
||||||
|
let brackets_navigation = "[]";
|
||||||
|
let text_fifth_part = "/";
|
||||||
|
let hjkl_navigation = "hjkl";
|
||||||
|
let len = text_first_part.chars().count()
|
||||||
|
+ alt.chars().count()
|
||||||
|
+ text_second_part.chars().count()
|
||||||
|
+ new_pane_shortcut.chars().count()
|
||||||
|
+ text_third_part.chars().count()
|
||||||
|
+ brackets_navigation.chars().count()
|
||||||
|
+ text_fifth_part.chars().count()
|
||||||
|
+ hjkl_navigation.chars().count();
|
||||||
|
LinePart {
|
||||||
|
part: format!(
|
||||||
|
"{}{}{}{}{}{}{}{}",
|
||||||
|
text_first_part,
|
||||||
|
Style::new().fg(ORANGE).bold().paint(alt),
|
||||||
|
text_second_part,
|
||||||
|
Style::new().fg(GREEN).bold().paint(new_pane_shortcut),
|
||||||
|
text_third_part,
|
||||||
|
Style::new().fg(GREEN).bold().paint(brackets_navigation),
|
||||||
|
text_fifth_part,
|
||||||
|
Style::new().fg(GREEN).bold().paint(hjkl_navigation),
|
||||||
|
),
|
||||||
|
len,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn locked_interface_indication() -> LinePart {
|
fn locked_interface_indication() -> LinePart {
|
||||||
let locked_text = " -- INTERFACE LOCKED -- ";
|
let locked_text = " -- INTERFACE LOCKED -- ";
|
||||||
|
|
@ -99,7 +215,7 @@ fn select_pane_shortcut(is_first_shortcut: bool) -> LinePart {
|
||||||
|
|
||||||
fn full_shortcut_list(help: &ModeInfo) -> LinePart {
|
fn full_shortcut_list(help: &ModeInfo) -> LinePart {
|
||||||
match help.mode {
|
match help.mode {
|
||||||
InputMode::Normal => LinePart::default(),
|
InputMode::Normal => quicknav_full(),
|
||||||
InputMode::Locked => locked_interface_indication(),
|
InputMode::Locked => locked_interface_indication(),
|
||||||
_ => {
|
_ => {
|
||||||
let mut line_part = LinePart::default();
|
let mut line_part = LinePart::default();
|
||||||
|
|
@ -118,7 +234,7 @@ fn full_shortcut_list(help: &ModeInfo) -> LinePart {
|
||||||
|
|
||||||
fn shortened_shortcut_list(help: &ModeInfo) -> LinePart {
|
fn shortened_shortcut_list(help: &ModeInfo) -> LinePart {
|
||||||
match help.mode {
|
match help.mode {
|
||||||
InputMode::Normal => LinePart::default(),
|
InputMode::Normal => quicknav_medium(),
|
||||||
InputMode::Locked => locked_interface_indication(),
|
InputMode::Locked => locked_interface_indication(),
|
||||||
_ => {
|
_ => {
|
||||||
let mut line_part = LinePart::default();
|
let mut line_part = LinePart::default();
|
||||||
|
|
@ -137,7 +253,14 @@ fn shortened_shortcut_list(help: &ModeInfo) -> LinePart {
|
||||||
|
|
||||||
fn best_effort_shortcut_list(help: &ModeInfo, max_len: usize) -> LinePart {
|
fn best_effort_shortcut_list(help: &ModeInfo, max_len: usize) -> LinePart {
|
||||||
match help.mode {
|
match help.mode {
|
||||||
InputMode::Normal => LinePart::default(),
|
InputMode::Normal => {
|
||||||
|
let line_part = quicknav_short();
|
||||||
|
if line_part.len <= max_len {
|
||||||
|
line_part
|
||||||
|
} else {
|
||||||
|
LinePart::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
InputMode::Locked => {
|
InputMode::Locked => {
|
||||||
let line_part = locked_interface_indication();
|
let line_part = locked_interface_indication();
|
||||||
if line_part.len <= max_len {
|
if line_part.len <= max_len {
|
||||||
|
|
@ -157,7 +280,7 @@ fn best_effort_shortcut_list(help: &ModeInfo, max_len: usize) -> LinePart {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
line_part.len += shortcut.len;
|
line_part.len += shortcut.len;
|
||||||
line_part.part = format!("{}{}", line_part.part, shortcut,);
|
line_part.part = format!("{}{}", line_part.part, shortcut);
|
||||||
}
|
}
|
||||||
let select_pane_shortcut = select_pane_shortcut(help.keybinds.is_empty());
|
let select_pane_shortcut = select_pane_shortcut(help.keybinds.is_empty());
|
||||||
if line_part.len + select_pane_shortcut.len <= max_len {
|
if line_part.len + select_pane_shortcut.len <= max_len {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ impl ZellijTile for State {
|
||||||
let next = self.selected().saturating_add(1);
|
let next = self.selected().saturating_add(1);
|
||||||
*self.selected_mut() = min(self.files.len() - 1, next);
|
*self.selected_mut() = min(self.files.len() - 1, next);
|
||||||
}
|
}
|
||||||
Key::Right | Key::Char('\n') | Key::Char('l') => {
|
Key::Right | Key::Char('\n') | Key::Char('l') if !self.files.is_empty() => {
|
||||||
match self.files[self.selected()].clone() {
|
match self.files[self.selected()].clone() {
|
||||||
FsEntry::Dir(p, _) => {
|
FsEntry::Dir(p, _) => {
|
||||||
self.path = p;
|
self.path = p;
|
||||||
|
|
|
||||||
26
example/config.yaml
Normal file
26
example/config.yaml
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
keybinds:
|
||||||
|
normal:
|
||||||
|
- action: [GoToTab: 1,]
|
||||||
|
key: [F: 1,]
|
||||||
|
- action: [GoToTab: 2,]
|
||||||
|
key: [F: 2,]
|
||||||
|
- action: [GoToTab: 3,]
|
||||||
|
key: [F: 3,]
|
||||||
|
- action: [GoToTab: 4,]
|
||||||
|
key: [F: 4,]
|
||||||
|
- action: [NewTab,]
|
||||||
|
key: [F: 5,]
|
||||||
|
- action: [MoveFocus: Left,]
|
||||||
|
key: [ Alt: h,]
|
||||||
|
- action: [MoveFocus: Right,]
|
||||||
|
key: [ Alt: l,]
|
||||||
|
- action: [MoveFocus: Down,]
|
||||||
|
key: [ Alt: j,]
|
||||||
|
- action: [MoveFocus: Up,]
|
||||||
|
key: [ Alt: k,]
|
||||||
|
pane:
|
||||||
|
- action: [ NewPane:, SwitchToMode: Normal,]
|
||||||
|
key: [Char: 'n',]
|
||||||
|
- action: [ NewPane: , ]
|
||||||
|
key: [Char: 'N',]
|
||||||
17
src/cli.rs
17
src/cli.rs
|
|
@ -1,7 +1,7 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
#[derive(StructOpt, Debug, Default)]
|
#[derive(StructOpt, Default, Debug)]
|
||||||
#[structopt(name = "zellij")]
|
#[structopt(name = "zellij")]
|
||||||
pub struct CliArgs {
|
pub struct CliArgs {
|
||||||
/// Send "split (direction h == horizontal / v == vertical)" to active zellij session
|
/// Send "split (direction h == horizontal / v == vertical)" to active zellij session
|
||||||
|
|
@ -24,6 +24,21 @@ pub struct CliArgs {
|
||||||
#[structopt(short, long)]
|
#[structopt(short, long)]
|
||||||
pub layout: Option<PathBuf>,
|
pub layout: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[structopt(subcommand)]
|
||||||
|
pub config: Option<ConfigCli>,
|
||||||
|
|
||||||
#[structopt(short, long)]
|
#[structopt(short, long)]
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, StructOpt)]
|
||||||
|
pub enum ConfigCli {
|
||||||
|
/// Path to the configuration yaml file
|
||||||
|
#[structopt(alias = "c")]
|
||||||
|
Config {
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
#[structopt(long)]
|
||||||
|
/// Disables loading of configuration file at default location
|
||||||
|
clean: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ pub mod boundary_type {
|
||||||
|
|
||||||
pub mod colors {
|
pub mod colors {
|
||||||
use ansi_term::Colour::{self, Fixed};
|
use ansi_term::Colour::{self, Fixed};
|
||||||
pub const WHITE: Colour = Fixed(255);
|
|
||||||
pub const GREEN: Colour = Fixed(154);
|
pub const GREEN: Colour = Fixed(154);
|
||||||
pub const GRAY: Colour = Fixed(238);
|
pub const GRAY: Colour = Fixed(238);
|
||||||
|
pub const ORANGE: Colour = Fixed(166);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type BoundaryType = &'static str; // easy way to refer to boundary_type above
|
pub type BoundaryType = &'static str; // easy way to refer to boundary_type above
|
||||||
|
|
@ -768,7 +768,7 @@ impl Boundaries {
|
||||||
let color = match color.is_some() {
|
let color = match color.is_some() {
|
||||||
true => match input_mode {
|
true => match input_mode {
|
||||||
InputMode::Normal | InputMode::Locked => Some(colors::GREEN),
|
InputMode::Normal | InputMode::Locked => Some(colors::GREEN),
|
||||||
_ => Some(colors::WHITE),
|
_ => Some(colors::ORANGE),
|
||||||
},
|
},
|
||||||
false => None,
|
false => None,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
pub mod boundaries;
|
pub mod boundaries;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
|
pub mod pane_resizer;
|
||||||
pub mod panes;
|
pub mod panes;
|
||||||
pub mod tab;
|
pub mod tab;
|
||||||
|
|
||||||
|
|
|
||||||
508
src/client/pane_resizer.rs
Normal file
508
src/client/pane_resizer.rs
Normal file
|
|
@ -0,0 +1,508 @@
|
||||||
|
use crate::os_input_output::OsApi;
|
||||||
|
use crate::panes::{PaneId, PositionAndSize};
|
||||||
|
use crate::tab::Pane;
|
||||||
|
use std::collections::{BTreeMap, HashSet};
|
||||||
|
|
||||||
|
pub struct PaneResizer<'a> {
|
||||||
|
panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>,
|
||||||
|
os_api: &'a mut Box<dyn OsApi>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: currently there are some functions here duplicated with Tab
|
||||||
|
// the reason for this is that we need to get rid of the expansion_boundary
|
||||||
|
// otherwise we'll have a big separation of concerns issue
|
||||||
|
// once that is done, all resizing functions should move here
|
||||||
|
|
||||||
|
impl<'a> PaneResizer<'a> {
|
||||||
|
pub fn new(
|
||||||
|
panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>,
|
||||||
|
os_api: &'a mut Box<dyn OsApi>,
|
||||||
|
) -> Self {
|
||||||
|
PaneResizer { panes, os_api }
|
||||||
|
}
|
||||||
|
pub fn resize(
|
||||||
|
&mut self,
|
||||||
|
mut current_size: PositionAndSize,
|
||||||
|
new_size: PositionAndSize,
|
||||||
|
) -> Option<(isize, isize)> {
|
||||||
|
// (column_difference, row_difference)
|
||||||
|
let mut successfully_resized = false;
|
||||||
|
let mut column_difference: isize = 0;
|
||||||
|
let mut row_difference: isize = 0;
|
||||||
|
if new_size.columns < current_size.columns {
|
||||||
|
let reduce_by = current_size.columns - new_size.columns;
|
||||||
|
find_reducible_vertical_chain(
|
||||||
|
&self.panes,
|
||||||
|
reduce_by,
|
||||||
|
current_size.columns,
|
||||||
|
current_size.rows,
|
||||||
|
)
|
||||||
|
.map(|panes_to_resize| {
|
||||||
|
self.reduce_panes_left_and_pull_adjacents_left(panes_to_resize, reduce_by);
|
||||||
|
column_difference = new_size.columns as isize - current_size.columns as isize;
|
||||||
|
current_size.columns = (current_size.columns as isize + column_difference) as usize;
|
||||||
|
successfully_resized = true;
|
||||||
|
});
|
||||||
|
} else if new_size.columns > current_size.columns {
|
||||||
|
let increase_by = new_size.columns - current_size.columns;
|
||||||
|
find_increasable_vertical_chain(
|
||||||
|
&self.panes,
|
||||||
|
increase_by,
|
||||||
|
current_size.columns,
|
||||||
|
current_size.rows,
|
||||||
|
)
|
||||||
|
.map(|panes_to_resize| {
|
||||||
|
self.increase_panes_right_and_push_adjacents_right(panes_to_resize, increase_by);
|
||||||
|
column_difference = new_size.columns as isize - current_size.columns as isize;
|
||||||
|
current_size.columns = (current_size.columns as isize + column_difference) as usize;
|
||||||
|
successfully_resized = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if new_size.rows < current_size.rows {
|
||||||
|
let reduce_by = current_size.rows - new_size.rows;
|
||||||
|
find_reducible_horizontal_chain(
|
||||||
|
&self.panes,
|
||||||
|
reduce_by,
|
||||||
|
current_size.columns,
|
||||||
|
current_size.rows,
|
||||||
|
)
|
||||||
|
.map(|panes_to_resize| {
|
||||||
|
self.reduce_panes_up_and_pull_adjacents_up(panes_to_resize, reduce_by);
|
||||||
|
row_difference = new_size.rows as isize - current_size.rows as isize;
|
||||||
|
current_size.rows = (current_size.rows as isize + row_difference) as usize;
|
||||||
|
successfully_resized = true;
|
||||||
|
});
|
||||||
|
} else if new_size.rows > current_size.rows {
|
||||||
|
let increase_by = new_size.rows - current_size.rows;
|
||||||
|
find_increasable_horizontal_chain(
|
||||||
|
&self.panes,
|
||||||
|
increase_by,
|
||||||
|
current_size.columns,
|
||||||
|
current_size.rows,
|
||||||
|
)
|
||||||
|
.map(|panes_to_resize| {
|
||||||
|
self.increase_panes_down_and_push_down_adjacents(panes_to_resize, increase_by);
|
||||||
|
row_difference = new_size.rows as isize - current_size.rows as isize;
|
||||||
|
current_size.rows = (current_size.rows as isize + row_difference) as usize;
|
||||||
|
successfully_resized = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if successfully_resized {
|
||||||
|
Some((column_difference, row_difference))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn reduce_panes_left_and_pull_adjacents_left(
|
||||||
|
&mut self,
|
||||||
|
panes_to_reduce: Vec<PaneId>,
|
||||||
|
reduce_by: usize,
|
||||||
|
) {
|
||||||
|
let mut pulled_panes: HashSet<PaneId> = HashSet::new();
|
||||||
|
for pane_id in panes_to_reduce {
|
||||||
|
let (pane_x, pane_y, pane_columns, pane_rows) = {
|
||||||
|
let pane = self.panes.get(&pane_id).unwrap();
|
||||||
|
(pane.x(), pane.y(), pane.columns(), pane.rows())
|
||||||
|
};
|
||||||
|
let panes_to_pull = self.panes.values_mut().filter(|p| {
|
||||||
|
p.x() > pane_x + pane_columns
|
||||||
|
&& (p.y() <= pane_y && p.y() + p.rows() >= pane_y
|
||||||
|
|| p.y() >= pane_y && p.y() + p.rows() <= pane_y + pane_rows)
|
||||||
|
});
|
||||||
|
for pane in panes_to_pull {
|
||||||
|
if !pulled_panes.contains(&pane.pid()) {
|
||||||
|
pane.pull_left(reduce_by);
|
||||||
|
pulled_panes.insert(pane.pid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.reduce_pane_width_left(&pane_id, reduce_by);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn reduce_panes_up_and_pull_adjacents_up(
|
||||||
|
&mut self,
|
||||||
|
panes_to_reduce: Vec<PaneId>,
|
||||||
|
reduce_by: usize,
|
||||||
|
) {
|
||||||
|
let mut pulled_panes: HashSet<PaneId> = HashSet::new();
|
||||||
|
for pane_id in panes_to_reduce {
|
||||||
|
let (pane_x, pane_y, pane_columns, pane_rows) = {
|
||||||
|
let pane = self.panes.get(&pane_id).unwrap();
|
||||||
|
(pane.x(), pane.y(), pane.columns(), pane.rows())
|
||||||
|
};
|
||||||
|
let panes_to_pull = self.panes.values_mut().filter(|p| {
|
||||||
|
p.y() > pane_y + pane_rows
|
||||||
|
&& (p.x() <= pane_x && p.x() + p.columns() >= pane_x
|
||||||
|
|| p.x() >= pane_x && p.x() + p.columns() <= pane_x + pane_columns)
|
||||||
|
});
|
||||||
|
for pane in panes_to_pull {
|
||||||
|
if !pulled_panes.contains(&pane.pid()) {
|
||||||
|
pane.pull_up(reduce_by);
|
||||||
|
pulled_panes.insert(pane.pid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.reduce_pane_height_up(&pane_id, reduce_by);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn increase_panes_down_and_push_down_adjacents(
|
||||||
|
&mut self,
|
||||||
|
panes_to_increase: Vec<PaneId>,
|
||||||
|
increase_by: usize,
|
||||||
|
) {
|
||||||
|
let mut pushed_panes: HashSet<PaneId> = HashSet::new();
|
||||||
|
for pane_id in panes_to_increase {
|
||||||
|
let (pane_x, pane_y, pane_columns, pane_rows) = {
|
||||||
|
let pane = self.panes.get(&pane_id).unwrap();
|
||||||
|
(pane.x(), pane.y(), pane.columns(), pane.rows())
|
||||||
|
};
|
||||||
|
let panes_to_push = self.panes.values_mut().filter(|p| {
|
||||||
|
p.y() > pane_y + pane_rows
|
||||||
|
&& (p.x() <= pane_x && p.x() + p.columns() >= pane_x
|
||||||
|
|| p.x() >= pane_x && p.x() + p.columns() <= pane_x + pane_columns)
|
||||||
|
});
|
||||||
|
for pane in panes_to_push {
|
||||||
|
if !pushed_panes.contains(&pane.pid()) {
|
||||||
|
pane.push_down(increase_by);
|
||||||
|
pushed_panes.insert(pane.pid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.increase_pane_height_down(&pane_id, increase_by);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn increase_panes_right_and_push_adjacents_right(
|
||||||
|
&mut self,
|
||||||
|
panes_to_increase: Vec<PaneId>,
|
||||||
|
increase_by: usize,
|
||||||
|
) {
|
||||||
|
let mut pushed_panes: HashSet<PaneId> = HashSet::new();
|
||||||
|
for pane_id in panes_to_increase {
|
||||||
|
let (pane_x, pane_y, pane_columns, pane_rows) = {
|
||||||
|
let pane = self.panes.get(&pane_id).unwrap();
|
||||||
|
(pane.x(), pane.y(), pane.columns(), pane.rows())
|
||||||
|
};
|
||||||
|
let panes_to_push = self.panes.values_mut().filter(|p| {
|
||||||
|
p.x() > pane_x + pane_columns
|
||||||
|
&& (p.y() <= pane_y && p.y() + p.rows() >= pane_y
|
||||||
|
|| p.y() >= pane_y && p.y() + p.rows() <= pane_y + pane_rows)
|
||||||
|
});
|
||||||
|
for pane in panes_to_push {
|
||||||
|
if !pushed_panes.contains(&pane.pid()) {
|
||||||
|
pane.push_right(increase_by);
|
||||||
|
pushed_panes.insert(pane.pid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.increase_pane_width_right(&pane_id, increase_by);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn reduce_pane_height_up(&mut self, id: &PaneId, count: usize) {
|
||||||
|
let pane = self.panes.get_mut(id).unwrap();
|
||||||
|
pane.reduce_height_up(count);
|
||||||
|
if let PaneId::Terminal(pid) = id {
|
||||||
|
self.os_api
|
||||||
|
.set_terminal_size_using_fd(*pid, pane.columns() as u16, pane.rows() as u16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn increase_pane_height_down(&mut self, id: &PaneId, count: usize) {
|
||||||
|
let pane = self.panes.get_mut(id).unwrap();
|
||||||
|
pane.increase_height_down(count);
|
||||||
|
if let PaneId::Terminal(pid) = pane.pid() {
|
||||||
|
self.os_api
|
||||||
|
.set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn increase_pane_width_right(&mut self, id: &PaneId, count: usize) {
|
||||||
|
let pane = self.panes.get_mut(id).unwrap();
|
||||||
|
pane.increase_width_right(count);
|
||||||
|
if let PaneId::Terminal(pid) = pane.pid() {
|
||||||
|
self.os_api
|
||||||
|
.set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn reduce_pane_width_left(&mut self, id: &PaneId, count: usize) {
|
||||||
|
let pane = self.panes.get_mut(id).unwrap();
|
||||||
|
pane.reduce_width_left(count);
|
||||||
|
if let PaneId::Terminal(pid) = pane.pid() {
|
||||||
|
self.os_api
|
||||||
|
.set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_next_increasable_horizontal_pane(
|
||||||
|
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
|
||||||
|
right_of: &Box<dyn Pane>,
|
||||||
|
increase_by: usize,
|
||||||
|
) -> Option<PaneId> {
|
||||||
|
let next_pane_candidates = panes.values().filter(
|
||||||
|
|p| {
|
||||||
|
p.x() == right_of.x() + right_of.columns() + 1
|
||||||
|
&& p.horizontally_overlaps_with(right_of.as_ref())
|
||||||
|
}, // TODO: the name here is wrong, it should be vertically_overlaps_with
|
||||||
|
);
|
||||||
|
let resizable_candidates =
|
||||||
|
next_pane_candidates.filter(|p| p.can_increase_height_by(increase_by));
|
||||||
|
resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id {
|
||||||
|
Some(next_pane) => {
|
||||||
|
let next_pane = panes.get(&next_pane).unwrap();
|
||||||
|
if next_pane.y() < p.y() {
|
||||||
|
next_pane_id
|
||||||
|
} else {
|
||||||
|
Some(p.pid())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Some(p.pid()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_next_increasable_vertical_pane(
|
||||||
|
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
|
||||||
|
below: &Box<dyn Pane>,
|
||||||
|
increase_by: usize,
|
||||||
|
) -> Option<PaneId> {
|
||||||
|
let next_pane_candidates = panes.values().filter(
|
||||||
|
|p| p.y() == below.y() + below.rows() + 1 && p.vertically_overlaps_with(below.as_ref()), // TODO: the name here is wrong, it should be horizontally_overlaps_with
|
||||||
|
);
|
||||||
|
let resizable_candidates =
|
||||||
|
next_pane_candidates.filter(|p| p.can_increase_width_by(increase_by));
|
||||||
|
resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id {
|
||||||
|
Some(next_pane) => {
|
||||||
|
let next_pane = panes.get(&next_pane).unwrap();
|
||||||
|
if next_pane.x() < p.x() {
|
||||||
|
next_pane_id
|
||||||
|
} else {
|
||||||
|
Some(p.pid())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Some(p.pid()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_next_reducible_vertical_pane(
|
||||||
|
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
|
||||||
|
below: &Box<dyn Pane>,
|
||||||
|
reduce_by: usize,
|
||||||
|
) -> Option<PaneId> {
|
||||||
|
let next_pane_candidates = panes.values().filter(
|
||||||
|
|p| p.y() == below.y() + below.rows() + 1 && p.vertically_overlaps_with(below.as_ref()), // TODO: the name here is wrong, it should be horizontally_overlaps_with
|
||||||
|
);
|
||||||
|
let resizable_candidates = next_pane_candidates.filter(|p| p.can_reduce_width_by(reduce_by));
|
||||||
|
resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id {
|
||||||
|
Some(next_pane) => {
|
||||||
|
let next_pane = panes.get(&next_pane).unwrap();
|
||||||
|
if next_pane.x() < p.x() {
|
||||||
|
next_pane_id
|
||||||
|
} else {
|
||||||
|
Some(p.pid())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Some(p.pid()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_next_reducible_horizontal_pane(
|
||||||
|
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
|
||||||
|
right_of: &Box<dyn Pane>,
|
||||||
|
reduce_by: usize,
|
||||||
|
) -> Option<PaneId> {
|
||||||
|
let next_pane_candidates = panes.values().filter(
|
||||||
|
|p| {
|
||||||
|
p.x() == right_of.x() + right_of.columns() + 1
|
||||||
|
&& p.horizontally_overlaps_with(right_of.as_ref())
|
||||||
|
}, // TODO: the name here is wrong, it should be vertically_overlaps_with
|
||||||
|
);
|
||||||
|
let resizable_candidates = next_pane_candidates.filter(|p| p.can_reduce_height_by(reduce_by));
|
||||||
|
resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id {
|
||||||
|
Some(next_pane) => {
|
||||||
|
let next_pane = panes.get(&next_pane).unwrap();
|
||||||
|
if next_pane.y() < p.y() {
|
||||||
|
next_pane_id
|
||||||
|
} else {
|
||||||
|
Some(p.pid())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Some(p.pid()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_increasable_horizontal_chain(
|
||||||
|
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
|
||||||
|
increase_by: usize,
|
||||||
|
screen_width: usize,
|
||||||
|
screen_height: usize, // TODO: this is the previous size (make this clearer)
|
||||||
|
) -> Option<Vec<PaneId>> {
|
||||||
|
let mut horizontal_coordinate = 0;
|
||||||
|
loop {
|
||||||
|
if horizontal_coordinate == screen_height {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match panes
|
||||||
|
.values()
|
||||||
|
.find(|p| p.x() == 0 && p.y() == horizontal_coordinate)
|
||||||
|
{
|
||||||
|
Some(leftmost_pane) => {
|
||||||
|
if !leftmost_pane.can_increase_height_by(increase_by) {
|
||||||
|
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut panes_to_resize = vec![];
|
||||||
|
let mut current_pane = leftmost_pane;
|
||||||
|
loop {
|
||||||
|
panes_to_resize.push(current_pane.pid());
|
||||||
|
if current_pane.x() + current_pane.columns() == screen_width {
|
||||||
|
return Some(panes_to_resize);
|
||||||
|
}
|
||||||
|
match find_next_increasable_horizontal_pane(panes, ¤t_pane, increase_by) {
|
||||||
|
Some(next_pane_id) => {
|
||||||
|
current_pane = panes.get(&next_pane_id).unwrap();
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_increasable_vertical_chain(
|
||||||
|
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
|
||||||
|
increase_by: usize,
|
||||||
|
screen_width: usize,
|
||||||
|
screen_height: usize, // TODO: this is the previous size (make this clearer)
|
||||||
|
) -> Option<Vec<PaneId>> {
|
||||||
|
let mut vertical_coordinate = 0;
|
||||||
|
loop {
|
||||||
|
if vertical_coordinate == screen_width {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match panes
|
||||||
|
.values()
|
||||||
|
.find(|p| p.y() == 0 && p.x() == vertical_coordinate)
|
||||||
|
{
|
||||||
|
Some(topmost_pane) => {
|
||||||
|
if !topmost_pane.can_increase_width_by(increase_by) {
|
||||||
|
vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut panes_to_resize = vec![];
|
||||||
|
let mut current_pane = topmost_pane;
|
||||||
|
loop {
|
||||||
|
panes_to_resize.push(current_pane.pid());
|
||||||
|
if current_pane.y() + current_pane.rows() == screen_height {
|
||||||
|
return Some(panes_to_resize);
|
||||||
|
}
|
||||||
|
match find_next_increasable_vertical_pane(panes, ¤t_pane, increase_by) {
|
||||||
|
Some(next_pane_id) => {
|
||||||
|
current_pane = panes.get(&next_pane_id).unwrap();
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_reducible_horizontal_chain(
|
||||||
|
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
|
||||||
|
reduce_by: usize,
|
||||||
|
screen_width: usize,
|
||||||
|
screen_height: usize, // TODO: this is the previous size (make this clearer)
|
||||||
|
) -> Option<Vec<PaneId>> {
|
||||||
|
let mut horizontal_coordinate = 0;
|
||||||
|
loop {
|
||||||
|
if horizontal_coordinate == screen_height {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match panes
|
||||||
|
.values()
|
||||||
|
.find(|p| p.x() == 0 && p.y() == horizontal_coordinate)
|
||||||
|
{
|
||||||
|
Some(leftmost_pane) => {
|
||||||
|
if !leftmost_pane.can_reduce_height_by(reduce_by) {
|
||||||
|
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut panes_to_resize = vec![];
|
||||||
|
let mut current_pane = leftmost_pane;
|
||||||
|
loop {
|
||||||
|
panes_to_resize.push(current_pane.pid());
|
||||||
|
if current_pane.x() + current_pane.columns() == screen_width {
|
||||||
|
return Some(panes_to_resize);
|
||||||
|
}
|
||||||
|
match find_next_reducible_horizontal_pane(panes, ¤t_pane, reduce_by) {
|
||||||
|
Some(next_pane_id) => {
|
||||||
|
current_pane = panes.get(&next_pane_id).unwrap();
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_reducible_vertical_chain(
|
||||||
|
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
|
||||||
|
increase_by: usize,
|
||||||
|
screen_width: usize,
|
||||||
|
screen_height: usize, // TODO: this is the previous size (make this clearer)
|
||||||
|
) -> Option<Vec<PaneId>> {
|
||||||
|
let mut vertical_coordinate = 0;
|
||||||
|
loop {
|
||||||
|
if vertical_coordinate == screen_width {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match panes
|
||||||
|
.values()
|
||||||
|
.find(|p| p.y() == 0 && p.x() == vertical_coordinate)
|
||||||
|
{
|
||||||
|
Some(topmost_pane) => {
|
||||||
|
if !topmost_pane.can_reduce_width_by(increase_by) {
|
||||||
|
vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut panes_to_resize = vec![];
|
||||||
|
let mut current_pane = topmost_pane;
|
||||||
|
loop {
|
||||||
|
panes_to_resize.push(current_pane.pid());
|
||||||
|
if current_pane.y() + current_pane.rows() == screen_height {
|
||||||
|
return Some(panes_to_resize);
|
||||||
|
}
|
||||||
|
match find_next_reducible_vertical_pane(panes, ¤t_pane, increase_by) {
|
||||||
|
Some(next_pane_id) => {
|
||||||
|
current_pane = panes.get(&next_pane_id).unwrap();
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#![allow(clippy::clippy::if_same_then_else)]
|
#![allow(clippy::clippy::if_same_then_else)]
|
||||||
|
|
||||||
use crate::{common::SenderWithContext, pty_bus::VteEvent, tab::Pane, wasm_vm::PluginInstruction};
|
use crate::{common::SenderWithContext, pty_bus::VteBytes, tab::Pane, wasm_vm::PluginInstruction};
|
||||||
|
|
||||||
use std::{sync::mpsc::channel, unimplemented};
|
use std::{sync::mpsc::channel, unimplemented};
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ impl Pane for PluginPane {
|
||||||
self.position_and_size_override = Some(position_and_size_override);
|
self.position_and_size_override = Some(position_and_size_override);
|
||||||
self.should_render = true;
|
self.should_render = true;
|
||||||
}
|
}
|
||||||
fn handle_event(&mut self, _event: VteEvent) {
|
fn handle_pty_bytes(&mut self, _event: VteBytes) {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
|
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
|
||||||
|
|
@ -173,6 +173,18 @@ impl Pane for PluginPane {
|
||||||
self.position_and_size.columns += count;
|
self.position_and_size.columns += count;
|
||||||
self.should_render = true;
|
self.should_render = true;
|
||||||
}
|
}
|
||||||
|
fn push_down(&mut self, count: usize) {
|
||||||
|
self.position_and_size.y += count;
|
||||||
|
}
|
||||||
|
fn push_right(&mut self, count: usize) {
|
||||||
|
self.position_and_size.x += count;
|
||||||
|
}
|
||||||
|
fn pull_left(&mut self, count: usize) {
|
||||||
|
self.position_and_size.x -= count;
|
||||||
|
}
|
||||||
|
fn pull_up(&mut self, count: usize) {
|
||||||
|
self.position_and_size.y -= count;
|
||||||
|
}
|
||||||
fn scroll_up(&mut self, _count: usize) {
|
fn scroll_up(&mut self, _count: usize) {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,14 @@ pub enum NamedColor {
|
||||||
Magenta,
|
Magenta,
|
||||||
Cyan,
|
Cyan,
|
||||||
White,
|
White,
|
||||||
|
BrightBlack,
|
||||||
|
BrightRed,
|
||||||
|
BrightGreen,
|
||||||
|
BrightYellow,
|
||||||
|
BrightBlue,
|
||||||
|
BrightMagenta,
|
||||||
|
BrightCyan,
|
||||||
|
BrightWhite,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NamedColor {
|
impl NamedColor {
|
||||||
|
|
@ -50,6 +58,14 @@ impl NamedColor {
|
||||||
NamedColor::Magenta => format!("{}", 35),
|
NamedColor::Magenta => format!("{}", 35),
|
||||||
NamedColor::Cyan => format!("{}", 36),
|
NamedColor::Cyan => format!("{}", 36),
|
||||||
NamedColor::White => format!("{}", 37),
|
NamedColor::White => format!("{}", 37),
|
||||||
|
NamedColor::BrightBlack => format!("{}", 90),
|
||||||
|
NamedColor::BrightRed => format!("{}", 91),
|
||||||
|
NamedColor::BrightGreen => format!("{}", 92),
|
||||||
|
NamedColor::BrightYellow => format!("{}", 93),
|
||||||
|
NamedColor::BrightBlue => format!("{}", 94),
|
||||||
|
NamedColor::BrightMagenta => format!("{}", 95),
|
||||||
|
NamedColor::BrightCyan => format!("{}", 96),
|
||||||
|
NamedColor::BrightWhite => format!("{}", 97),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn to_background_ansi_code(&self) -> String {
|
fn to_background_ansi_code(&self) -> String {
|
||||||
|
|
@ -62,6 +78,14 @@ impl NamedColor {
|
||||||
NamedColor::Magenta => format!("{}", 45),
|
NamedColor::Magenta => format!("{}", 45),
|
||||||
NamedColor::Cyan => format!("{}", 46),
|
NamedColor::Cyan => format!("{}", 46),
|
||||||
NamedColor::White => format!("{}", 47),
|
NamedColor::White => format!("{}", 47),
|
||||||
|
NamedColor::BrightBlack => format!("{}", 100),
|
||||||
|
NamedColor::BrightRed => format!("{}", 101),
|
||||||
|
NamedColor::BrightGreen => format!("{}", 102),
|
||||||
|
NamedColor::BrightYellow => format!("{}", 103),
|
||||||
|
NamedColor::BrightBlue => format!("{}", 104),
|
||||||
|
NamedColor::BrightMagenta => format!("{}", 105),
|
||||||
|
NamedColor::BrightCyan => format!("{}", 106),
|
||||||
|
NamedColor::BrightWhite => format!("{}", 107),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -383,6 +407,46 @@ impl CharacterStyles {
|
||||||
params_used += 1; // even if it's a bug, let's not create an endless loop, eh?
|
params_used += 1; // even if it's a bug, let's not create an endless loop, eh?
|
||||||
}
|
}
|
||||||
[49, ..] => *self = self.background(Some(AnsiCode::Reset)),
|
[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
|
// if this happens, it's a bug
|
||||||
let _ = debug_log_to_file(format!("unhandled csi m code {:?}", ansi_params));
|
let _ = debug_log_to_file(format!("unhandled csi m code {:?}", ansi_params));
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,14 @@
|
||||||
use crate::tab::Pane;
|
use crate::tab::Pane;
|
||||||
use ::nix::pty::Winsize;
|
use ::nix::pty::Winsize;
|
||||||
use ::std::os::unix::io::RawFd;
|
use ::std::os::unix::io::RawFd;
|
||||||
use ::vte::Perform;
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::panes::grid::Grid;
|
use crate::panes::grid::Grid;
|
||||||
use crate::panes::terminal_character::{
|
use crate::panes::terminal_character::{
|
||||||
CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
|
CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
|
||||||
};
|
};
|
||||||
|
use crate::pty_bus::VteBytes;
|
||||||
use crate::utils::logging::debug_log_to_file;
|
use crate::utils::logging::debug_log_to_file;
|
||||||
use crate::VteEvent;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)]
|
#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)]
|
||||||
pub enum PaneId {
|
pub enum PaneId {
|
||||||
|
|
@ -86,34 +85,10 @@ impl Pane for TerminalPane {
|
||||||
self.position_and_size_override = Some(position_and_size_override);
|
self.position_and_size_override = Some(position_and_size_override);
|
||||||
self.reflow_lines();
|
self.reflow_lines();
|
||||||
}
|
}
|
||||||
fn handle_event(&mut self, event: VteEvent) {
|
fn handle_pty_bytes(&mut self, bytes: VteBytes) {
|
||||||
match event {
|
let mut vte_parser = vte::Parser::new();
|
||||||
VteEvent::Print(c) => {
|
for byte in bytes.iter() {
|
||||||
self.print(c);
|
vte_parser.advance(self, *byte);
|
||||||
self.mark_for_rerender();
|
|
||||||
}
|
|
||||||
VteEvent::Execute(byte) => {
|
|
||||||
self.execute(byte);
|
|
||||||
}
|
|
||||||
VteEvent::Hook(params, intermediates, ignore, c) => {
|
|
||||||
self.hook(¶ms, &intermediates, ignore, c);
|
|
||||||
}
|
|
||||||
VteEvent::Put(byte) => {
|
|
||||||
self.put(byte);
|
|
||||||
}
|
|
||||||
VteEvent::Unhook => {
|
|
||||||
self.unhook();
|
|
||||||
}
|
|
||||||
VteEvent::OscDispatch(params, bell_terminated) => {
|
|
||||||
let params: Vec<&[u8]> = params.iter().map(|p| &p[..]).collect();
|
|
||||||
self.osc_dispatch(¶ms[..], bell_terminated);
|
|
||||||
}
|
|
||||||
VteEvent::CsiDispatch(params, intermediates, ignore, c) => {
|
|
||||||
self.csi_dispatch(¶ms, &intermediates, ignore, c);
|
|
||||||
}
|
|
||||||
VteEvent::EscDispatch(intermediates, ignore, byte) => {
|
|
||||||
self.esc_dispatch(&intermediates, ignore, byte);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
|
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
|
||||||
|
|
@ -282,6 +257,18 @@ impl Pane for TerminalPane {
|
||||||
self.position_and_size.columns += count;
|
self.position_and_size.columns += count;
|
||||||
self.reflow_lines();
|
self.reflow_lines();
|
||||||
}
|
}
|
||||||
|
fn push_down(&mut self, count: usize) {
|
||||||
|
self.position_and_size.y += count;
|
||||||
|
}
|
||||||
|
fn push_right(&mut self, count: usize) {
|
||||||
|
self.position_and_size.x += count;
|
||||||
|
}
|
||||||
|
fn pull_left(&mut self, count: usize) {
|
||||||
|
self.position_and_size.x -= count;
|
||||||
|
}
|
||||||
|
fn pull_up(&mut self, count: usize) {
|
||||||
|
self.position_and_size.y -= count;
|
||||||
|
}
|
||||||
fn scroll_up(&mut self, count: usize) {
|
fn scroll_up(&mut self, count: usize) {
|
||||||
self.grid.move_viewport_up(count);
|
self.grid.move_viewport_up(count);
|
||||||
self.mark_for_rerender();
|
self.mark_for_rerender();
|
||||||
|
|
@ -367,9 +354,15 @@ impl TerminalPane {
|
||||||
self.grid.rotate_scroll_region_down(count);
|
self.grid.rotate_scroll_region_down(count);
|
||||||
self.mark_for_rerender();
|
self.mark_for_rerender();
|
||||||
}
|
}
|
||||||
|
fn reset_terminal_state(&mut self) {
|
||||||
|
let rows = self.get_rows();
|
||||||
|
let columns = self.get_columns();
|
||||||
|
self.grid = Grid::new(rows, columns);
|
||||||
|
self.alternative_grid = None;
|
||||||
|
self.cursor_key_mode = false;
|
||||||
|
}
|
||||||
fn add_newline(&mut self) {
|
fn add_newline(&mut self) {
|
||||||
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
|
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||||
pad_character.styles = self.pending_styles;
|
|
||||||
self.grid.add_canonical_line(pad_character);
|
self.grid.add_canonical_line(pad_character);
|
||||||
self.mark_for_rerender();
|
self.mark_for_rerender();
|
||||||
}
|
}
|
||||||
|
|
@ -485,8 +478,7 @@ impl vte::Perform for TerminalPane {
|
||||||
} else {
|
} else {
|
||||||
(params[0] as usize - 1, params[1] as usize - 1)
|
(params[0] as usize - 1, params[1] as usize - 1)
|
||||||
};
|
};
|
||||||
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
|
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||||
pad_character.styles = self.pending_styles;
|
|
||||||
self.grid.move_cursor_to(col, row, pad_character);
|
self.grid.move_cursor_to(col, row, pad_character);
|
||||||
} else if c == 'A' {
|
} else if c == 'A' {
|
||||||
// move cursor up until edge of screen
|
// move cursor up until edge of screen
|
||||||
|
|
@ -495,8 +487,7 @@ impl vte::Perform for TerminalPane {
|
||||||
} else if c == 'B' {
|
} else if c == 'B' {
|
||||||
// move cursor down until edge of screen
|
// move cursor down until edge of screen
|
||||||
let move_down_count = if params[0] == 0 { 1 } else { params[0] };
|
let move_down_count = if params[0] == 0 { 1 } else { params[0] };
|
||||||
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
|
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||||
pad_character.styles = self.pending_styles;
|
|
||||||
self.grid
|
self.grid
|
||||||
.move_cursor_down(move_down_count as usize, pad_character);
|
.move_cursor_down(move_down_count as usize, pad_character);
|
||||||
} else if c == 'D' {
|
} else if c == 'D' {
|
||||||
|
|
@ -588,8 +579,7 @@ impl vte::Perform for TerminalPane {
|
||||||
} else {
|
} else {
|
||||||
params[0] as usize
|
params[0] as usize
|
||||||
};
|
};
|
||||||
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
|
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||||
pad_character.styles = self.pending_styles;
|
|
||||||
self.grid
|
self.grid
|
||||||
.delete_lines_in_scroll_region(line_count_to_delete, pad_character);
|
.delete_lines_in_scroll_region(line_count_to_delete, pad_character);
|
||||||
} else if c == 'L' {
|
} else if c == 'L' {
|
||||||
|
|
@ -599,8 +589,7 @@ impl vte::Perform for TerminalPane {
|
||||||
} else {
|
} else {
|
||||||
params[0] as usize
|
params[0] as usize
|
||||||
};
|
};
|
||||||
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
|
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||||
pad_character.styles = self.pending_styles;
|
|
||||||
self.grid
|
self.grid
|
||||||
.add_empty_lines_in_scroll_region(line_count_to_add, pad_character);
|
.add_empty_lines_in_scroll_region(line_count_to_add, pad_character);
|
||||||
} else if c == 'q' {
|
} else if c == 'q' {
|
||||||
|
|
@ -620,8 +609,7 @@ impl vte::Perform for TerminalPane {
|
||||||
// minus 1 because this is 1 indexed
|
// minus 1 because this is 1 indexed
|
||||||
params[0] as usize - 1
|
params[0] as usize - 1
|
||||||
};
|
};
|
||||||
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
|
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||||
pad_character.styles = self.pending_styles;
|
|
||||||
self.grid.move_cursor_to_line(line, pad_character);
|
self.grid.move_cursor_to_line(line, pad_character);
|
||||||
} else if c == 'P' {
|
} else if c == 'P' {
|
||||||
// erase characters
|
// erase characters
|
||||||
|
|
@ -660,8 +648,7 @@ impl vte::Perform for TerminalPane {
|
||||||
} else {
|
} else {
|
||||||
params[0] as usize
|
params[0] as usize
|
||||||
};
|
};
|
||||||
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
|
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||||
pad_character.styles = self.pending_styles;
|
|
||||||
self.grid
|
self.grid
|
||||||
.delete_lines_in_scroll_region(count, pad_character);
|
.delete_lines_in_scroll_region(count, pad_character);
|
||||||
// TODO: since delete_lines_in_scroll_region also adds lines, is the below redundant?
|
// TODO: since delete_lines_in_scroll_region also adds lines, is the below redundant?
|
||||||
|
|
@ -673,8 +660,14 @@ impl vte::Perform for TerminalPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, byte: u8) {
|
fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, byte: u8) {
|
||||||
if let (b'M', None) = (byte, intermediates.get(0)) {
|
match (byte, intermediates.get(0)) {
|
||||||
|
(b'M', None) => {
|
||||||
self.grid.move_cursor_up_with_scrolling(1);
|
self.grid.move_cursor_up_with_scrolling(1);
|
||||||
}
|
}
|
||||||
|
(b'c', None) => {
|
||||||
|
self.reset_terminal_state();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,16 @@
|
||||||
//! as well as how they should be resized
|
//! as well as how they should be resized
|
||||||
|
|
||||||
use crate::boundaries::colors;
|
use crate::boundaries::colors;
|
||||||
|
use crate::client::pane_resizer::PaneResizer;
|
||||||
use crate::common::{input::handler::parse_keys, AppInstruction, SenderWithContext};
|
use crate::common::{input::handler::parse_keys, AppInstruction, SenderWithContext};
|
||||||
use crate::layout::Layout;
|
use crate::layout::Layout;
|
||||||
|
use crate::os_input_output::OsApi;
|
||||||
use crate::panes::{PaneId, PositionAndSize, TerminalPane};
|
use crate::panes::{PaneId, PositionAndSize, TerminalPane};
|
||||||
use crate::pty_bus::{PtyInstruction, VteEvent};
|
use crate::pty_bus::{PtyInstruction, VteBytes};
|
||||||
|
use crate::utils::shared::adjust_to_size;
|
||||||
use crate::wasm_vm::PluginInstruction;
|
use crate::wasm_vm::PluginInstruction;
|
||||||
use crate::{boundaries::Boundaries, panes::PluginPane};
|
use crate::{boundaries::Boundaries, panes::PluginPane};
|
||||||
use crate::{os_input_output::OsApi, utils::shared::pad_to_size};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Reverse,
|
cmp::Reverse,
|
||||||
|
|
@ -66,6 +69,16 @@ pub struct Tab {
|
||||||
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||||
pub send_app_instructions: SenderWithContext<AppInstruction>,
|
pub send_app_instructions: SenderWithContext<AppInstruction>,
|
||||||
expansion_boundary: Option<PositionAndSize>,
|
expansion_boundary: Option<PositionAndSize>,
|
||||||
|
should_clear_display_before_rendering: bool,
|
||||||
|
pub mode_info: ModeInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||||
|
pub struct TabData {
|
||||||
|
/* subset of fields to publish to plugins */
|
||||||
|
pub position: usize,
|
||||||
|
pub name: String,
|
||||||
|
pub active: bool,
|
||||||
pub mode_info: ModeInfo,
|
pub mode_info: ModeInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,7 +91,7 @@ pub trait Pane {
|
||||||
fn reset_size_and_position_override(&mut self);
|
fn reset_size_and_position_override(&mut self);
|
||||||
fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize);
|
fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize);
|
||||||
fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize);
|
fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize);
|
||||||
fn handle_event(&mut self, event: VteEvent);
|
fn handle_pty_bytes(&mut self, bytes: VteBytes);
|
||||||
fn cursor_coordinates(&self) -> Option<(usize, usize)>;
|
fn cursor_coordinates(&self) -> Option<(usize, usize)>;
|
||||||
fn adjust_input_to_terminal(&self, input_bytes: Vec<u8>) -> Vec<u8>;
|
fn adjust_input_to_terminal(&self, input_bytes: Vec<u8>) -> Vec<u8>;
|
||||||
|
|
||||||
|
|
@ -99,6 +112,10 @@ pub trait Pane {
|
||||||
fn reduce_width_right(&mut self, count: usize);
|
fn reduce_width_right(&mut self, count: usize);
|
||||||
fn reduce_width_left(&mut self, count: usize);
|
fn reduce_width_left(&mut self, count: usize);
|
||||||
fn increase_width_left(&mut self, count: usize);
|
fn increase_width_left(&mut self, count: usize);
|
||||||
|
fn push_down(&mut self, count: usize);
|
||||||
|
fn push_right(&mut self, count: usize);
|
||||||
|
fn pull_left(&mut self, count: usize);
|
||||||
|
fn pull_up(&mut self, count: usize);
|
||||||
fn scroll_up(&mut self, count: usize);
|
fn scroll_up(&mut self, count: usize);
|
||||||
fn scroll_down(&mut self, count: usize);
|
fn scroll_down(&mut self, count: usize);
|
||||||
fn clear_scroll(&mut self);
|
fn clear_scroll(&mut self);
|
||||||
|
|
@ -153,6 +170,22 @@ pub trait Pane {
|
||||||
rows: self.rows(),
|
rows: self.rows(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn can_increase_height_by(&self, increase_by: usize) -> bool {
|
||||||
|
self.max_height()
|
||||||
|
.map(|max_height| self.rows() + increase_by <= max_height)
|
||||||
|
.unwrap_or(true)
|
||||||
|
}
|
||||||
|
fn can_increase_width_by(&self, increase_by: usize) -> bool {
|
||||||
|
self.max_width()
|
||||||
|
.map(|max_width| self.columns() + increase_by <= max_width)
|
||||||
|
.unwrap_or(true)
|
||||||
|
}
|
||||||
|
fn can_reduce_height_by(&self, reduce_by: usize) -> bool {
|
||||||
|
self.rows() > reduce_by && self.rows() - reduce_by >= self.min_height()
|
||||||
|
}
|
||||||
|
fn can_reduce_width_by(&self, reduce_by: usize) -> bool {
|
||||||
|
self.columns() > reduce_by && self.columns() - reduce_by >= self.min_width()
|
||||||
|
}
|
||||||
fn min_width(&self) -> usize {
|
fn min_width(&self) -> usize {
|
||||||
MIN_TERMINAL_WIDTH
|
MIN_TERMINAL_WIDTH
|
||||||
}
|
}
|
||||||
|
|
@ -213,6 +246,7 @@ impl Tab {
|
||||||
send_pty_instructions,
|
send_pty_instructions,
|
||||||
send_plugin_instructions,
|
send_plugin_instructions,
|
||||||
expansion_boundary: None,
|
expansion_boundary: None,
|
||||||
|
should_clear_display_before_rendering: false,
|
||||||
mode_info,
|
mode_info,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -527,14 +561,17 @@ impl Tab {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn handle_pty_event(&mut self, pid: RawFd, event: VteEvent) {
|
pub fn has_terminal_pid(&self, pid: RawFd) -> bool {
|
||||||
|
self.panes.contains_key(&PaneId::Terminal(pid))
|
||||||
|
}
|
||||||
|
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
|
// 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_bus but has not
|
||||||
// yet been created in Screen. These events are currently not buffered, so
|
// yet been created in Screen. These events are currently not buffered, so
|
||||||
// if you're debugging seemingly randomly missing stdout data, this is
|
// if you're debugging seemingly randomly missing stdout data, this is
|
||||||
// the reason
|
// the reason
|
||||||
if let Some(terminal_output) = self.panes.get_mut(&PaneId::Terminal(pid)) {
|
if let Some(terminal_output) = self.panes.get_mut(&PaneId::Terminal(pid)) {
|
||||||
terminal_output.handle_event(event);
|
terminal_output.handle_pty_bytes(bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn write_to_active_terminal(&mut self, input_bytes: Vec<u8>) {
|
pub fn write_to_active_terminal(&mut self, input_bytes: Vec<u8>) {
|
||||||
|
|
@ -597,8 +634,17 @@ impl Tab {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
self.panes_to_hide = pane_ids_to_hide.collect();
|
self.panes_to_hide = pane_ids_to_hide.collect();
|
||||||
|
if self.panes_to_hide.is_empty() {
|
||||||
|
// nothing to do, pane is already as fullscreen as it can be, let's bail
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
let active_terminal = self.panes.get_mut(&active_pane_id).unwrap();
|
let active_terminal = self.panes.get_mut(&active_pane_id).unwrap();
|
||||||
active_terminal.override_size_and_position(expand_to.x, expand_to.y, &expand_to);
|
active_terminal.override_size_and_position(
|
||||||
|
expand_to.x,
|
||||||
|
expand_to.y,
|
||||||
|
&expand_to,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let active_terminal = self.panes.get(&active_pane_id).unwrap();
|
let active_terminal = self.panes.get(&active_pane_id).unwrap();
|
||||||
if let PaneId::Terminal(active_pid) = active_pane_id {
|
if let PaneId::Terminal(active_pid) = active_pane_id {
|
||||||
|
|
@ -630,28 +676,33 @@ impl Tab {
|
||||||
stdout
|
stdout
|
||||||
.write_all(&hide_cursor.as_bytes())
|
.write_all(&hide_cursor.as_bytes())
|
||||||
.expect("cannot write to stdout");
|
.expect("cannot write to stdout");
|
||||||
for (kind, terminal) in self.panes.iter_mut() {
|
if self.should_clear_display_before_rendering {
|
||||||
if !self.panes_to_hide.contains(&terminal.pid()) {
|
let clear_display = "\u{1b}[2J";
|
||||||
match self.active_terminal.unwrap() == terminal.pid() {
|
stdout
|
||||||
true => boundaries.add_rect(
|
.write_all(&clear_display.as_bytes())
|
||||||
terminal.as_ref(),
|
.expect("cannot write to stdout");
|
||||||
self.mode_info.mode,
|
self.should_clear_display_before_rendering = false;
|
||||||
Some(colors::GREEN),
|
|
||||||
),
|
|
||||||
false => boundaries.add_rect(terminal.as_ref(), self.mode_info.mode, None),
|
|
||||||
}
|
}
|
||||||
if let Some(vte_output) = terminal.render() {
|
for (kind, pane) in self.panes.iter_mut() {
|
||||||
|
if !self.panes_to_hide.contains(&pane.pid()) {
|
||||||
|
match self.active_terminal.unwrap() == pane.pid() {
|
||||||
|
true => {
|
||||||
|
boundaries.add_rect(pane.as_ref(), self.mode_info.mode, Some(colors::GREEN))
|
||||||
|
}
|
||||||
|
false => boundaries.add_rect(pane.as_ref(), self.mode_info.mode, None),
|
||||||
|
}
|
||||||
|
if let Some(vte_output) = pane.render() {
|
||||||
let vte_output = if let PaneId::Terminal(_) = kind {
|
let vte_output = if let PaneId::Terminal(_) = kind {
|
||||||
vte_output
|
vte_output
|
||||||
} else {
|
} else {
|
||||||
pad_to_size(&vte_output, terminal.rows(), terminal.columns())
|
adjust_to_size(&vte_output, pane.rows(), pane.columns())
|
||||||
};
|
};
|
||||||
// FIXME: Use Termion for cursor and style clearing?
|
// FIXME: Use Termion for cursor and style clearing?
|
||||||
write!(
|
write!(
|
||||||
stdout,
|
stdout,
|
||||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||||
terminal.y() + 1,
|
pane.y() + 1,
|
||||||
terminal.x() + 1,
|
pane.x() + 1,
|
||||||
vte_output
|
vte_output
|
||||||
)
|
)
|
||||||
.expect("cannot write to stdout");
|
.expect("cannot write to stdout");
|
||||||
|
|
@ -1664,17 +1715,30 @@ impl Tab {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn resize_right(&mut self) {
|
pub fn resize_whole_tab(&mut self, new_screen_size: PositionAndSize) {
|
||||||
// TODO: find out by how much we actually reduced and only reduce by that much
|
if self.fullscreen_is_active {
|
||||||
let count = 10;
|
// this is not ideal but until we get rid of expansion_boundary, it's a necessity
|
||||||
if let Some(active_pane_id) = self.get_active_pane_id() {
|
self.toggle_active_pane_fullscreen();
|
||||||
if self.can_increase_pane_and_surroundings_right(&active_pane_id, count) {
|
|
||||||
self.increase_pane_and_surroundings_right(&active_pane_id, count);
|
|
||||||
} else if self.can_reduce_pane_and_surroundings_right(&active_pane_id, count) {
|
|
||||||
self.reduce_pane_and_surroundings_right(&active_pane_id, count);
|
|
||||||
}
|
}
|
||||||
|
match PaneResizer::new(&mut self.panes, &mut self.os_api)
|
||||||
|
.resize(self.full_screen_ws, new_screen_size)
|
||||||
|
{
|
||||||
|
Some((column_difference, row_difference)) => {
|
||||||
|
self.should_clear_display_before_rendering = true;
|
||||||
|
self.expansion_boundary.as_mut().map(|expansion_boundary| {
|
||||||
|
// TODO: this is not always accurate
|
||||||
|
expansion_boundary.columns =
|
||||||
|
(expansion_boundary.columns as isize + column_difference) as usize;
|
||||||
|
expansion_boundary.rows =
|
||||||
|
(expansion_boundary.rows as isize + row_difference) as usize;
|
||||||
|
});
|
||||||
|
self.full_screen_ws.columns =
|
||||||
|
(self.full_screen_ws.columns as isize + column_difference) as usize;
|
||||||
|
self.full_screen_ws.rows =
|
||||||
|
(self.full_screen_ws.rows as isize + row_difference) as usize;
|
||||||
}
|
}
|
||||||
self.render();
|
None => {}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
pub fn resize_left(&mut self) {
|
pub fn resize_left(&mut self) {
|
||||||
// TODO: find out by how much we actually reduced and only reduce by that much
|
// TODO: find out by how much we actually reduced and only reduce by that much
|
||||||
|
|
@ -1688,6 +1752,18 @@ impl Tab {
|
||||||
}
|
}
|
||||||
self.render();
|
self.render();
|
||||||
}
|
}
|
||||||
|
pub fn resize_right(&mut self) {
|
||||||
|
// TODO: find out by how much we actually reduced and only reduce by that much
|
||||||
|
let count = 10;
|
||||||
|
if let Some(active_pane_id) = self.get_active_pane_id() {
|
||||||
|
if self.can_increase_pane_and_surroundings_right(&active_pane_id, count) {
|
||||||
|
self.increase_pane_and_surroundings_right(&active_pane_id, count);
|
||||||
|
} else if self.can_reduce_pane_and_surroundings_right(&active_pane_id, count) {
|
||||||
|
self.reduce_pane_and_surroundings_right(&active_pane_id, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.render();
|
||||||
|
}
|
||||||
pub fn resize_down(&mut self) {
|
pub fn resize_down(&mut self) {
|
||||||
// TODO: find out by how much we actually reduced and only reduce by that much
|
// TODO: find out by how much we actually reduced and only reduce by that much
|
||||||
let count = 2;
|
let count = 2;
|
||||||
|
|
@ -1733,6 +1809,62 @@ impl Tab {
|
||||||
}
|
}
|
||||||
self.render();
|
self.render();
|
||||||
}
|
}
|
||||||
|
pub fn focus_next_pane(&mut self) {
|
||||||
|
if !self.has_selectable_panes() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.fullscreen_is_active {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let active_pane_id = self.get_active_pane_id().unwrap();
|
||||||
|
let mut panes: Vec<(&PaneId, &Box<dyn Pane>)> = self.get_selectable_panes().collect();
|
||||||
|
panes.sort_by(|(_a_id, a_pane), (_b_id, b_pane)| {
|
||||||
|
if a_pane.y() == b_pane.y() {
|
||||||
|
a_pane.x().cmp(&b_pane.x())
|
||||||
|
} else {
|
||||||
|
a_pane.y().cmp(&b_pane.y())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let first_pane = panes.get(0).unwrap();
|
||||||
|
let active_pane_position = panes
|
||||||
|
.iter()
|
||||||
|
.position(|(id, _)| *id == &active_pane_id) // TODO: better
|
||||||
|
.unwrap();
|
||||||
|
if let Some(next_pane) = panes.get(active_pane_position + 1) {
|
||||||
|
self.active_terminal = Some(*next_pane.0);
|
||||||
|
} else {
|
||||||
|
self.active_terminal = Some(*first_pane.0);
|
||||||
|
}
|
||||||
|
self.render();
|
||||||
|
}
|
||||||
|
pub fn focus_previous_pane(&mut self) {
|
||||||
|
if !self.has_selectable_panes() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.fullscreen_is_active {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let active_pane_id = self.get_active_pane_id().unwrap();
|
||||||
|
let mut panes: Vec<(&PaneId, &Box<dyn Pane>)> = self.get_selectable_panes().collect();
|
||||||
|
panes.sort_by(|(_a_id, a_pane), (_b_id, b_pane)| {
|
||||||
|
if a_pane.y() == b_pane.y() {
|
||||||
|
a_pane.x().cmp(&b_pane.x())
|
||||||
|
} else {
|
||||||
|
a_pane.y().cmp(&b_pane.y())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let last_pane = panes.last().unwrap();
|
||||||
|
let active_pane_position = panes
|
||||||
|
.iter()
|
||||||
|
.position(|(id, _)| *id == &active_pane_id) // TODO: better
|
||||||
|
.unwrap();
|
||||||
|
if active_pane_position == 0 {
|
||||||
|
self.active_terminal = Some(*last_pane.0);
|
||||||
|
} else {
|
||||||
|
self.active_terminal = Some(*panes.get(active_pane_position - 1).unwrap().0);
|
||||||
|
}
|
||||||
|
self.render();
|
||||||
|
}
|
||||||
pub fn move_focus_left(&mut self) {
|
pub fn move_focus_left(&mut self) {
|
||||||
if !self.has_selectable_panes() {
|
if !self.has_selectable_panes() {
|
||||||
return;
|
return;
|
||||||
|
|
@ -2000,47 +2132,79 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn close_pane_without_rerender(&mut self, id: PaneId) {
|
pub fn close_pane_without_rerender(&mut self, id: PaneId) {
|
||||||
if let Some(terminal_to_close) = self.panes.get(&id) {
|
if self.fullscreen_is_active {
|
||||||
let terminal_to_close_width = terminal_to_close.columns();
|
self.toggle_active_pane_fullscreen();
|
||||||
let terminal_to_close_height = terminal_to_close.rows();
|
}
|
||||||
if let Some(terminals) = self.panes_to_the_left_between_aligning_borders(id) {
|
if let Some(pane_to_close) = self.panes.get(&id) {
|
||||||
for terminal_id in terminals.iter() {
|
let pane_to_close_width = pane_to_close.columns();
|
||||||
self.increase_pane_width_right(&terminal_id, terminal_to_close_width + 1);
|
let pane_to_close_height = pane_to_close.rows();
|
||||||
|
if let Some(panes) = self.panes_to_the_left_between_aligning_borders(id) {
|
||||||
|
if panes.iter().all(|p| {
|
||||||
|
let pane = self.panes.get(p).unwrap();
|
||||||
|
pane.can_increase_width_by(pane_to_close_width + 1)
|
||||||
|
}) {
|
||||||
|
for pane_id in panes.iter() {
|
||||||
|
self.increase_pane_width_right(&pane_id, pane_to_close_width + 1);
|
||||||
// 1 for the border
|
// 1 for the border
|
||||||
}
|
}
|
||||||
if self.active_terminal == Some(id) {
|
|
||||||
self.active_terminal = self.next_active_pane(terminals);
|
|
||||||
}
|
|
||||||
} else if let Some(terminals) = self.panes_to_the_right_between_aligning_borders(id) {
|
|
||||||
for terminal_id in terminals.iter() {
|
|
||||||
self.increase_pane_width_left(&terminal_id, terminal_to_close_width + 1);
|
|
||||||
// 1 for the border
|
|
||||||
}
|
|
||||||
if self.active_terminal == Some(id) {
|
|
||||||
self.active_terminal = self.next_active_pane(terminals);
|
|
||||||
}
|
|
||||||
} else if let Some(terminals) = self.panes_above_between_aligning_borders(id) {
|
|
||||||
for terminal_id in terminals.iter() {
|
|
||||||
self.increase_pane_height_down(&terminal_id, terminal_to_close_height + 1);
|
|
||||||
// 1 for the border
|
|
||||||
}
|
|
||||||
if self.active_terminal == Some(id) {
|
|
||||||
self.active_terminal = self.next_active_pane(terminals);
|
|
||||||
}
|
|
||||||
} else if let Some(terminals) = self.panes_below_between_aligning_borders(id) {
|
|
||||||
for terminal_id in terminals.iter() {
|
|
||||||
self.increase_pane_height_up(&terminal_id, terminal_to_close_height + 1);
|
|
||||||
// 1 for the border
|
|
||||||
}
|
|
||||||
if self.active_terminal == Some(id) {
|
|
||||||
self.active_terminal = self.next_active_pane(terminals);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
}
|
|
||||||
self.panes.remove(&id);
|
self.panes.remove(&id);
|
||||||
if self.active_terminal.is_none() {
|
if self.active_terminal == Some(id) {
|
||||||
self.active_terminal = self.next_active_pane(self.get_pane_ids());
|
self.active_terminal = self.next_active_pane(panes);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(panes) = self.panes_to_the_right_between_aligning_borders(id) {
|
||||||
|
if panes.iter().all(|p| {
|
||||||
|
let pane = self.panes.get(p).unwrap();
|
||||||
|
pane.can_increase_width_by(pane_to_close_width + 1)
|
||||||
|
}) {
|
||||||
|
for pane_id in panes.iter() {
|
||||||
|
self.increase_pane_width_left(&pane_id, pane_to_close_width + 1);
|
||||||
|
// 1 for the border
|
||||||
|
}
|
||||||
|
self.panes.remove(&id);
|
||||||
|
if self.active_terminal == Some(id) {
|
||||||
|
self.active_terminal = self.next_active_pane(panes);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(panes) = self.panes_above_between_aligning_borders(id) {
|
||||||
|
if panes.iter().all(|p| {
|
||||||
|
let pane = self.panes.get(p).unwrap();
|
||||||
|
pane.can_increase_height_by(pane_to_close_height + 1)
|
||||||
|
}) {
|
||||||
|
for pane_id in panes.iter() {
|
||||||
|
self.increase_pane_height_down(&pane_id, pane_to_close_height + 1);
|
||||||
|
// 1 for the border
|
||||||
|
}
|
||||||
|
self.panes.remove(&id);
|
||||||
|
if self.active_terminal == Some(id) {
|
||||||
|
self.active_terminal = self.next_active_pane(panes);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(panes) = self.panes_below_between_aligning_borders(id) {
|
||||||
|
if panes.iter().all(|p| {
|
||||||
|
let pane = self.panes.get(p).unwrap();
|
||||||
|
pane.can_increase_height_by(pane_to_close_height + 1)
|
||||||
|
}) {
|
||||||
|
for pane_id in panes.iter() {
|
||||||
|
self.increase_pane_height_up(&pane_id, pane_to_close_height + 1);
|
||||||
|
// 1 for the border
|
||||||
|
}
|
||||||
|
self.panes.remove(&id);
|
||||||
|
if self.active_terminal == Some(id) {
|
||||||
|
self.active_terminal = self.next_active_pane(panes);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if we reached here, this is either the last pane or there's some sort of
|
||||||
|
// configuration error (eg. we're trying to close a pane surrounded by fixed panes)
|
||||||
|
self.panes.remove(&id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn close_focused_pane(&mut self) {
|
pub fn close_focused_pane(&mut self) {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ impl CommandIsExecuting {
|
||||||
let (lock, cvar) = &*self.closing_pane;
|
let (lock, cvar) = &*self.closing_pane;
|
||||||
let mut closing_pane = lock.lock().unwrap();
|
let mut closing_pane = lock.lock().unwrap();
|
||||||
*closing_pane = false;
|
*closing_pane = false;
|
||||||
cvar.notify_one();
|
cvar.notify_all();
|
||||||
}
|
}
|
||||||
pub fn opening_new_pane(&mut self) {
|
pub fn opening_new_pane(&mut self) {
|
||||||
let (lock, _cvar) = &*self.opening_new_pane;
|
let (lock, _cvar) = &*self.opening_new_pane;
|
||||||
|
|
@ -34,7 +34,7 @@ impl CommandIsExecuting {
|
||||||
let (lock, cvar) = &*self.opening_new_pane;
|
let (lock, cvar) = &*self.opening_new_pane;
|
||||||
let mut opening_new_pane = lock.lock().unwrap();
|
let mut opening_new_pane = lock.lock().unwrap();
|
||||||
*opening_new_pane = false;
|
*opening_new_pane = false;
|
||||||
cvar.notify_one();
|
cvar.notify_all();
|
||||||
}
|
}
|
||||||
pub fn wait_until_pane_is_closed(&self) {
|
pub fn wait_until_pane_is_closed(&self) {
|
||||||
let (lock, cvar) = &*self.closing_pane;
|
let (lock, cvar) = &*self.closing_pane;
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ pub fn handle_panic(
|
||||||
msg,
|
msg,
|
||||||
location.file(),
|
location.file(),
|
||||||
location.line(),
|
location.line(),
|
||||||
backtrace
|
backtrace,
|
||||||
),
|
),
|
||||||
(Some(location), None) => format!(
|
(Some(location), None) => format!(
|
||||||
"{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked: {}:{}\n\u{1b}[0;0m{:?}",
|
"{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked: {}:{}\n\u{1b}[0;0m{:?}",
|
||||||
|
|
@ -168,7 +168,7 @@ impl Display for ContextType {
|
||||||
/// Stack call representations corresponding to the different types of [`ScreenInstruction`]s.
|
/// Stack call representations corresponding to the different types of [`ScreenInstruction`]s.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum ScreenContext {
|
pub enum ScreenContext {
|
||||||
HandlePtyEvent,
|
HandlePtyBytes,
|
||||||
Render,
|
Render,
|
||||||
NewPane,
|
NewPane,
|
||||||
HorizontalSplit,
|
HorizontalSplit,
|
||||||
|
|
@ -178,7 +178,9 @@ pub enum ScreenContext {
|
||||||
ResizeRight,
|
ResizeRight,
|
||||||
ResizeDown,
|
ResizeDown,
|
||||||
ResizeUp,
|
ResizeUp,
|
||||||
MoveFocus,
|
SwitchFocus,
|
||||||
|
FocusNextPane,
|
||||||
|
FocusPreviousPane,
|
||||||
MoveFocusLeft,
|
MoveFocusLeft,
|
||||||
MoveFocusDown,
|
MoveFocusDown,
|
||||||
MoveFocusUp,
|
MoveFocusUp,
|
||||||
|
|
@ -200,6 +202,7 @@ pub enum ScreenContext {
|
||||||
CloseTab,
|
CloseTab,
|
||||||
GoToTab,
|
GoToTab,
|
||||||
UpdateTabName,
|
UpdateTabName,
|
||||||
|
TerminalResize,
|
||||||
ChangeMode,
|
ChangeMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,7 +210,7 @@ pub enum ScreenContext {
|
||||||
impl From<&ScreenInstruction> for ScreenContext {
|
impl From<&ScreenInstruction> for ScreenContext {
|
||||||
fn from(screen_instruction: &ScreenInstruction) -> Self {
|
fn from(screen_instruction: &ScreenInstruction) -> Self {
|
||||||
match *screen_instruction {
|
match *screen_instruction {
|
||||||
ScreenInstruction::Pty(..) => ScreenContext::HandlePtyEvent,
|
ScreenInstruction::PtyBytes(..) => ScreenContext::HandlePtyBytes,
|
||||||
ScreenInstruction::Render => ScreenContext::Render,
|
ScreenInstruction::Render => ScreenContext::Render,
|
||||||
ScreenInstruction::NewPane(_) => ScreenContext::NewPane,
|
ScreenInstruction::NewPane(_) => ScreenContext::NewPane,
|
||||||
ScreenInstruction::HorizontalSplit(_) => ScreenContext::HorizontalSplit,
|
ScreenInstruction::HorizontalSplit(_) => ScreenContext::HorizontalSplit,
|
||||||
|
|
@ -217,7 +220,9 @@ impl From<&ScreenInstruction> for ScreenContext {
|
||||||
ScreenInstruction::ResizeRight => ScreenContext::ResizeRight,
|
ScreenInstruction::ResizeRight => ScreenContext::ResizeRight,
|
||||||
ScreenInstruction::ResizeDown => ScreenContext::ResizeDown,
|
ScreenInstruction::ResizeDown => ScreenContext::ResizeDown,
|
||||||
ScreenInstruction::ResizeUp => ScreenContext::ResizeUp,
|
ScreenInstruction::ResizeUp => ScreenContext::ResizeUp,
|
||||||
ScreenInstruction::MoveFocus => ScreenContext::MoveFocus,
|
ScreenInstruction::SwitchFocus => ScreenContext::SwitchFocus,
|
||||||
|
ScreenInstruction::FocusNextPane => ScreenContext::FocusNextPane,
|
||||||
|
ScreenInstruction::FocusPreviousPane => ScreenContext::FocusPreviousPane,
|
||||||
ScreenInstruction::MoveFocusLeft => ScreenContext::MoveFocusLeft,
|
ScreenInstruction::MoveFocusLeft => ScreenContext::MoveFocusLeft,
|
||||||
ScreenInstruction::MoveFocusDown => ScreenContext::MoveFocusDown,
|
ScreenInstruction::MoveFocusDown => ScreenContext::MoveFocusDown,
|
||||||
ScreenInstruction::MoveFocusUp => ScreenContext::MoveFocusUp,
|
ScreenInstruction::MoveFocusUp => ScreenContext::MoveFocusUp,
|
||||||
|
|
@ -241,6 +246,7 @@ impl From<&ScreenInstruction> for ScreenContext {
|
||||||
ScreenInstruction::CloseTab => ScreenContext::CloseTab,
|
ScreenInstruction::CloseTab => ScreenContext::CloseTab,
|
||||||
ScreenInstruction::GoToTab(_) => ScreenContext::GoToTab,
|
ScreenInstruction::GoToTab(_) => ScreenContext::GoToTab,
|
||||||
ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName,
|
ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName,
|
||||||
|
ScreenInstruction::TerminalResize => ScreenContext::TerminalResize,
|
||||||
ScreenInstruction::ChangeMode(_) => ScreenContext::ChangeMode,
|
ScreenInstruction::ChangeMode(_) => ScreenContext::ChangeMode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
//! Definition of the actions that can be bound to keys.
|
//! Definition of the actions that can be bound to keys.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use zellij_tile::data::InputMode;
|
use zellij_tile::data::InputMode;
|
||||||
|
|
||||||
/// The four directions (left, right, up, down).
|
/// The four directions (left, right, up, down).
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
|
|
@ -12,7 +13,7 @@ pub enum Direction {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Actions that can be bound to keys.
|
/// Actions that can be bound to keys.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
/// Quit Zellij.
|
/// Quit Zellij.
|
||||||
Quit,
|
Quit,
|
||||||
|
|
@ -23,8 +24,10 @@ pub enum Action {
|
||||||
/// Resize focus pane in specified direction.
|
/// Resize focus pane in specified direction.
|
||||||
Resize(Direction),
|
Resize(Direction),
|
||||||
/// Switch focus to next pane in specified direction.
|
/// Switch focus to next pane in specified direction.
|
||||||
SwitchFocus(Direction),
|
FocusNextPane,
|
||||||
|
FocusPreviousPane,
|
||||||
/// Move the focus pane in specified direction.
|
/// Move the focus pane in specified direction.
|
||||||
|
SwitchFocus,
|
||||||
MoveFocus(Direction),
|
MoveFocus(Direction),
|
||||||
/// Scroll up in focus pane.
|
/// Scroll up in focus pane.
|
||||||
ScrollUp,
|
ScrollUp,
|
||||||
|
|
|
||||||
158
src/common/input/config.rs
Normal file
158
src/common/input/config.rs
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
//! Deserializes configuration options.
|
||||||
|
use std::error;
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, Read};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use super::keybinds::{Keybinds, KeybindsFromYaml};
|
||||||
|
use crate::cli::ConfigCli;
|
||||||
|
|
||||||
|
use directories_next::ProjectDirs;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
type ConfigResult = Result<Config, ConfigError>;
|
||||||
|
|
||||||
|
/// Intermediate deserialisation config struct
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct ConfigFromYaml {
|
||||||
|
pub keybinds: Option<KeybindsFromYaml>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Main configuration.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Config {
|
||||||
|
pub keybinds: Keybinds,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ConfigError {
|
||||||
|
// Deserialisation error
|
||||||
|
Serde(serde_yaml::Error),
|
||||||
|
// Io error
|
||||||
|
Io(io::Error),
|
||||||
|
// Io error with path context
|
||||||
|
IoPath(io::Error, PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
let keybinds = Keybinds::default();
|
||||||
|
Config { keybinds }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// Uses defaults, but lets config override them.
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserializes from given path.
|
||||||
|
#[allow(unused_must_use)]
|
||||||
|
pub fn new(path: &Path) -> ConfigResult {
|
||||||
|
match File::open(path) {
|
||||||
|
Ok(mut file) => {
|
||||||
|
let mut yaml_config = String::new();
|
||||||
|
file.read_to_string(&mut yaml_config)
|
||||||
|
.map_err(|e| ConfigError::IoPath(e, path.to_path_buf()))?;
|
||||||
|
Ok(Config::from_yaml(&yaml_config)?)
|
||||||
|
}
|
||||||
|
Err(e) => Err(ConfigError::IoPath(e, path.into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserializes the config from a default platform specific path,
|
||||||
|
/// merges the default configuration - options take precedence.
|
||||||
|
fn from_default_path() -> ConfigResult {
|
||||||
|
let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap();
|
||||||
|
let mut config_path: PathBuf = project_dirs.config_dir().to_owned();
|
||||||
|
config_path.push("config.yaml");
|
||||||
|
|
||||||
|
match Config::new(&config_path) {
|
||||||
|
Ok(config) => Ok(config),
|
||||||
|
Err(ConfigError::IoPath(_, _)) => Ok(Config::default()),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Entry point of the configuration
|
||||||
|
#[cfg(not(test))]
|
||||||
|
pub fn from_cli_config(cli_config: Option<ConfigCli>) -> ConfigResult {
|
||||||
|
match cli_config {
|
||||||
|
Some(ConfigCli::Config { clean, .. }) if clean => Ok(Config::default()),
|
||||||
|
Some(ConfigCli::Config { path, .. }) if path.is_some() => {
|
||||||
|
Ok(Config::new(&path.unwrap())?)
|
||||||
|
}
|
||||||
|
Some(_) | None => Ok(Config::from_default_path()?),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#[allow(unused_must_use)]
|
||||||
|
/// In order not to mess up tests from changing configurations
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn from_cli_config(_: Option<ConfigCli>) -> ConfigResult {
|
||||||
|
Ok(Config::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ConfigError {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
ConfigError::Io(ref err) => write!(formatter, "IoError: {}", err),
|
||||||
|
ConfigError::IoPath(ref err, ref path) => {
|
||||||
|
write!(formatter, "IoError: {}, File: {}", err, path.display(),)
|
||||||
|
}
|
||||||
|
ConfigError::Serde(ref err) => write!(formatter, "Deserialisation error: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ConfigError {
|
||||||
|
fn cause(&self) -> Option<&dyn error::Error> {
|
||||||
|
match *self {
|
||||||
|
ConfigError::Io(ref err) => Some(err),
|
||||||
|
ConfigError::IoPath(ref err, _) => Some(err),
|
||||||
|
ConfigError::Serde(ref err) => Some(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for ConfigError {
|
||||||
|
fn from(err: io::Error) -> ConfigError {
|
||||||
|
ConfigError::Io(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<serde_yaml::Error> for ConfigError {
|
||||||
|
fn from(err: serde_yaml::Error) -> ConfigError {
|
||||||
|
ConfigError::Serde(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The unit test location.
|
||||||
|
#[cfg(test)]
|
||||||
|
mod config_test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clean_option_equals_default_config() {
|
||||||
|
let no_file = PathBuf::from(r"../fixtures/config/config.yamlll");
|
||||||
|
let cli_config = ConfigCli::Config {
|
||||||
|
path: Some(no_file),
|
||||||
|
clean: true,
|
||||||
|
};
|
||||||
|
let config = Config::from_cli_config(Some(cli_config)).unwrap();
|
||||||
|
let default = Config::default();
|
||||||
|
assert_eq!(config, default);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_config_option_file_equals_default_config() {
|
||||||
|
let config = Config::from_cli_config(None).unwrap();
|
||||||
|
let default = Config::default();
|
||||||
|
assert_eq!(config, default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
//! Main input logic.
|
//! Main input logic.
|
||||||
|
|
||||||
use super::actions::Action;
|
use super::actions::Action;
|
||||||
use super::keybinds::get_default_keybinds;
|
use super::keybinds::Keybinds;
|
||||||
|
use crate::common::input::config::Config;
|
||||||
use crate::common::{AppInstruction, SenderWithContext, OPENCALLS};
|
use crate::common::{AppInstruction, SenderWithContext, OPENCALLS};
|
||||||
use crate::errors::ContextType;
|
use crate::errors::ContextType;
|
||||||
use crate::os_input_output::OsApi;
|
use crate::os_input_output::OsApi;
|
||||||
|
|
@ -13,19 +14,19 @@ use crate::CommandIsExecuting;
|
||||||
use termion::input::{TermRead, TermReadEventsAndRaw};
|
use termion::input::{TermRead, TermReadEventsAndRaw};
|
||||||
use zellij_tile::data::{Event, InputMode, Key, ModeInfo};
|
use zellij_tile::data::{Event, InputMode, Key, ModeInfo};
|
||||||
|
|
||||||
use super::keybinds::key_to_actions;
|
|
||||||
|
|
||||||
/// Handles the dispatching of [`Action`]s according to the current
|
/// Handles the dispatching of [`Action`]s according to the current
|
||||||
/// [`InputMode`], and keep tracks of the current [`InputMode`].
|
/// [`InputMode`], and keep tracks of the current [`InputMode`].
|
||||||
struct InputHandler {
|
struct InputHandler {
|
||||||
/// The current input mode
|
/// The current input mode
|
||||||
mode: InputMode,
|
mode: InputMode,
|
||||||
os_input: Box<dyn OsApi>,
|
os_input: Box<dyn OsApi>,
|
||||||
|
config: Config,
|
||||||
command_is_executing: CommandIsExecuting,
|
command_is_executing: CommandIsExecuting,
|
||||||
send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||||
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||||
send_app_instructions: SenderWithContext<AppInstruction>,
|
send_app_instructions: SenderWithContext<AppInstruction>,
|
||||||
|
should_exit: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputHandler {
|
impl InputHandler {
|
||||||
|
|
@ -33,6 +34,7 @@ impl InputHandler {
|
||||||
fn new(
|
fn new(
|
||||||
os_input: Box<dyn OsApi>,
|
os_input: Box<dyn OsApi>,
|
||||||
command_is_executing: CommandIsExecuting,
|
command_is_executing: CommandIsExecuting,
|
||||||
|
config: Config,
|
||||||
send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||||
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||||
|
|
@ -41,11 +43,13 @@ impl InputHandler {
|
||||||
InputHandler {
|
InputHandler {
|
||||||
mode: InputMode::Normal,
|
mode: InputMode::Normal,
|
||||||
os_input,
|
os_input,
|
||||||
|
config,
|
||||||
command_is_executing,
|
command_is_executing,
|
||||||
send_screen_instructions,
|
send_screen_instructions,
|
||||||
send_pty_instructions,
|
send_pty_instructions,
|
||||||
send_plugin_instructions,
|
send_plugin_instructions,
|
||||||
send_app_instructions,
|
send_app_instructions,
|
||||||
|
should_exit: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,40 +61,44 @@ impl InputHandler {
|
||||||
self.send_pty_instructions.update(err_ctx);
|
self.send_pty_instructions.update(err_ctx);
|
||||||
self.send_app_instructions.update(err_ctx);
|
self.send_app_instructions.update(err_ctx);
|
||||||
self.send_screen_instructions.update(err_ctx);
|
self.send_screen_instructions.update(err_ctx);
|
||||||
if let Ok(keybinds) = get_default_keybinds() {
|
let keybinds = self.config.keybinds.clone();
|
||||||
'input_loop: loop {
|
let alt_left_bracket = vec![27, 91];
|
||||||
//@@@ I think this should actually just iterate over stdin directly
|
loop {
|
||||||
|
if self.should_exit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
let stdin_buffer = self.os_input.read_from_stdin();
|
let stdin_buffer = self.os_input.read_from_stdin();
|
||||||
for key_result in stdin_buffer.events_and_raw() {
|
for key_result in stdin_buffer.events_and_raw() {
|
||||||
match key_result {
|
match key_result {
|
||||||
Ok((event, raw_bytes)) => match event {
|
Ok((event, raw_bytes)) => match event {
|
||||||
termion::event::Event::Key(key) => {
|
termion::event::Event::Key(key) => {
|
||||||
let key = cast_termion_key(key);
|
let key = cast_termion_key(key);
|
||||||
// FIXME this explicit break is needed because the current test
|
self.handle_key(&key, raw_bytes, &keybinds);
|
||||||
// framework relies on it to not create dead threads that loop
|
|
||||||
// and eat up CPUs. Do not remove until the test framework has
|
|
||||||
// been revised. Sorry about this (@categorille)
|
|
||||||
let mut should_break = false;
|
|
||||||
for action in key_to_actions(&key, raw_bytes, &self.mode, &keybinds)
|
|
||||||
{
|
|
||||||
should_break |= self.dispatch_action(action);
|
|
||||||
}
|
}
|
||||||
if should_break {
|
termion::event::Event::Unsupported(unsupported_key) => {
|
||||||
break 'input_loop;
|
// we have to do this because of a bug in termion
|
||||||
|
// this should be a key event and not an unsupported event
|
||||||
|
if unsupported_key == alt_left_bracket {
|
||||||
|
let key = Key::Alt('[');
|
||||||
|
self.handle_key(&key, raw_bytes, &keybinds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
termion::event::Event::Mouse(_)
|
termion::event::Event::Mouse(_) => {
|
||||||
| termion::event::Event::Unsupported(_) => {
|
// Mouse events aren't implemented yet,
|
||||||
unimplemented!("Mouse and unsupported events aren't supported!");
|
// use a NoOp untill then.
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(err) => panic!("Encountered read error: {:?}", err),
|
Err(err) => panic!("Encountered read error: {:?}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
//@@@ Error handling?
|
fn handle_key(&mut self, key: &Key, raw_bytes: Vec<u8>, keybinds: &Keybinds) {
|
||||||
self.exit();
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,9 +153,19 @@ impl InputHandler {
|
||||||
};
|
};
|
||||||
self.send_screen_instructions.send(screen_instr).unwrap();
|
self.send_screen_instructions.send(screen_instr).unwrap();
|
||||||
}
|
}
|
||||||
Action::SwitchFocus(_) => {
|
Action::SwitchFocus => {
|
||||||
self.send_screen_instructions
|
self.send_screen_instructions
|
||||||
.send(ScreenInstruction::MoveFocus)
|
.send(ScreenInstruction::SwitchFocus)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
Action::FocusNextPane => {
|
||||||
|
self.send_screen_instructions
|
||||||
|
.send(ScreenInstruction::FocusNextPane)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
Action::FocusPreviousPane => {
|
||||||
|
self.send_screen_instructions
|
||||||
|
.send(ScreenInstruction::FocusPreviousPane)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
Action::MoveFocus(direction) => {
|
Action::MoveFocus(direction) => {
|
||||||
|
|
@ -290,6 +308,7 @@ pub fn get_mode_info(mode: InputMode) -> ModeInfo {
|
||||||
/// its [`InputHandler::handle_input()`] loop.
|
/// its [`InputHandler::handle_input()`] loop.
|
||||||
pub fn input_loop(
|
pub fn input_loop(
|
||||||
os_input: Box<dyn OsApi>,
|
os_input: Box<dyn OsApi>,
|
||||||
|
config: Config,
|
||||||
command_is_executing: CommandIsExecuting,
|
command_is_executing: CommandIsExecuting,
|
||||||
send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||||
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||||
|
|
@ -299,6 +318,7 @@ pub fn input_loop(
|
||||||
let _handler = InputHandler::new(
|
let _handler = InputHandler::new(
|
||||||
os_input,
|
os_input,
|
||||||
command_is_executing,
|
command_is_executing,
|
||||||
|
config,
|
||||||
send_screen_instructions,
|
send_screen_instructions,
|
||||||
send_pty_instructions,
|
send_pty_instructions,
|
||||||
send_plugin_instructions,
|
send_plugin_instructions,
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,78 @@
|
||||||
//! Mapping of inputs to sequences of actions.
|
//! Mapping of inputs to sequences of actions.
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use super::actions::{Action, Direction};
|
use super::actions::{Action, Direction};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use zellij_tile::data::*;
|
use zellij_tile::data::*;
|
||||||
|
|
||||||
type Keybinds = HashMap<InputMode, ModeKeybinds>;
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
type ModeKeybinds = HashMap<Key, Vec<Action>>;
|
pub struct Keybinds(HashMap<InputMode, ModeKeybinds>);
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
pub struct ModeKeybinds(HashMap<Key, Vec<Action>>);
|
||||||
|
|
||||||
/// Populates the default hashmap of keybinds.
|
/// Intermediate struct used for deserialisation
|
||||||
/// @@@khs26 What about an input config file?
|
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||||
pub fn get_default_keybinds() -> Result<Keybinds, String> {
|
pub struct KeybindsFromYaml(HashMap<InputMode, Vec<KeyActionFromYaml>>);
|
||||||
|
|
||||||
|
/// Intermediate struct used for deserialisation
|
||||||
|
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||||
|
pub struct KeyActionFromYaml {
|
||||||
|
action: Vec<Action>,
|
||||||
|
key: Vec<Key>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Keybinds {
|
||||||
|
fn default() -> Keybinds {
|
||||||
let mut defaults = Keybinds::new();
|
let mut defaults = Keybinds::new();
|
||||||
|
|
||||||
for mode in InputMode::iter() {
|
for mode in InputMode::iter() {
|
||||||
defaults.insert(mode, get_defaults_for_mode(&mode));
|
defaults
|
||||||
|
.0
|
||||||
|
.insert(mode, Keybinds::get_defaults_for_mode(&mode));
|
||||||
|
}
|
||||||
|
defaults
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(defaults)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the default keybinds for a givent [`InputMode`].
|
impl Keybinds {
|
||||||
fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds {
|
pub fn new() -> Keybinds {
|
||||||
let mut defaults = ModeKeybinds::new();
|
Keybinds(HashMap::<InputMode, ModeKeybinds>::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_default_keybinds_with_config(keybinds: Option<KeybindsFromYaml>) -> Keybinds {
|
||||||
|
let default_keybinds = Keybinds::default();
|
||||||
|
if let Some(keybinds) = keybinds {
|
||||||
|
default_keybinds.merge_keybinds(Keybinds::from(keybinds))
|
||||||
|
} else {
|
||||||
|
default_keybinds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merges two Keybinds structs into one Keybinds struct
|
||||||
|
/// `other` overrides the ModeKeybinds of `self`.
|
||||||
|
fn merge_keybinds(&self, other: Keybinds) -> Keybinds {
|
||||||
|
let mut keybinds = Keybinds::new();
|
||||||
|
|
||||||
|
for mode in InputMode::iter() {
|
||||||
|
let mut mode_keybinds = ModeKeybinds::new();
|
||||||
|
if let Some(keybind) = self.0.get(&mode) {
|
||||||
|
mode_keybinds.0.extend(keybind.0.clone());
|
||||||
|
};
|
||||||
|
if let Some(keybind) = other.0.get(&mode) {
|
||||||
|
mode_keybinds.0.extend(keybind.0.clone());
|
||||||
|
}
|
||||||
|
if !mode_keybinds.0.is_empty() {
|
||||||
|
keybinds.0.insert(mode, mode_keybinds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keybinds
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the default keybinds for a given [`InputMode`].
|
||||||
|
fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds {
|
||||||
|
let mut defaults = HashMap::new();
|
||||||
|
|
||||||
match *mode {
|
match *mode {
|
||||||
InputMode::Normal => {
|
InputMode::Normal => {
|
||||||
|
|
@ -43,6 +91,14 @@ fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds {
|
||||||
vec![Action::SwitchToMode(InputMode::Scroll)],
|
vec![Action::SwitchToMode(InputMode::Scroll)],
|
||||||
);
|
);
|
||||||
defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
|
defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
|
||||||
|
|
||||||
|
defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]);
|
||||||
|
defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]);
|
||||||
|
defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]);
|
||||||
|
defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]);
|
||||||
|
defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]);
|
||||||
|
defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]);
|
||||||
|
defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]);
|
||||||
}
|
}
|
||||||
InputMode::Locked => {
|
InputMode::Locked => {
|
||||||
defaults.insert(
|
defaults.insert(
|
||||||
|
|
@ -85,6 +141,14 @@ fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds {
|
||||||
defaults.insert(Key::Down, vec![Action::Resize(Direction::Down)]);
|
defaults.insert(Key::Down, vec![Action::Resize(Direction::Down)]);
|
||||||
defaults.insert(Key::Up, vec![Action::Resize(Direction::Up)]);
|
defaults.insert(Key::Up, vec![Action::Resize(Direction::Up)]);
|
||||||
defaults.insert(Key::Right, vec![Action::Resize(Direction::Right)]);
|
defaults.insert(Key::Right, vec![Action::Resize(Direction::Right)]);
|
||||||
|
|
||||||
|
defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]);
|
||||||
|
defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]);
|
||||||
|
defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]);
|
||||||
|
defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]);
|
||||||
|
defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]);
|
||||||
|
defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]);
|
||||||
|
defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]);
|
||||||
}
|
}
|
||||||
InputMode::Pane => {
|
InputMode::Pane => {
|
||||||
defaults.insert(
|
defaults.insert(
|
||||||
|
|
@ -125,7 +189,7 @@ fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds {
|
||||||
defaults.insert(Key::Up, vec![Action::MoveFocus(Direction::Up)]);
|
defaults.insert(Key::Up, vec![Action::MoveFocus(Direction::Up)]);
|
||||||
defaults.insert(Key::Right, vec![Action::MoveFocus(Direction::Right)]);
|
defaults.insert(Key::Right, vec![Action::MoveFocus(Direction::Right)]);
|
||||||
|
|
||||||
defaults.insert(Key::Char('p'), vec![Action::SwitchFocus(Direction::Right)]);
|
defaults.insert(Key::Char('p'), vec![Action::SwitchFocus]);
|
||||||
defaults.insert(Key::Char('n'), vec![Action::NewPane(None)]);
|
defaults.insert(Key::Char('n'), vec![Action::NewPane(None)]);
|
||||||
defaults.insert(Key::Char('d'), vec![Action::NewPane(Some(Direction::Down))]);
|
defaults.insert(Key::Char('d'), vec![Action::NewPane(Some(Direction::Down))]);
|
||||||
defaults.insert(
|
defaults.insert(
|
||||||
|
|
@ -134,6 +198,14 @@ fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds {
|
||||||
);
|
);
|
||||||
defaults.insert(Key::Char('x'), vec![Action::CloseFocus]);
|
defaults.insert(Key::Char('x'), vec![Action::CloseFocus]);
|
||||||
defaults.insert(Key::Char('f'), vec![Action::ToggleFocusFullscreen]);
|
defaults.insert(Key::Char('f'), vec![Action::ToggleFocusFullscreen]);
|
||||||
|
|
||||||
|
defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]);
|
||||||
|
defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]);
|
||||||
|
defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]);
|
||||||
|
defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]);
|
||||||
|
defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]);
|
||||||
|
defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]);
|
||||||
|
defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]);
|
||||||
}
|
}
|
||||||
InputMode::Tab => {
|
InputMode::Tab => {
|
||||||
defaults.insert(
|
defaults.insert(
|
||||||
|
|
@ -192,6 +264,13 @@ fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds {
|
||||||
for i in '1'..='9' {
|
for i in '1'..='9' {
|
||||||
defaults.insert(Key::Char(i), vec![Action::GoToTab(i.to_digit(10).unwrap())]);
|
defaults.insert(Key::Char(i), vec![Action::GoToTab(i.to_digit(10).unwrap())]);
|
||||||
}
|
}
|
||||||
|
defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]);
|
||||||
|
defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]);
|
||||||
|
defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]);
|
||||||
|
defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]);
|
||||||
|
defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]);
|
||||||
|
defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]);
|
||||||
|
defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]);
|
||||||
}
|
}
|
||||||
InputMode::Scroll => {
|
InputMode::Scroll => {
|
||||||
defaults.insert(
|
defaults.insert(
|
||||||
|
|
@ -224,6 +303,14 @@ fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds {
|
||||||
|
|
||||||
defaults.insert(Key::Down, vec![Action::ScrollDown]);
|
defaults.insert(Key::Down, vec![Action::ScrollDown]);
|
||||||
defaults.insert(Key::Up, vec![Action::ScrollUp]);
|
defaults.insert(Key::Up, vec![Action::ScrollUp]);
|
||||||
|
|
||||||
|
defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]);
|
||||||
|
defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]);
|
||||||
|
defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]);
|
||||||
|
defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]);
|
||||||
|
defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]);
|
||||||
|
defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]);
|
||||||
|
defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]);
|
||||||
}
|
}
|
||||||
InputMode::RenameTab => {
|
InputMode::RenameTab => {
|
||||||
defaults.insert(Key::Char('\n'), vec![Action::SwitchToMode(InputMode::Tab)]);
|
defaults.insert(Key::Char('\n'), vec![Action::SwitchToMode(InputMode::Tab)]);
|
||||||
|
|
@ -238,24 +325,33 @@ fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds {
|
||||||
Action::SwitchToMode(InputMode::Tab),
|
Action::SwitchToMode(InputMode::Tab),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]);
|
||||||
|
defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]);
|
||||||
|
defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]);
|
||||||
|
defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]);
|
||||||
|
defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]);
|
||||||
|
defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]);
|
||||||
|
defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ModeKeybinds(defaults)
|
||||||
|
}
|
||||||
|
|
||||||
defaults
|
/// Converts a [`Key`] terminal event to a sequence of [`Action`]s according to the current
|
||||||
}
|
/// [`InputMode`] and [`Keybinds`].
|
||||||
|
pub fn key_to_actions(
|
||||||
/// Converts a [`Key`] terminal event to a sequence of [`Action`]s according to the current
|
|
||||||
/// [`InputMode`] and [`Keybinds`].
|
|
||||||
pub fn key_to_actions(
|
|
||||||
key: &Key,
|
key: &Key,
|
||||||
input: Vec<u8>,
|
input: Vec<u8>,
|
||||||
mode: &InputMode,
|
mode: &InputMode,
|
||||||
keybinds: &Keybinds,
|
keybinds: &Keybinds,
|
||||||
) -> Vec<Action> {
|
) -> Vec<Action> {
|
||||||
let mode_keybind_or_action = |action: Action| {
|
let mode_keybind_or_action = |action: Action| {
|
||||||
keybinds
|
keybinds
|
||||||
|
.0
|
||||||
.get(mode)
|
.get(mode)
|
||||||
.unwrap_or_else(|| unreachable!("Unrecognized mode: {:?}", mode))
|
.unwrap_or_else(|| unreachable!("Unrecognized mode: {:?}", mode))
|
||||||
|
.0
|
||||||
.get(key)
|
.get(key)
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_else(|| vec![action])
|
.unwrap_or_else(|| vec![action])
|
||||||
|
|
@ -265,4 +361,55 @@ pub fn key_to_actions(
|
||||||
InputMode::RenameTab => mode_keybind_or_action(Action::TabNameInput(input)),
|
InputMode::RenameTab => mode_keybind_or_action(Action::TabNameInput(input)),
|
||||||
_ => mode_keybind_or_action(Action::NoOp),
|
_ => mode_keybind_or_action(Action::NoOp),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ModeKeybinds {
|
||||||
|
fn new() -> ModeKeybinds {
|
||||||
|
ModeKeybinds(HashMap::<Key, Vec<Action>>::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merges `self` with `other`, if keys are the same, `other` overwrites.
|
||||||
|
fn merge(self, other: ModeKeybinds) -> ModeKeybinds {
|
||||||
|
let mut merged = self;
|
||||||
|
merged.0.extend(other.0);
|
||||||
|
merged
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<KeybindsFromYaml> for Keybinds {
|
||||||
|
fn from(keybinds_from_yaml: KeybindsFromYaml) -> Keybinds {
|
||||||
|
let mut keybinds = Keybinds::new();
|
||||||
|
|
||||||
|
for mode in InputMode::iter() {
|
||||||
|
let mut mode_keybinds = ModeKeybinds::new();
|
||||||
|
for key_action in keybinds_from_yaml.0.get(&mode).iter() {
|
||||||
|
for keybind in key_action.iter() {
|
||||||
|
mode_keybinds = mode_keybinds.merge(ModeKeybinds::from(keybind.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keybinds.0.insert(mode, mode_keybinds);
|
||||||
|
}
|
||||||
|
keybinds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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()
|
||||||
|
.map(|k| (k, actions.clone()))
|
||||||
|
.collect::<HashMap<Key, Vec<Action>>>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The unit test location.
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "./unit/keybinds_test.rs"]
|
||||||
|
mod keybinds_test;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
//! The way terminal iput is handled.
|
//! The way terminal input is handled.
|
||||||
|
|
||||||
pub mod actions;
|
pub mod actions;
|
||||||
|
pub mod config;
|
||||||
pub mod handler;
|
pub mod handler;
|
||||||
pub mod keybinds;
|
pub mod keybinds;
|
||||||
|
|
|
||||||
135
src/common/input/unit/keybinds_test.rs
Normal file
135
src/common/input/unit/keybinds_test.rs
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
use super::super::actions::*;
|
||||||
|
use super::super::keybinds::*;
|
||||||
|
use zellij_tile::data::Key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn merge_keybinds_merges_different_keys() {
|
||||||
|
let mut mode_keybinds_self = ModeKeybinds::new();
|
||||||
|
mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]);
|
||||||
|
let mut mode_keybinds_other = ModeKeybinds::new();
|
||||||
|
mode_keybinds_other
|
||||||
|
.0
|
||||||
|
.insert(Key::Backspace, vec![Action::NoOp]);
|
||||||
|
|
||||||
|
let mut mode_keybinds_expected = ModeKeybinds::new();
|
||||||
|
mode_keybinds_expected
|
||||||
|
.0
|
||||||
|
.insert(Key::F(1), vec![Action::NoOp]);
|
||||||
|
mode_keybinds_expected
|
||||||
|
.0
|
||||||
|
.insert(Key::Backspace, vec![Action::NoOp]);
|
||||||
|
|
||||||
|
let mode_keybinds_merged = mode_keybinds_self.merge(mode_keybinds_other);
|
||||||
|
|
||||||
|
assert_eq!(mode_keybinds_expected, mode_keybinds_merged);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn merge_mode_keybinds_overwrites_same_keys() {
|
||||||
|
let mut mode_keybinds_self = ModeKeybinds::new();
|
||||||
|
mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]);
|
||||||
|
let mut mode_keybinds_other = ModeKeybinds::new();
|
||||||
|
mode_keybinds_other
|
||||||
|
.0
|
||||||
|
.insert(Key::F(1), vec![Action::GoToTab(1)]);
|
||||||
|
|
||||||
|
let mut mode_keybinds_expected = ModeKeybinds::new();
|
||||||
|
mode_keybinds_expected
|
||||||
|
.0
|
||||||
|
.insert(Key::F(1), vec![Action::GoToTab(1)]);
|
||||||
|
|
||||||
|
let mode_keybinds_merged = mode_keybinds_self.merge(mode_keybinds_other);
|
||||||
|
|
||||||
|
assert_eq!(mode_keybinds_expected, mode_keybinds_merged);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn merge_keybinds_merges() {
|
||||||
|
let mut mode_keybinds_self = ModeKeybinds::new();
|
||||||
|
mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]);
|
||||||
|
let mut mode_keybinds_other = ModeKeybinds::new();
|
||||||
|
mode_keybinds_other
|
||||||
|
.0
|
||||||
|
.insert(Key::Backspace, vec![Action::NoOp]);
|
||||||
|
let mut keybinds_self = Keybinds::new();
|
||||||
|
keybinds_self
|
||||||
|
.0
|
||||||
|
.insert(InputMode::Normal, mode_keybinds_self.clone());
|
||||||
|
let mut keybinds_other = Keybinds::new();
|
||||||
|
keybinds_other
|
||||||
|
.0
|
||||||
|
.insert(InputMode::Resize, mode_keybinds_other.clone());
|
||||||
|
let mut keybinds_expected = Keybinds::new();
|
||||||
|
keybinds_expected
|
||||||
|
.0
|
||||||
|
.insert(InputMode::Normal, mode_keybinds_self);
|
||||||
|
keybinds_expected
|
||||||
|
.0
|
||||||
|
.insert(InputMode::Resize, mode_keybinds_other);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
keybinds_expected,
|
||||||
|
keybinds_self.merge_keybinds(keybinds_other)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn merge_keybinds_overwrites_same_keys() {
|
||||||
|
let mut mode_keybinds_self = ModeKeybinds::new();
|
||||||
|
mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]);
|
||||||
|
mode_keybinds_self.0.insert(Key::F(2), vec![Action::NoOp]);
|
||||||
|
mode_keybinds_self.0.insert(Key::F(3), vec![Action::NoOp]);
|
||||||
|
let mut mode_keybinds_other = ModeKeybinds::new();
|
||||||
|
mode_keybinds_other
|
||||||
|
.0
|
||||||
|
.insert(Key::F(1), vec![Action::GoToTab(1)]);
|
||||||
|
mode_keybinds_other
|
||||||
|
.0
|
||||||
|
.insert(Key::F(2), vec![Action::GoToTab(2)]);
|
||||||
|
mode_keybinds_other
|
||||||
|
.0
|
||||||
|
.insert(Key::F(3), vec![Action::GoToTab(3)]);
|
||||||
|
let mut keybinds_self = Keybinds::new();
|
||||||
|
keybinds_self
|
||||||
|
.0
|
||||||
|
.insert(InputMode::Normal, mode_keybinds_self.clone());
|
||||||
|
let mut keybinds_other = Keybinds::new();
|
||||||
|
keybinds_other
|
||||||
|
.0
|
||||||
|
.insert(InputMode::Normal, mode_keybinds_other.clone());
|
||||||
|
let mut keybinds_expected = Keybinds::new();
|
||||||
|
keybinds_expected
|
||||||
|
.0
|
||||||
|
.insert(InputMode::Normal, mode_keybinds_other);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
keybinds_expected,
|
||||||
|
keybinds_self.merge_keybinds(keybinds_other)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_keyaction_from_yaml_to_mode_keybindings() {
|
||||||
|
let actions = vec![Action::NoOp, Action::GoToTab(1)];
|
||||||
|
let keyaction = KeyActionFromYaml {
|
||||||
|
action: actions.clone(),
|
||||||
|
key: vec![Key::F(1), Key::Backspace, Key::Char('t')],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut expected = ModeKeybinds::new();
|
||||||
|
expected.0.insert(Key::F(1), actions.clone());
|
||||||
|
expected.0.insert(Key::Backspace, actions.clone());
|
||||||
|
expected.0.insert(Key::Char('t'), actions);
|
||||||
|
|
||||||
|
assert_eq!(expected, ModeKeybinds::from(keyaction));
|
||||||
|
}
|
||||||
|
|
||||||
|
//#[test]
|
||||||
|
//fn from_keybinds_from_yaml_to_keybinds(){
|
||||||
|
//let mut keybinds_from_yaml = KeybindsFromYaml(HashMap<InputMode, Vec<KeyActionFromYaml>>);
|
||||||
|
//let actions = vec![Action::NoOp, Action::GoToTab(1), ];
|
||||||
|
//let keyaction = KeyActionFromYaml {
|
||||||
|
//action : actions.clone(),
|
||||||
|
//key : vec![ Key::F(1), Key::Backspace , Key::Char('t'), ],
|
||||||
|
//};
|
||||||
|
//}
|
||||||
|
|
@ -22,6 +22,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::cli::CliArgs;
|
use crate::cli::CliArgs;
|
||||||
|
use crate::common::input::config::Config;
|
||||||
use crate::layout::Layout;
|
use crate::layout::Layout;
|
||||||
use crate::panes::PaneId;
|
use crate::panes::PaneId;
|
||||||
use command_is_executing::CommandIsExecuting;
|
use command_is_executing::CommandIsExecuting;
|
||||||
|
|
@ -125,6 +126,13 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||||
.write(take_snapshot.as_bytes())
|
.write(take_snapshot.as_bytes())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let config = Config::from_cli_config(opts.config)
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("There was an error in the config file:\n{}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let command_is_executing = CommandIsExecuting::new();
|
let command_is_executing = CommandIsExecuting::new();
|
||||||
|
|
||||||
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
|
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
|
||||||
|
|
@ -267,11 +275,23 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||||
screen.send_app_instructions.update(err_ctx);
|
screen.send_app_instructions.update(err_ctx);
|
||||||
screen.send_pty_instructions.update(err_ctx);
|
screen.send_pty_instructions.update(err_ctx);
|
||||||
match event {
|
match event {
|
||||||
ScreenInstruction::Pty(pid, vte_event) => {
|
ScreenInstruction::PtyBytes(pid, vte_bytes) => {
|
||||||
screen
|
let active_tab = screen.get_active_tab_mut().unwrap();
|
||||||
.get_active_tab_mut()
|
if active_tab.has_terminal_pid(pid) {
|
||||||
.unwrap()
|
// it's most likely that this event is directed at the active tab
|
||||||
.handle_pty_event(pid, vte_event);
|
// 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 => {
|
ScreenInstruction::Render => {
|
||||||
screen.render();
|
screen.render();
|
||||||
|
|
@ -306,9 +326,15 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||||
ScreenInstruction::ResizeUp => {
|
ScreenInstruction::ResizeUp => {
|
||||||
screen.get_active_tab_mut().unwrap().resize_up();
|
screen.get_active_tab_mut().unwrap().resize_up();
|
||||||
}
|
}
|
||||||
ScreenInstruction::MoveFocus => {
|
ScreenInstruction::SwitchFocus => {
|
||||||
screen.get_active_tab_mut().unwrap().move_focus();
|
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 => {
|
ScreenInstruction::MoveFocusLeft => {
|
||||||
screen.get_active_tab_mut().unwrap().move_focus_left();
|
screen.get_active_tab_mut().unwrap().move_focus_left();
|
||||||
}
|
}
|
||||||
|
|
@ -389,6 +415,9 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||||
ScreenInstruction::UpdateTabName(c) => {
|
ScreenInstruction::UpdateTabName(c) => {
|
||||||
screen.update_active_tab_name(c);
|
screen.update_active_tab_name(c);
|
||||||
}
|
}
|
||||||
|
ScreenInstruction::TerminalResize => {
|
||||||
|
screen.resize_to_screen();
|
||||||
|
}
|
||||||
ScreenInstruction::ChangeMode(mode_info) => {
|
ScreenInstruction::ChangeMode(mode_info) => {
|
||||||
screen.change_mode(mode_info);
|
screen.change_mode(mode_info);
|
||||||
}
|
}
|
||||||
|
|
@ -506,6 +535,19 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let _signal_thread = thread::Builder::new()
|
||||||
|
.name("signal_listener".to_string())
|
||||||
|
.spawn({
|
||||||
|
let os_input = os_input.clone();
|
||||||
|
let send_screen_instructions = send_screen_instructions.clone();
|
||||||
|
move || {
|
||||||
|
os_input.receive_sigwinch(Box::new(move || {
|
||||||
|
let _ = send_screen_instructions.send(ScreenInstruction::TerminalResize);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// TODO: currently we don't wait for this to quit
|
// TODO: currently we don't wait for this to quit
|
||||||
// because otherwise the app will hang. Need to fix this so it both
|
// because otherwise the app will hang. Need to fix this so it both
|
||||||
// listens to the ipc-bus and is able to quit cleanly
|
// listens to the ipc-bus and is able to quit cleanly
|
||||||
|
|
@ -553,7 +595,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||||
}
|
}
|
||||||
ApiCommand::MoveFocus => {
|
ApiCommand::MoveFocus => {
|
||||||
send_screen_instructions
|
send_screen_instructions
|
||||||
.send(ScreenInstruction::MoveFocus)
|
.send(ScreenInstruction::FocusNextPane)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -574,9 +616,11 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||||
let send_pty_instructions = send_pty_instructions.clone();
|
let send_pty_instructions = send_pty_instructions.clone();
|
||||||
let send_plugin_instructions = send_plugin_instructions.clone();
|
let send_plugin_instructions = send_plugin_instructions.clone();
|
||||||
let os_input = os_input.clone();
|
let os_input = os_input.clone();
|
||||||
|
let config = config;
|
||||||
move || {
|
move || {
|
||||||
input_loop(
|
input_loop(
|
||||||
os_input,
|
os_input,
|
||||||
|
config,
|
||||||
command_is_executing,
|
command_is_executing,
|
||||||
send_screen_instructions,
|
send_screen_instructions,
|
||||||
send_pty_instructions,
|
send_pty_instructions,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ use std::path::PathBuf;
|
||||||
use std::process::{Child, Command};
|
use std::process::{Child, Command};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use signal_hook::{consts::signal::*, iterator::Signals};
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
fn into_raw_mode(pid: RawFd) {
|
fn into_raw_mode(pid: RawFd) {
|
||||||
|
|
@ -65,7 +67,7 @@ pub fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) {
|
||||||
/// process exits.
|
/// process exits.
|
||||||
fn handle_command_exit(mut child: Child) {
|
fn handle_command_exit(mut child: Child) {
|
||||||
// register the SIGINT signal (TODO handle more signals)
|
// register the SIGINT signal (TODO handle more signals)
|
||||||
let signals = ::signal_hook::iterator::Signals::new(&[::signal_hook::SIGINT]).unwrap();
|
let mut signals = ::signal_hook::iterator::Signals::new(&[SIGINT]).unwrap();
|
||||||
'handle_exit: loop {
|
'handle_exit: loop {
|
||||||
// test whether the child process has exited
|
// test whether the child process has exited
|
||||||
match child.try_wait() {
|
match child.try_wait() {
|
||||||
|
|
@ -82,11 +84,16 @@ fn handle_command_exit(mut child: Child) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for signal in signals.pending() {
|
for signal in signals.pending() {
|
||||||
if signal == signal_hook::SIGINT {
|
// FIXME: We need to handle more signals here!
|
||||||
|
#[allow(clippy::single_match)]
|
||||||
|
match signal {
|
||||||
|
SIGINT => {
|
||||||
child.kill().unwrap();
|
child.kill().unwrap();
|
||||||
child.wait().unwrap();
|
child.wait().unwrap();
|
||||||
break 'handle_exit;
|
break 'handle_exit;
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -188,6 +195,7 @@ pub trait OsApi: Send + Sync {
|
||||||
fn get_stdout_writer(&self) -> Box<dyn io::Write>;
|
fn get_stdout_writer(&self) -> Box<dyn io::Write>;
|
||||||
/// Returns a [`Box`] pointer to this [`OsApi`] struct.
|
/// Returns a [`Box`] pointer to this [`OsApi`] struct.
|
||||||
fn box_clone(&self) -> Box<dyn OsApi>;
|
fn box_clone(&self) -> Box<dyn OsApi>;
|
||||||
|
fn receive_sigwinch(&self, cb: Box<dyn Fn()>);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OsApi for OsInputOutput {
|
impl OsApi for OsInputOutput {
|
||||||
|
|
@ -234,10 +242,30 @@ impl OsApi for OsInputOutput {
|
||||||
Box::new(stdout)
|
Box::new(stdout)
|
||||||
}
|
}
|
||||||
fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error> {
|
fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error> {
|
||||||
kill(Pid::from_raw(pid), Some(Signal::SIGINT)).unwrap();
|
// TODO:
|
||||||
|
// Ideally, we should be using SIGINT rather than SIGKILL here, but there are cases in which
|
||||||
|
// the terminal we're trying to kill hangs on SIGINT and so all the app gets stuck
|
||||||
|
// that's why we're sending SIGKILL here
|
||||||
|
// A better solution would be to send SIGINT here and not wait for it, and then have
|
||||||
|
// a background thread do the waitpid stuff and send SIGKILL if the process is stuck
|
||||||
|
kill(Pid::from_raw(pid), Some(Signal::SIGKILL)).unwrap();
|
||||||
waitpid(Pid::from_raw(pid), None).unwrap();
|
waitpid(Pid::from_raw(pid), None).unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
fn receive_sigwinch(&self, cb: Box<dyn Fn()>) {
|
||||||
|
let mut signals = Signals::new(&[SIGWINCH, SIGTERM, SIGINT, SIGQUIT]).unwrap();
|
||||||
|
for signal in signals.forever() {
|
||||||
|
match signal {
|
||||||
|
SIGWINCH => {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
SIGTERM | SIGINT | SIGQUIT => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Box<dyn OsApi> {
|
impl Clone for Box<dyn OsApi> {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ use ::std::os::unix::io::RawFd;
|
||||||
use ::std::pin::*;
|
use ::std::pin::*;
|
||||||
use ::std::sync::mpsc::Receiver;
|
use ::std::sync::mpsc::Receiver;
|
||||||
use ::std::time::{Duration, Instant};
|
use ::std::time::{Duration, Instant};
|
||||||
use ::vte;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::{ScreenInstruction, SenderWithContext, OPENCALLS};
|
use super::{ScreenInstruction, SenderWithContext, OPENCALLS};
|
||||||
|
|
@ -64,86 +63,7 @@ impl Stream for ReadFromPid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
pub type VteBytes = Vec<u8>;
|
||||||
pub enum VteEvent {
|
|
||||||
// TODO: try not to allocate Vecs
|
|
||||||
Print(char),
|
|
||||||
Execute(u8), // byte
|
|
||||||
Hook(Vec<i64>, Vec<u8>, bool, char), // params, intermediates, ignore, char
|
|
||||||
Put(u8), // byte
|
|
||||||
Unhook,
|
|
||||||
OscDispatch(Vec<Vec<u8>>, bool), // params, bell_terminated
|
|
||||||
CsiDispatch(Vec<i64>, Vec<u8>, bool, char), // params, intermediates, ignore, char
|
|
||||||
EscDispatch(Vec<u8>, bool, u8), // intermediates, ignore, byte
|
|
||||||
}
|
|
||||||
|
|
||||||
struct VteEventSender {
|
|
||||||
id: RawFd,
|
|
||||||
sender: SenderWithContext<ScreenInstruction>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VteEventSender {
|
|
||||||
pub fn new(id: RawFd, sender: SenderWithContext<ScreenInstruction>) -> Self {
|
|
||||||
VteEventSender { id, sender }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl vte::Perform for VteEventSender {
|
|
||||||
fn print(&mut self, c: char) {
|
|
||||||
let _ = self
|
|
||||||
.sender
|
|
||||||
.send(ScreenInstruction::Pty(self.id, VteEvent::Print(c)));
|
|
||||||
}
|
|
||||||
fn execute(&mut self, byte: u8) {
|
|
||||||
let _ = self
|
|
||||||
.sender
|
|
||||||
.send(ScreenInstruction::Pty(self.id, VteEvent::Execute(byte)));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) {
|
|
||||||
let params = params.iter().copied().collect();
|
|
||||||
let intermediates = intermediates.iter().copied().collect();
|
|
||||||
let instruction =
|
|
||||||
ScreenInstruction::Pty(self.id, VteEvent::Hook(params, intermediates, ignore, c));
|
|
||||||
let _ = self.sender.send(instruction);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn put(&mut self, byte: u8) {
|
|
||||||
let _ = self
|
|
||||||
.sender
|
|
||||||
.send(ScreenInstruction::Pty(self.id, VteEvent::Put(byte)));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unhook(&mut self) {
|
|
||||||
let _ = self
|
|
||||||
.sender
|
|
||||||
.send(ScreenInstruction::Pty(self.id, VteEvent::Unhook));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
|
|
||||||
let params = params.iter().map(|p| p.to_vec()).collect();
|
|
||||||
let instruction =
|
|
||||||
ScreenInstruction::Pty(self.id, VteEvent::OscDispatch(params, bell_terminated));
|
|
||||||
let _ = self.sender.send(instruction);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) {
|
|
||||||
let params = params.iter().copied().collect();
|
|
||||||
let intermediates = intermediates.iter().copied().collect();
|
|
||||||
let instruction = ScreenInstruction::Pty(
|
|
||||||
self.id,
|
|
||||||
VteEvent::CsiDispatch(params, intermediates, ignore, c),
|
|
||||||
);
|
|
||||||
let _ = self.sender.send(instruction);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
|
|
||||||
let intermediates = intermediates.iter().copied().collect();
|
|
||||||
let instruction =
|
|
||||||
ScreenInstruction::Pty(self.id, VteEvent::EscDispatch(intermediates, ignore, byte));
|
|
||||||
let _ = self.sender.send(instruction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Instructions related to PTYs (pseudoterminals).
|
/// Instructions related to PTYs (pseudoterminals).
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -178,8 +98,6 @@ fn stream_terminal_bytes(
|
||||||
async move {
|
async move {
|
||||||
err_ctx.add_call(ContextType::AsyncTask);
|
err_ctx.add_call(ContextType::AsyncTask);
|
||||||
send_screen_instructions.update(err_ctx);
|
send_screen_instructions.update(err_ctx);
|
||||||
let mut vte_parser = vte::Parser::new();
|
|
||||||
let mut vte_event_sender = VteEventSender::new(pid, send_screen_instructions.clone());
|
|
||||||
let mut terminal_bytes = ReadFromPid::new(&pid, os_input);
|
let mut terminal_bytes = ReadFromPid::new(&pid, os_input);
|
||||||
|
|
||||||
let mut last_byte_receive_time: Option<Instant> = None;
|
let mut last_byte_receive_time: Option<Instant> = None;
|
||||||
|
|
@ -188,13 +106,13 @@ fn stream_terminal_bytes(
|
||||||
|
|
||||||
while let Some(bytes) = terminal_bytes.next().await {
|
while let Some(bytes) = terminal_bytes.next().await {
|
||||||
let bytes_is_empty = bytes.is_empty();
|
let bytes_is_empty = bytes.is_empty();
|
||||||
for byte in bytes {
|
|
||||||
if debug {
|
if debug {
|
||||||
debug_to_file(byte, pid).unwrap();
|
for byte in bytes.iter() {
|
||||||
|
debug_to_file(*byte, pid).unwrap();
|
||||||
}
|
}
|
||||||
vte_parser.advance(&mut vte_event_sender, byte);
|
|
||||||
}
|
}
|
||||||
if !bytes_is_empty {
|
if !bytes_is_empty {
|
||||||
|
let _ = send_screen_instructions.send(ScreenInstruction::PtyBytes(pid, bytes));
|
||||||
// for UX reasons, if we got something on the wire, we only send the render notice if:
|
// 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
|
// 1. there aren't any more bytes on the wire afterwards
|
||||||
// 2. a certain period (currently 30ms) has elapsed since the last render
|
// 2. a certain period (currently 30ms) has elapsed since the last render
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use std::sync::mpsc::Receiver;
|
||||||
use super::{AppInstruction, SenderWithContext};
|
use super::{AppInstruction, SenderWithContext};
|
||||||
use crate::os_input_output::OsApi;
|
use crate::os_input_output::OsApi;
|
||||||
use crate::panes::PositionAndSize;
|
use crate::panes::PositionAndSize;
|
||||||
use crate::pty_bus::{PtyInstruction, VteEvent};
|
use crate::pty_bus::{PtyInstruction, VteBytes};
|
||||||
use crate::tab::Tab;
|
use crate::tab::Tab;
|
||||||
use crate::{errors::ErrorContext, wasm_vm::PluginInstruction};
|
use crate::{errors::ErrorContext, wasm_vm::PluginInstruction};
|
||||||
use crate::{layout::Layout, panes::PaneId};
|
use crate::{layout::Layout, panes::PaneId};
|
||||||
|
|
@ -18,7 +18,7 @@ use zellij_tile::data::{Event, ModeInfo, TabInfo};
|
||||||
/// Instructions that can be sent to the [`Screen`].
|
/// Instructions that can be sent to the [`Screen`].
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ScreenInstruction {
|
pub enum ScreenInstruction {
|
||||||
Pty(RawFd, VteEvent),
|
PtyBytes(RawFd, VteBytes),
|
||||||
Render,
|
Render,
|
||||||
NewPane(PaneId),
|
NewPane(PaneId),
|
||||||
HorizontalSplit(PaneId),
|
HorizontalSplit(PaneId),
|
||||||
|
|
@ -28,7 +28,9 @@ pub enum ScreenInstruction {
|
||||||
ResizeRight,
|
ResizeRight,
|
||||||
ResizeDown,
|
ResizeDown,
|
||||||
ResizeUp,
|
ResizeUp,
|
||||||
MoveFocus,
|
SwitchFocus,
|
||||||
|
FocusNextPane,
|
||||||
|
FocusPreviousPane,
|
||||||
MoveFocusLeft,
|
MoveFocusLeft,
|
||||||
MoveFocusDown,
|
MoveFocusDown,
|
||||||
MoveFocusUp,
|
MoveFocusUp,
|
||||||
|
|
@ -50,6 +52,7 @@ pub enum ScreenInstruction {
|
||||||
CloseTab,
|
CloseTab,
|
||||||
GoToTab(u32),
|
GoToTab(u32),
|
||||||
UpdateTabName(Vec<u8>),
|
UpdateTabName(Vec<u8>),
|
||||||
|
TerminalResize,
|
||||||
ChangeMode(ModeInfo),
|
ChangeMode(ModeInfo),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,6 +216,15 @@ impl Screen {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn resize_to_screen(&mut self) {
|
||||||
|
let new_screen_size = self.os_api.get_terminal_size_using_fd(0);
|
||||||
|
self.full_screen_ws = new_screen_size;
|
||||||
|
for (_, tab) in self.tabs.iter_mut() {
|
||||||
|
tab.resize_whole_tab(new_screen_size);
|
||||||
|
}
|
||||||
|
self.render();
|
||||||
|
}
|
||||||
|
|
||||||
/// Renders this [`Screen`], which amounts to rendering its active [`Tab`].
|
/// Renders this [`Screen`], which amounts to rendering its active [`Tab`].
|
||||||
pub fn render(&mut self) {
|
pub fn render(&mut self) {
|
||||||
if let Some(active_tab) = self.get_active_tab_mut() {
|
if let Some(active_tab) = self.get_active_tab_mut() {
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,18 @@ fn ansi_len(s: &str) -> usize {
|
||||||
.count()
|
.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pad_to_size(s: &str, rows: usize, columns: usize) -> String {
|
pub fn adjust_to_size(s: &str, rows: usize, columns: usize) -> String {
|
||||||
s.lines()
|
s.lines()
|
||||||
.map(|l| [l, &str::repeat(" ", columns - ansi_len(l))].concat())
|
.map(|l| {
|
||||||
|
let actual_len = ansi_len(l);
|
||||||
|
if actual_len > columns {
|
||||||
|
let mut line = String::from(l);
|
||||||
|
line.truncate(columns);
|
||||||
|
return line;
|
||||||
|
} else {
|
||||||
|
return [l, &str::repeat(" ", columns - ansi_len(l))].concat();
|
||||||
|
}
|
||||||
|
})
|
||||||
.chain(iter::repeat(str::repeat(" ", columns)))
|
.chain(iter::repeat(str::repeat(" ", columns)))
|
||||||
.take(rows)
|
.take(rows)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ use structopt::StructOpt;
|
||||||
use crate::cli::CliArgs;
|
use crate::cli::CliArgs;
|
||||||
use crate::command_is_executing::CommandIsExecuting;
|
use crate::command_is_executing::CommandIsExecuting;
|
||||||
use crate::os_input_output::get_os_input;
|
use crate::os_input_output::get_os_input;
|
||||||
use crate::pty_bus::VteEvent;
|
|
||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
consts::{ZELLIJ_IPC_PIPE, ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR},
|
consts::{ZELLIJ_IPC_PIPE, ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR},
|
||||||
logging::*,
|
logging::*,
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,13 @@ use std::collections::{HashMap, VecDeque};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::{Arc, Condvar, Mutex};
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use crate::os_input_output::OsApi;
|
use crate::os_input_output::OsApi;
|
||||||
use crate::tests::possible_tty_inputs::{get_possible_tty_inputs, Bytes};
|
use crate::tests::possible_tty_inputs::{get_possible_tty_inputs, Bytes};
|
||||||
|
|
||||||
use crate::tests::utils::commands::SLEEP;
|
use crate::tests::utils::commands::{QUIT, SLEEP};
|
||||||
|
|
||||||
const MIN_TIME_BETWEEN_SNAPSHOTS: Duration = Duration::from_millis(50);
|
const MIN_TIME_BETWEEN_SNAPSHOTS: Duration = Duration::from_millis(50);
|
||||||
|
|
||||||
|
|
@ -72,7 +71,8 @@ pub struct FakeInputOutput {
|
||||||
win_sizes: Arc<Mutex<HashMap<RawFd, PositionAndSize>>>,
|
win_sizes: Arc<Mutex<HashMap<RawFd, PositionAndSize>>>,
|
||||||
possible_tty_inputs: HashMap<u16, Bytes>,
|
possible_tty_inputs: HashMap<u16, Bytes>,
|
||||||
last_snapshot_time: Arc<Mutex<Instant>>,
|
last_snapshot_time: Arc<Mutex<Instant>>,
|
||||||
started_reading_from_pty: Arc<AtomicBool>,
|
should_trigger_sigwinch: Arc<(Mutex<bool>, Condvar)>,
|
||||||
|
sigwinch_event: Option<PositionAndSize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FakeInputOutput {
|
impl FakeInputOutput {
|
||||||
|
|
@ -91,7 +91,8 @@ impl FakeInputOutput {
|
||||||
io_events: Arc::new(Mutex::new(vec![])),
|
io_events: Arc::new(Mutex::new(vec![])),
|
||||||
win_sizes: Arc::new(Mutex::new(win_sizes)),
|
win_sizes: Arc::new(Mutex::new(win_sizes)),
|
||||||
possible_tty_inputs: get_possible_tty_inputs(),
|
possible_tty_inputs: get_possible_tty_inputs(),
|
||||||
started_reading_from_pty: Arc::new(AtomicBool::new(false)),
|
should_trigger_sigwinch: Arc::new((Mutex::new(false), Condvar::new())),
|
||||||
|
sigwinch_event: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn with_tty_inputs(mut self, tty_inputs: HashMap<u16, Bytes>) -> Self {
|
pub fn with_tty_inputs(mut self, tty_inputs: HashMap<u16, Bytes>) -> Self {
|
||||||
|
|
@ -108,10 +109,20 @@ impl FakeInputOutput {
|
||||||
pub fn add_terminal(&mut self, fd: RawFd) {
|
pub fn add_terminal(&mut self, fd: RawFd) {
|
||||||
self.stdin_writes.lock().unwrap().insert(fd, vec![]);
|
self.stdin_writes.lock().unwrap().insert(fd, vec![]);
|
||||||
}
|
}
|
||||||
|
pub fn add_sigwinch_event(&mut self, new_position_and_size: PositionAndSize) {
|
||||||
|
self.sigwinch_event = Some(new_position_and_size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OsApi for FakeInputOutput {
|
impl OsApi for FakeInputOutput {
|
||||||
fn get_terminal_size_using_fd(&self, pid: RawFd) -> PositionAndSize {
|
fn get_terminal_size_using_fd(&self, pid: RawFd) -> PositionAndSize {
|
||||||
|
if let Some(new_position_and_size) = self.sigwinch_event {
|
||||||
|
let (lock, _cvar) = &*self.should_trigger_sigwinch;
|
||||||
|
let should_trigger_sigwinch = lock.lock().unwrap();
|
||||||
|
if *should_trigger_sigwinch && pid == 0 {
|
||||||
|
return new_position_and_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
let win_sizes = self.win_sizes.lock().unwrap();
|
let win_sizes = self.win_sizes.lock().unwrap();
|
||||||
let winsize = win_sizes.get(&pid).unwrap();
|
let winsize = win_sizes.get(&pid).unwrap();
|
||||||
*winsize
|
*winsize
|
||||||
|
|
@ -159,7 +170,6 @@ impl OsApi for FakeInputOutput {
|
||||||
if bytes_read > bytes.read_position {
|
if bytes_read > bytes.read_position {
|
||||||
bytes.set_read_position(bytes_read);
|
bytes.set_read_position(bytes_read);
|
||||||
}
|
}
|
||||||
self.started_reading_from_pty.store(true, Ordering::Release);
|
|
||||||
return Ok(bytes_read);
|
return Ok(bytes_read);
|
||||||
}
|
}
|
||||||
None => Err(nix::Error::Sys(nix::errno::Errno::EAGAIN)),
|
None => Err(nix::Error::Sys(nix::errno::Errno::EAGAIN)),
|
||||||
|
|
@ -199,6 +209,12 @@ impl OsApi for FakeInputOutput {
|
||||||
.unwrap_or(vec![]);
|
.unwrap_or(vec![]);
|
||||||
if command == SLEEP {
|
if command == SLEEP {
|
||||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||||
|
} else if command == QUIT && self.sigwinch_event.is_some() {
|
||||||
|
let (lock, cvar) = &*self.should_trigger_sigwinch;
|
||||||
|
let mut should_trigger_sigwinch = lock.lock().unwrap();
|
||||||
|
*should_trigger_sigwinch = true;
|
||||||
|
cvar.notify_one();
|
||||||
|
::std::thread::sleep(MIN_TIME_BETWEEN_SNAPSHOTS); // give some time for the app to resize before quitting
|
||||||
}
|
}
|
||||||
command
|
command
|
||||||
}
|
}
|
||||||
|
|
@ -209,4 +225,14 @@ impl OsApi for FakeInputOutput {
|
||||||
self.io_events.lock().unwrap().push(IoEvent::Kill(fd));
|
self.io_events.lock().unwrap().push(IoEvent::Kill(fd));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
fn receive_sigwinch(&self, cb: Box<dyn Fn()>) {
|
||||||
|
if self.sigwinch_event.is_some() {
|
||||||
|
let (lock, cvar) = &*self.should_trigger_sigwinch;
|
||||||
|
let mut should_trigger_sigwinch = lock.lock().unwrap();
|
||||||
|
while !*should_trigger_sigwinch {
|
||||||
|
should_trigger_sigwinch = cvar.wait(should_trigger_sigwinch).unwrap();
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}
|
||||||
use crate::{start, CliArgs};
|
use crate::{start, CliArgs};
|
||||||
|
|
||||||
use crate::tests::utils::commands::{
|
use crate::tests::utils::commands::{
|
||||||
CLOSE_PANE_IN_PANE_MODE, COMMAND_TOGGLE, ESC, MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT,
|
CLOSE_PANE_IN_PANE_MODE, ESC, MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT,
|
||||||
RESIZE_DOWN_IN_RESIZE_MODE, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE,
|
RESIZE_DOWN_IN_RESIZE_MODE, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE,
|
||||||
SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE,
|
SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -12,4 +12,5 @@ pub mod resize_left;
|
||||||
pub mod resize_right;
|
pub mod resize_right;
|
||||||
pub mod resize_up;
|
pub mod resize_up;
|
||||||
pub mod tabs;
|
pub mod tabs;
|
||||||
|
pub mod terminal_window_resize;
|
||||||
pub mod toggle_fullscreen;
|
pub mod toggle_fullscreen;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
source: src/tests/integration/terminal_window_resize.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
|
||||||
|
█
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
source: src/tests/integration/terminal_window_resize.rs
|
||||||
|
expression: snapshot_before_quit
|
||||||
|
|
||||||
|
---
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
prompt $ █
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
source: src/tests/integration/terminal_window_resize.rs
|
||||||
|
expression: snapshot_before_quit
|
||||||
|
|
||||||
|
---
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
prompt $ █
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
source: src/tests/integration/terminal_window_resize.rs
|
||||||
|
expression: snapshot_before_quit
|
||||||
|
|
||||||
|
---
|
||||||
|
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 $
|
||||||
|
█
|
||||||
127
src/tests/integration/terminal_window_resize.rs
Normal file
127
src/tests/integration/terminal_window_resize.rs
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
use crate::panes::PositionAndSize;
|
||||||
|
use ::insta::assert_snapshot;
|
||||||
|
|
||||||
|
use crate::tests::fakes::FakeInputOutput;
|
||||||
|
use crate::tests::utils::commands::QUIT;
|
||||||
|
use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
|
||||||
|
use crate::{start, CliArgs};
|
||||||
|
|
||||||
|
fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
|
||||||
|
FakeInputOutput::new(fake_win_size.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn window_width_decrease_with_one_pane() {
|
||||||
|
let fake_win_size = PositionAndSize {
|
||||||
|
columns: 121,
|
||||||
|
rows: 20,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
};
|
||||||
|
let mut fake_input_output = get_fake_os_input(&fake_win_size);
|
||||||
|
fake_input_output.add_terminal_input(&[&QUIT]);
|
||||||
|
fake_input_output.add_sigwinch_event(PositionAndSize {
|
||||||
|
columns: 90,
|
||||||
|
rows: 20,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
});
|
||||||
|
let opts = CliArgs::default();
|
||||||
|
start(Box::new(fake_input_output.clone()), opts);
|
||||||
|
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 window_width_increase_with_one_pane() {
|
||||||
|
let fake_win_size = PositionAndSize {
|
||||||
|
columns: 121,
|
||||||
|
rows: 20,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
};
|
||||||
|
let mut fake_input_output = get_fake_os_input(&fake_win_size);
|
||||||
|
fake_input_output.add_terminal_input(&[&QUIT]);
|
||||||
|
fake_input_output.add_sigwinch_event(PositionAndSize {
|
||||||
|
columns: 141,
|
||||||
|
rows: 20,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
});
|
||||||
|
let opts = CliArgs::default();
|
||||||
|
start(Box::new(fake_input_output.clone()), opts);
|
||||||
|
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 window_height_increase_with_one_pane() {
|
||||||
|
let fake_win_size = PositionAndSize {
|
||||||
|
columns: 121,
|
||||||
|
rows: 20,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
};
|
||||||
|
let mut fake_input_output = get_fake_os_input(&fake_win_size);
|
||||||
|
fake_input_output.add_terminal_input(&[&QUIT]);
|
||||||
|
fake_input_output.add_sigwinch_event(PositionAndSize {
|
||||||
|
columns: 121,
|
||||||
|
rows: 30,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
});
|
||||||
|
let opts = CliArgs::default();
|
||||||
|
start(Box::new(fake_input_output.clone()), opts);
|
||||||
|
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 window_width_and_height_decrease_with_one_pane() {
|
||||||
|
let fake_win_size = PositionAndSize {
|
||||||
|
columns: 121,
|
||||||
|
rows: 20,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
};
|
||||||
|
let mut fake_input_output = get_fake_os_input(&fake_win_size);
|
||||||
|
fake_input_output.add_terminal_input(&[&QUIT]);
|
||||||
|
fake_input_output.add_sigwinch_event(PositionAndSize {
|
||||||
|
columns: 90,
|
||||||
|
rows: 10,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
});
|
||||||
|
let opts = CliArgs::default();
|
||||||
|
start(Box::new(fake_input_output.clone()), opts);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::tests::tty_inputs::{
|
use crate::tests::tty_inputs::{
|
||||||
COL_10, COL_121, COL_14, COL_15, COL_19, COL_20, COL_24, COL_25, COL_29, COL_30, COL_34,
|
COL_10, COL_121, COL_14, COL_141, COL_15, COL_19, COL_20, COL_24, COL_25, COL_29, COL_30,
|
||||||
COL_39, COL_4, COL_40, COL_47, COL_50, COL_60, COL_70, COL_8, COL_9, COL_90, COL_96,
|
COL_34, COL_39, COL_4, COL_40, COL_47, COL_50, COL_60, COL_70, COL_8, COL_80, COL_9, COL_90,
|
||||||
|
COL_96,
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
@ -69,9 +70,11 @@ pub fn get_possible_tty_inputs() -> HashMap<u16, Bytes> {
|
||||||
let col_50_bytes = Bytes::new().content_from_str(&COL_50);
|
let col_50_bytes = Bytes::new().content_from_str(&COL_50);
|
||||||
let col_60_bytes = Bytes::new().content_from_str(&COL_60);
|
let col_60_bytes = Bytes::new().content_from_str(&COL_60);
|
||||||
let col_70_bytes = Bytes::new().content_from_str(&COL_70);
|
let col_70_bytes = Bytes::new().content_from_str(&COL_70);
|
||||||
|
let col_80_bytes = Bytes::new().content_from_str(&COL_80);
|
||||||
let col_90_bytes = Bytes::new().content_from_str(&COL_90);
|
let col_90_bytes = Bytes::new().content_from_str(&COL_90);
|
||||||
let col_96_bytes = Bytes::new().content_from_str(&COL_96);
|
let col_96_bytes = Bytes::new().content_from_str(&COL_96);
|
||||||
let col_121_bytes = Bytes::new().content_from_str(&COL_121);
|
let col_121_bytes = Bytes::new().content_from_str(&COL_121);
|
||||||
|
let col_141_bytes = Bytes::new().content_from_str(&COL_141);
|
||||||
possible_inputs.insert(4, col_4_bytes);
|
possible_inputs.insert(4, col_4_bytes);
|
||||||
possible_inputs.insert(8, col_8_bytes);
|
possible_inputs.insert(8, col_8_bytes);
|
||||||
possible_inputs.insert(9, col_9_bytes);
|
possible_inputs.insert(9, col_9_bytes);
|
||||||
|
|
@ -91,8 +94,10 @@ pub fn get_possible_tty_inputs() -> HashMap<u16, Bytes> {
|
||||||
possible_inputs.insert(50, col_50_bytes);
|
possible_inputs.insert(50, col_50_bytes);
|
||||||
possible_inputs.insert(60, col_60_bytes);
|
possible_inputs.insert(60, col_60_bytes);
|
||||||
possible_inputs.insert(70, col_70_bytes);
|
possible_inputs.insert(70, col_70_bytes);
|
||||||
|
possible_inputs.insert(80, col_80_bytes);
|
||||||
possible_inputs.insert(90, col_90_bytes);
|
possible_inputs.insert(90, col_90_bytes);
|
||||||
possible_inputs.insert(96, col_96_bytes);
|
possible_inputs.insert(96, col_96_bytes);
|
||||||
possible_inputs.insert(121, col_121_bytes);
|
possible_inputs.insert(121, col_121_bytes);
|
||||||
|
possible_inputs.insert(141, col_141_bytes);
|
||||||
possible_inputs
|
possible_inputs
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,26 @@
|
||||||
|
pub const COL_141: [&str; 20] = [
|
||||||
|
"line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"line3-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"line4-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"line5-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"line6-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"line7-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"line8-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"line17-baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"line18-baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"line19-baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
"prompt $ ",
|
||||||
|
];
|
||||||
|
|
||||||
pub const COL_121: [&str; 20] = [
|
pub const COL_121: [&str; 20] = [
|
||||||
"line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
"line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
"line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
"line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
|
||||||
|
|
@ -457,6 +480,29 @@ pub const COL_70: [&str; 20] = [
|
||||||
"prompt $ ",
|
"prompt $ ",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub const COL_80: [&str; 20] = [
|
||||||
|
"line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
"prompt $ ",
|
||||||
|
];
|
||||||
|
|
||||||
pub const COL_90: [&str; 20] = [
|
pub const COL_90: [&str; 20] = [
|
||||||
"line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
"line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
"line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
"line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@ pub fn get_next_to_last_snapshot(mut snapshots: Vec<String>) -> Option<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod commands {
|
pub mod commands {
|
||||||
pub const COMMAND_TOGGLE: [u8; 1] = [7]; // ctrl-g
|
|
||||||
pub const QUIT: [u8; 1] = [17]; // ctrl-q
|
pub const QUIT: [u8; 1] = [17]; // ctrl-q
|
||||||
pub const ESC: [u8; 1] = [27];
|
pub const ESC: [u8; 1] = [27];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,18 +37,25 @@ pub enum Event {
|
||||||
pub enum InputMode {
|
pub enum InputMode {
|
||||||
/// In `Normal` mode, input is always written to the terminal, except for the shortcuts leading
|
/// In `Normal` mode, input is always written to the terminal, except for the shortcuts leading
|
||||||
/// to other modes
|
/// to other modes
|
||||||
|
#[serde(alias = "normal")]
|
||||||
Normal,
|
Normal,
|
||||||
/// In `Locked` mode, input is always written to the terminal and all shortcuts are disabled
|
/// In `Locked` mode, input is always written to the terminal and all shortcuts are disabled
|
||||||
/// except the one leading back to normal mode
|
/// except the one leading back to normal mode
|
||||||
|
#[serde(alias = "locked")]
|
||||||
Locked,
|
Locked,
|
||||||
/// `Resize` mode allows resizing the different existing panes.
|
/// `Resize` mode allows resizing the different existing panes.
|
||||||
|
#[serde(alias = "resize")]
|
||||||
Resize,
|
Resize,
|
||||||
/// `Pane` mode allows creating and closing panes, as well as moving between them.
|
/// `Pane` mode allows creating and closing panes, as well as moving between them.
|
||||||
|
#[serde(alias = "pane")]
|
||||||
Pane,
|
Pane,
|
||||||
/// `Tab` mode allows creating and closing tabs, as well as moving between them.
|
/// `Tab` mode allows creating and closing tabs, as well as moving between them.
|
||||||
|
#[serde(alias = "tab")]
|
||||||
Tab,
|
Tab,
|
||||||
/// `Scroll` mode allows scrolling up and down within a pane.
|
/// `Scroll` mode allows scrolling up and down within a pane.
|
||||||
|
#[serde(alias = "scroll")]
|
||||||
Scroll,
|
Scroll,
|
||||||
|
#[serde(alias = "renametab")]
|
||||||
RenameTab,
|
RenameTab,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue