merge(main): update branch with changes from main

This commit is contained in:
Brooks J Rady 2021-04-13 16:17:40 +01:00
commit 0e73227fe2
49 changed files with 2420 additions and 554 deletions

14
Cargo.lock generated
View file

@ -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",

View file

@ -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"

View file

@ -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>

View file

@ -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.

View file

@ -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 "$@"

View file

@ -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
} }

View file

@ -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.

View file

@ -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
} }
} }

View file

@ -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);
} }
} }

View file

@ -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 {

View file

@ -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
View 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',]

View file

@ -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,
},
}

View file

@ -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,
}; };

View file

@ -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
View 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, &current_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, &current_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, &current_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, &current_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;
}
}
}
}

View file

@ -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!()
} }

View file

@ -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));

View file

@ -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(&params, &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(&params[..], bell_terminated);
}
VteEvent::CsiDispatch(params, intermediates, ignore, c) => {
self.csi_dispatch(&params, &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)) {
self.grid.move_cursor_up_with_scrolling(1); (b'M', None) => {
self.grid.move_cursor_up_with_scrolling(1);
}
(b'c', None) => {
self.reset_terminal_state();
}
_ => {}
} }
} }
} }

View file

@ -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();
let active_terminal = self.panes.get_mut(&active_pane_id).unwrap(); if self.panes_to_hide.is_empty() {
active_terminal.override_size_and_position(expand_to.x, expand_to.y, &expand_to); // 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();
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), }
), for (kind, pane) in self.panes.iter_mut() {
false => boundaries.add_rect(terminal.as_ref(), self.mode_info.mode, None), 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) = terminal.render() { 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);
}
} }
self.render(); 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;
}
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();
// 1 for the border 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
}
self.panes.remove(&id);
if self.active_terminal == Some(id) {
self.active_terminal = self.next_active_pane(panes);
}
return;
} }
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 {
} }
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); self.panes.remove(&id);
if self.active_terminal.is_none() {
self.active_terminal = self.next_active_pane(self.get_pane_ids());
}
} }
} }
pub fn close_focused_pane(&mut self) { pub fn close_focused_pane(&mut self) {

View file

@ -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;

View file

@ -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,
} }
} }

View file

@ -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
View 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);
}
}

View file

@ -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 {
let stdin_buffer = self.os_input.read_from_stdin(); if self.should_exit {
for key_result in stdin_buffer.events_and_raw() { break;
match key_result { }
Ok((event, raw_bytes)) => match event { let stdin_buffer = self.os_input.read_from_stdin();
termion::event::Event::Key(key) => { for key_result in stdin_buffer.events_and_raw() {
let key = cast_termion_key(key); match key_result {
// FIXME this explicit break is needed because the current test Ok((event, raw_bytes)) => match event {
// framework relies on it to not create dead threads that loop termion::event::Event::Key(key) => {
// and eat up CPUs. Do not remove until the test framework has let key = cast_termion_key(key);
// been revised. Sorry about this (@categorille) self.handle_key(&key, raw_bytes, &keybinds);
let mut should_break = false; }
for action in key_to_actions(&key, raw_bytes, &self.mode, &keybinds) termion::event::Event::Unsupported(unsupported_key) => {
{ // we have to do this because of a bug in termion
should_break |= self.dispatch_action(action); // this should be a key event and not an unsupported event
} if unsupported_key == alt_left_bracket {
if should_break { let key = Key::Alt('[');
break 'input_loop; self.handle_key(&key, raw_bytes, &keybinds);
}
} }
termion::event::Event::Mouse(_) }
| termion::event::Event::Unsupported(_) => { termion::event::Event::Mouse(_) => {
unimplemented!("Mouse and unsupported events aren't supported!"); // Mouse events aren't implemented yet,
} // use a NoOp untill then.
}, }
Err(err) => panic!("Encountered read error: {:?}", err), },
} Err(err) => panic!("Encountered read error: {:?}", err),
} }
} }
} else { }
//@@@ Error handling? }
self.exit(); fn handle_key(&mut self, key: &Key, raw_bytes: Vec<u8>, keybinds: &Keybinds) {
for action in Keybinds::key_to_actions(&key, raw_bytes, &self.mode, &keybinds) {
let should_exit = self.dispatch_action(action);
if should_exit {
self.should_exit = true;
}
} }
} }
@ -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,

View file

@ -1,268 +1,415 @@
//! 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>>);
let mut defaults = Keybinds::new();
for mode in InputMode::iter() { /// Intermediate struct used for deserialisation
defaults.insert(mode, get_defaults_for_mode(&mode)); #[derive(Clone, Debug, PartialEq, Deserialize)]
} pub struct KeyActionFromYaml {
action: Vec<Action>,
Ok(defaults) key: Vec<Key>,
} }
/// Returns the default keybinds for a givent [`InputMode`]. impl Default for Keybinds {
fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds { fn default() -> Keybinds {
let mut defaults = ModeKeybinds::new(); let mut defaults = Keybinds::new();
match *mode { for mode in InputMode::iter() {
InputMode::Normal => { defaults
defaults.insert( .0
Key::Ctrl('g'), .insert(mode, Keybinds::get_defaults_for_mode(&mode));
vec![Action::SwitchToMode(InputMode::Locked)],
);
defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
defaults.insert(
Key::Ctrl('r'),
vec![Action::SwitchToMode(InputMode::Resize)],
);
defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
defaults.insert(
Key::Ctrl('s'),
vec![Action::SwitchToMode(InputMode::Scroll)],
);
defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
} }
InputMode::Locked => { defaults
defaults.insert( }
Key::Ctrl('g'), }
vec![Action::SwitchToMode(InputMode::Normal)],
); impl Keybinds {
pub fn new() -> Keybinds {
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
} }
InputMode::Resize => { }
defaults.insert(
Key::Ctrl('g'),
vec![Action::SwitchToMode(InputMode::Locked)],
);
defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
defaults.insert(
Key::Ctrl('r'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
defaults.insert(
Key::Ctrl('s'),
vec![Action::SwitchToMode(InputMode::Scroll)],
);
defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]);
defaults.insert(
Key::Char('\n'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(
Key::Char(' '),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(Key::Char('h'), vec![Action::Resize(Direction::Left)]); /// Merges two Keybinds structs into one Keybinds struct
defaults.insert(Key::Char('j'), vec![Action::Resize(Direction::Down)]); /// `other` overrides the ModeKeybinds of `self`.
defaults.insert(Key::Char('k'), vec![Action::Resize(Direction::Up)]); fn merge_keybinds(&self, other: Keybinds) -> Keybinds {
defaults.insert(Key::Char('l'), vec![Action::Resize(Direction::Right)]); let mut keybinds = Keybinds::new();
defaults.insert(Key::Left, vec![Action::Resize(Direction::Left)]); for mode in InputMode::iter() {
defaults.insert(Key::Down, vec![Action::Resize(Direction::Down)]); let mut mode_keybinds = ModeKeybinds::new();
defaults.insert(Key::Up, vec![Action::Resize(Direction::Up)]); if let Some(keybind) = self.0.get(&mode) {
defaults.insert(Key::Right, vec![Action::Resize(Direction::Right)]); mode_keybinds.0.extend(keybind.0.clone());
} };
InputMode::Pane => { if let Some(keybind) = other.0.get(&mode) {
defaults.insert( mode_keybinds.0.extend(keybind.0.clone());
Key::Ctrl('g'), }
vec![Action::SwitchToMode(InputMode::Locked)], if !mode_keybinds.0.is_empty() {
); keybinds.0.insert(mode, mode_keybinds);
defaults.insert(
Key::Ctrl('p'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(
Key::Ctrl('r'),
vec![Action::SwitchToMode(InputMode::Resize)],
);
defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
defaults.insert(
Key::Ctrl('s'),
vec![Action::SwitchToMode(InputMode::Scroll)],
);
defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]);
defaults.insert(
Key::Char('\n'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(
Key::Char(' '),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(Key::Char('h'), vec![Action::MoveFocus(Direction::Left)]);
defaults.insert(Key::Char('j'), vec![Action::MoveFocus(Direction::Down)]);
defaults.insert(Key::Char('k'), vec![Action::MoveFocus(Direction::Up)]);
defaults.insert(Key::Char('l'), vec![Action::MoveFocus(Direction::Right)]);
defaults.insert(Key::Left, vec![Action::MoveFocus(Direction::Left)]);
defaults.insert(Key::Down, vec![Action::MoveFocus(Direction::Down)]);
defaults.insert(Key::Up, vec![Action::MoveFocus(Direction::Up)]);
defaults.insert(Key::Right, vec![Action::MoveFocus(Direction::Right)]);
defaults.insert(Key::Char('p'), vec![Action::SwitchFocus(Direction::Right)]);
defaults.insert(Key::Char('n'), vec![Action::NewPane(None)]);
defaults.insert(Key::Char('d'), vec![Action::NewPane(Some(Direction::Down))]);
defaults.insert(
Key::Char('r'),
vec![Action::NewPane(Some(Direction::Right))],
);
defaults.insert(Key::Char('x'), vec![Action::CloseFocus]);
defaults.insert(Key::Char('f'), vec![Action::ToggleFocusFullscreen]);
}
InputMode::Tab => {
defaults.insert(
Key::Ctrl('g'),
vec![Action::SwitchToMode(InputMode::Locked)],
);
defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
defaults.insert(
Key::Ctrl('r'),
vec![Action::SwitchToMode(InputMode::Resize)],
);
defaults.insert(
Key::Ctrl('t'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(
Key::Ctrl('s'),
vec![Action::SwitchToMode(InputMode::Scroll)],
);
defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]);
defaults.insert(
Key::Char('\n'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(
Key::Char(' '),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(Key::Char('h'), vec![Action::GoToPreviousTab]);
defaults.insert(Key::Char('j'), vec![Action::GoToNextTab]);
defaults.insert(Key::Char('k'), vec![Action::GoToPreviousTab]);
defaults.insert(Key::Char('l'), vec![Action::GoToNextTab]);
defaults.insert(Key::Left, vec![Action::GoToPreviousTab]);
defaults.insert(Key::Down, vec![Action::GoToNextTab]);
defaults.insert(Key::Up, vec![Action::GoToPreviousTab]);
defaults.insert(Key::Right, vec![Action::GoToNextTab]);
defaults.insert(Key::Char('n'), vec![Action::NewTab]);
defaults.insert(Key::Char('x'), vec![Action::CloseTab]);
defaults.insert(
Key::Char('r'),
vec![
Action::SwitchToMode(InputMode::RenameTab),
Action::TabNameInput(vec![0]),
],
);
defaults.insert(Key::Char('q'), vec![Action::Quit]);
defaults.insert(
Key::Ctrl('g'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
for i in '1'..='9' {
defaults.insert(Key::Char(i), vec![Action::GoToTab(i.to_digit(10).unwrap())]);
} }
} }
InputMode::Scroll => {
defaults.insert(
Key::Ctrl('g'),
vec![Action::SwitchToMode(InputMode::Locked)],
);
defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
defaults.insert(
Key::Ctrl('r'),
vec![Action::SwitchToMode(InputMode::Resize)],
);
defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
defaults.insert(
Key::Ctrl('s'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]);
defaults.insert(
Key::Char('\n'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(
Key::Char(' '),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(Key::Char('j'), vec![Action::ScrollDown]);
defaults.insert(Key::Char('k'), vec![Action::ScrollUp]);
defaults.insert(Key::Down, vec![Action::ScrollDown]);
defaults.insert(Key::Up, vec![Action::ScrollUp]);
}
InputMode::RenameTab => {
defaults.insert(Key::Char('\n'), vec![Action::SwitchToMode(InputMode::Tab)]);
defaults.insert(
Key::Ctrl('g'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(
Key::Esc,
vec![
Action::TabNameInput(vec![0x1b]),
Action::SwitchToMode(InputMode::Tab),
],
);
}
}
defaults
}
/// 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,
input: Vec<u8>,
mode: &InputMode,
keybinds: &Keybinds,
) -> Vec<Action> {
let mode_keybind_or_action = |action: Action| {
keybinds keybinds
.get(mode) }
.unwrap_or_else(|| unreachable!("Unrecognized mode: {:?}", mode))
.get(key) /// Returns the default keybinds for a given [`InputMode`].
.cloned() fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds {
.unwrap_or_else(|| vec![action]) let mut defaults = HashMap::new();
};
match *mode { match *mode {
InputMode::Normal | InputMode::Locked => mode_keybind_or_action(Action::Write(input)), InputMode::Normal => {
InputMode::RenameTab => mode_keybind_or_action(Action::TabNameInput(input)), defaults.insert(
_ => mode_keybind_or_action(Action::NoOp), Key::Ctrl('g'),
vec![Action::SwitchToMode(InputMode::Locked)],
);
defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
defaults.insert(
Key::Ctrl('r'),
vec![Action::SwitchToMode(InputMode::Resize)],
);
defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
defaults.insert(
Key::Ctrl('s'),
vec![Action::SwitchToMode(InputMode::Scroll)],
);
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 => {
defaults.insert(
Key::Ctrl('g'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
}
InputMode::Resize => {
defaults.insert(
Key::Ctrl('g'),
vec![Action::SwitchToMode(InputMode::Locked)],
);
defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
defaults.insert(
Key::Ctrl('r'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
defaults.insert(
Key::Ctrl('s'),
vec![Action::SwitchToMode(InputMode::Scroll)],
);
defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]);
defaults.insert(
Key::Char('\n'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(
Key::Char(' '),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(Key::Char('h'), vec![Action::Resize(Direction::Left)]);
defaults.insert(Key::Char('j'), vec![Action::Resize(Direction::Down)]);
defaults.insert(Key::Char('k'), vec![Action::Resize(Direction::Up)]);
defaults.insert(Key::Char('l'), vec![Action::Resize(Direction::Right)]);
defaults.insert(Key::Left, vec![Action::Resize(Direction::Left)]);
defaults.insert(Key::Down, vec![Action::Resize(Direction::Down)]);
defaults.insert(Key::Up, vec![Action::Resize(Direction::Up)]);
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 => {
defaults.insert(
Key::Ctrl('g'),
vec![Action::SwitchToMode(InputMode::Locked)],
);
defaults.insert(
Key::Ctrl('p'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(
Key::Ctrl('r'),
vec![Action::SwitchToMode(InputMode::Resize)],
);
defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
defaults.insert(
Key::Ctrl('s'),
vec![Action::SwitchToMode(InputMode::Scroll)],
);
defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]);
defaults.insert(
Key::Char('\n'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(
Key::Char(' '),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(Key::Char('h'), vec![Action::MoveFocus(Direction::Left)]);
defaults.insert(Key::Char('j'), vec![Action::MoveFocus(Direction::Down)]);
defaults.insert(Key::Char('k'), vec![Action::MoveFocus(Direction::Up)]);
defaults.insert(Key::Char('l'), vec![Action::MoveFocus(Direction::Right)]);
defaults.insert(Key::Left, vec![Action::MoveFocus(Direction::Left)]);
defaults.insert(Key::Down, vec![Action::MoveFocus(Direction::Down)]);
defaults.insert(Key::Up, vec![Action::MoveFocus(Direction::Up)]);
defaults.insert(Key::Right, vec![Action::MoveFocus(Direction::Right)]);
defaults.insert(Key::Char('p'), vec![Action::SwitchFocus]);
defaults.insert(Key::Char('n'), vec![Action::NewPane(None)]);
defaults.insert(Key::Char('d'), vec![Action::NewPane(Some(Direction::Down))]);
defaults.insert(
Key::Char('r'),
vec![Action::NewPane(Some(Direction::Right))],
);
defaults.insert(Key::Char('x'), vec![Action::CloseFocus]);
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 => {
defaults.insert(
Key::Ctrl('g'),
vec![Action::SwitchToMode(InputMode::Locked)],
);
defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
defaults.insert(
Key::Ctrl('r'),
vec![Action::SwitchToMode(InputMode::Resize)],
);
defaults.insert(
Key::Ctrl('t'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(
Key::Ctrl('s'),
vec![Action::SwitchToMode(InputMode::Scroll)],
);
defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]);
defaults.insert(
Key::Char('\n'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(
Key::Char(' '),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(Key::Char('h'), vec![Action::GoToPreviousTab]);
defaults.insert(Key::Char('j'), vec![Action::GoToNextTab]);
defaults.insert(Key::Char('k'), vec![Action::GoToPreviousTab]);
defaults.insert(Key::Char('l'), vec![Action::GoToNextTab]);
defaults.insert(Key::Left, vec![Action::GoToPreviousTab]);
defaults.insert(Key::Down, vec![Action::GoToNextTab]);
defaults.insert(Key::Up, vec![Action::GoToPreviousTab]);
defaults.insert(Key::Right, vec![Action::GoToNextTab]);
defaults.insert(Key::Char('n'), vec![Action::NewTab]);
defaults.insert(Key::Char('x'), vec![Action::CloseTab]);
defaults.insert(
Key::Char('r'),
vec![
Action::SwitchToMode(InputMode::RenameTab),
Action::TabNameInput(vec![0]),
],
);
defaults.insert(Key::Char('q'), vec![Action::Quit]);
defaults.insert(
Key::Ctrl('g'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
for i in '1'..='9' {
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 => {
defaults.insert(
Key::Ctrl('g'),
vec![Action::SwitchToMode(InputMode::Locked)],
);
defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
defaults.insert(
Key::Ctrl('r'),
vec![Action::SwitchToMode(InputMode::Resize)],
);
defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
defaults.insert(
Key::Ctrl('s'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]);
defaults.insert(
Key::Char('\n'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(
Key::Char(' '),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(Key::Char('j'), vec![Action::ScrollDown]);
defaults.insert(Key::Char('k'), vec![Action::ScrollUp]);
defaults.insert(Key::Down, vec![Action::ScrollDown]);
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 => {
defaults.insert(Key::Char('\n'), vec![Action::SwitchToMode(InputMode::Tab)]);
defaults.insert(
Key::Ctrl('g'),
vec![Action::SwitchToMode(InputMode::Normal)],
);
defaults.insert(
Key::Esc,
vec![
Action::TabNameInput(vec![0x1b]),
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)
}
/// 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,
input: Vec<u8>,
mode: &InputMode,
keybinds: &Keybinds,
) -> Vec<Action> {
let mode_keybind_or_action = |action: Action| {
keybinds
.0
.get(mode)
.unwrap_or_else(|| unreachable!("Unrecognized mode: {:?}", mode))
.0
.get(key)
.cloned()
.unwrap_or_else(|| vec![action])
};
match *mode {
InputMode::Normal | InputMode::Locked => mode_keybind_or_action(Action::Write(input)),
InputMode::RenameTab => mode_keybind_or_action(Action::TabNameInput(input)),
_ => 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;

View file

@ -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;

View 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'), ],
//};
//}

View file

@ -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,

View file

@ -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,10 +84,15 @@ 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!
child.kill().unwrap(); #[allow(clippy::single_match)]
child.wait().unwrap(); match signal {
break 'handle_exit; SIGINT => {
child.kill().unwrap();
child.wait().unwrap();
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> {

View file

@ -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 { for byte in bytes.iter() {
debug_to_file(byte, pid).unwrap(); 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

View file

@ -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() {

View file

@ -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<_>>()

View file

@ -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::*,

View file

@ -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();
}
}
} }

View file

@ -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,
}; };

View file

@ -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;

View file

@ -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

View file

@ -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 $ █

View file

@ -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 $ █

View file

@ -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 $

View 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);
}

View file

@ -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
} }

View file

@ -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",

View file

@ -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];

View file

@ -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,
} }