wip: cleaning up a bit, need to map the colors to the right places in the palette

This commit is contained in:
denis 2021-04-09 14:14:50 +03:00
commit 748a7ffa6e
27 changed files with 1536 additions and 531 deletions

View file

@ -36,6 +36,44 @@ Zellij was initially called "Mosaic".
The status bar on the bottom should guide you through the possible keyboard shortcuts in the app. The status bar on the bottom should guide you through the possible keyboard shortcuts in the app.
# 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'

View file

@ -63,37 +63,51 @@ impl CtrlKeyShortcut {
} }
} }
fn get_bg(palette: Palette) -> (u8, u8, u8) {
match palette.theme {
Theme::Dark => palette.white,
Theme::Light => palette.fg,
}
}
fn get_fg(palette: Palette) -> (u8, u8, u8) {
match palette.theme {
Theme::Dark => palette.black,
Theme::Light => palette.bg,
}
}
fn unselected_mode_shortcut(letter: char, text: &str, palette: Palette) -> LinePart { fn unselected_mode_shortcut(letter: char, text: &str, palette: Palette) -> LinePart {
let prefix_separator = Style::new() let prefix_separator = Style::new()
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(get_fg(palette).0, get_fg(palette).1, get_fg(palette).2))
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
.paint(ARROW_SEPARATOR); .paint(ARROW_SEPARATOR);
let char_left_separator = Style::new() let char_left_separator = Style::new()
.bold() .bold()
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(get_fg(palette).0, get_fg(palette).1, get_fg(palette).2))
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
.bold() .bold()
.paint(" <"); .paint(" <");
let char_shortcut = Style::new() let char_shortcut = Style::new()
.bold() .bold()
.fg(RGB(palette.red.0, palette.red.1, palette.red.2)) .fg(RGB(palette.red.0, palette.red.1, palette.red.2))
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
.bold() .bold()
.paint(letter.to_string()); .paint(letter.to_string());
let char_right_separator = Style::new() let char_right_separator = Style::new()
.bold() .bold()
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(get_fg(palette).0, get_fg(palette).1, get_fg(palette).2))
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
.bold() .bold()
.paint(">"); .paint(">");
let styled_text = Style::new() let styled_text = Style::new()
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(get_fg(palette).0, get_fg(palette).1, get_fg(palette).2))
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
.bold() .bold()
.paint(format!("{} ", text)); .paint(format!("{} ", text));
let suffix_separator = Style::new() let suffix_separator = Style::new()
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(get_fg(palette).0, get_fg(palette).1, get_fg(palette).2))
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
.paint(ARROW_SEPARATOR); .paint(ARROW_SEPARATOR);
LinePart { LinePart {
part: ANSIStrings(&[ part: ANSIStrings(&[
@ -111,8 +125,8 @@ fn unselected_mode_shortcut(letter: char, text: &str, palette: Palette) -> LineP
fn selected_mode_shortcut(letter: char, text: &str, palette: Palette) -> LinePart { fn selected_mode_shortcut(letter: char, text: &str, palette: Palette) -> LinePart {
let prefix_separator = Style::new() let prefix_separator = Style::new()
.fg(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .fg(RGB(get_fg(palette).0, get_fg(palette).1, get_fg(palette).2))
.on(RGB(palette.green.0, palette.green.1, palette.green.2)) .on(RGB(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
.paint(ARROW_SEPARATOR); .paint(ARROW_SEPARATOR);
let char_left_separator = Style::new() let char_left_separator = Style::new()
.bold() .bold()
@ -133,12 +147,12 @@ fn selected_mode_shortcut(letter: char, text: &str, palette: Palette) -> LinePar
.bold() .bold()
.paint(format!(">")); .paint(format!(">"));
let styled_text = Style::new() let styled_text = Style::new()
.fg(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .fg(RGB(get_fg(palette).0, get_fg(palette).1, get_fg(palette).2))
.on(RGB(palette.green.0, palette.green.1, palette.green.2)) .on(RGB(palette.green.0, palette.green.1, palette.green.2))
.bold() .bold()
.paint(format!("{} ", text)); .paint(format!("{} ", text));
let suffix_separator = Style::new() let suffix_separator = Style::new()
.fg(RGB(palette.green.0, palette.green.1, palette.green.2)) .fg(RGB(get_fg(palette).0, get_fg(palette).1, get_fg(palette).2))
.on(RGB(palette.green.0, palette.green.1, palette.green.2)) .on(RGB(palette.green.0, palette.green.1, palette.green.2))
.paint(ARROW_SEPARATOR); .paint(ARROW_SEPARATOR);
LinePart { LinePart {
@ -157,17 +171,17 @@ fn selected_mode_shortcut(letter: char, text: &str, palette: Palette) -> LinePar
fn disabled_mode_shortcut(text: &str, palette: Palette) -> LinePart { fn disabled_mode_shortcut(text: &str, palette: Palette) -> LinePart {
let prefix_separator = Style::new() let prefix_separator = Style::new()
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(get_fg(palette).0, get_fg(palette).1, get_fg(palette).2))
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
.paint(ARROW_SEPARATOR); .paint(ARROW_SEPARATOR);
let styled_text = Style::new() let styled_text = Style::new()
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(get_fg(palette).0, get_fg(palette).1, get_fg(palette).2))
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
.dimmed() .dimmed()
.paint(format!("{} ", text)); .paint(format!("{} ", text));
let suffix_separator = Style::new() let suffix_separator = Style::new()
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(get_fg(palette).0, get_fg(palette).1, get_fg(palette).2))
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
.paint(ARROW_SEPARATOR); .paint(ARROW_SEPARATOR);
LinePart { LinePart {
part: format!("{}{}{}", prefix_separator, styled_text, suffix_separator), part: format!("{}{}{}", prefix_separator, styled_text, suffix_separator),
@ -179,7 +193,7 @@ fn selected_mode_shortcut_single_letter(letter: char, palette: Palette) -> LineP
let char_shortcut_text = format!(" {} ", letter); let char_shortcut_text = format!(" {} ", letter);
let len = char_shortcut_text.chars().count() + 4; // 2 for the arrows, 2 for the padding let len = char_shortcut_text.chars().count() + 4; // 2 for the arrows, 2 for the padding
let prefix_separator = Style::new() let prefix_separator = Style::new()
.fg(RGB(palette.black.0, palette.black.1, palette.black.2)) .fg(RGB(get_fg(palette).0, get_fg(palette).1, get_fg(palette).2))
.on(RGB(palette.green.0, palette.green.1, palette.green.2)) .on(RGB(palette.green.0, palette.green.1, palette.green.2))
.paint(ARROW_SEPARATOR); .paint(ARROW_SEPARATOR);
let char_shortcut = Style::new() let char_shortcut = Style::new()
@ -189,8 +203,8 @@ fn selected_mode_shortcut_single_letter(letter: char, palette: Palette) -> LineP
.bold() .bold()
.paint(char_shortcut_text); .paint(char_shortcut_text);
let suffix_separator = Style::new() let suffix_separator = Style::new()
.fg(RGB(palette.green.0, palette.green.1, palette.green.2)) .fg(RGB(get_fg(palette).0, get_fg(palette).1, get_fg(palette).2))
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
.paint(ARROW_SEPARATOR); .paint(ARROW_SEPARATOR);
LinePart { LinePart {
part: ANSIStrings(&[prefix_separator, char_shortcut, suffix_separator]).to_string(), part: ANSIStrings(&[prefix_separator, char_shortcut, suffix_separator]).to_string(),
@ -202,18 +216,18 @@ fn unselected_mode_shortcut_single_letter(letter: char, palette: Palette) -> Lin
let char_shortcut_text = format!(" {} ", letter); let char_shortcut_text = format!(" {} ", letter);
let len = char_shortcut_text.chars().count() + 4; // 2 for the arrows, 2 for the padding let len = char_shortcut_text.chars().count() + 4; // 2 for the arrows, 2 for the padding
let prefix_separator = Style::new() let prefix_separator = Style::new()
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(get_fg(palette).0, get_fg(palette).1, get_fg(palette).2))
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
.paint(ARROW_SEPARATOR); .paint(ARROW_SEPARATOR);
let char_shortcut = Style::new() let char_shortcut = Style::new()
.bold() .bold()
.fg(RGB(palette.red.0, palette.red.1, palette.red.2)) .fg(RGB(palette.red.0, palette.red.1, palette.red.2))
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
.bold() .bold()
.paint(char_shortcut_text); .paint(char_shortcut_text);
let suffix_separator = Style::new() let suffix_separator = Style::new()
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(get_fg(palette).0, get_fg(palette).1, get_fg(palette).2))
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
.paint(ARROW_SEPARATOR); .paint(ARROW_SEPARATOR);
LinePart { LinePart {
part: ANSIStrings(&[prefix_separator, char_shortcut, suffix_separator]).to_string(), part: ANSIStrings(&[prefix_separator, char_shortcut, suffix_separator]).to_string(),
@ -304,8 +318,8 @@ fn key_indicators(max_len: usize, keys: &[CtrlKeyShortcut], palette: Palette) ->
pub fn superkey(palette: Palette) -> LinePart { pub fn superkey(palette: Palette) -> LinePart {
let prefix_text = " Ctrl + "; let prefix_text = " Ctrl + ";
let prefix = Style::new() let prefix = Style::new()
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(get_fg(palette).0, get_fg(palette).1, get_fg(palette).2))
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
.bold() .bold()
.paint(prefix_text); .paint(prefix_text);
LinePart { LinePart {

View file

@ -2,7 +2,7 @@ mod first_line;
mod second_line; mod second_line;
use std::fmt::{Display, Error, Formatter}; use std::fmt::{Display, Error, Formatter};
use zellij_tile::{prelude::*, data::Theme}; use zellij_tile::{data::Theme, prelude::*};
use first_line::{ctrl_keys, superkey}; use first_line::{ctrl_keys, superkey};
use second_line::keybinds; use second_line::keybinds;
@ -62,30 +62,15 @@ impl ZellijTile for State {
let first_line = format!("{}{}", superkey, ctrl_keys); let first_line = format!("{}{}", superkey, ctrl_keys);
let second_line = keybinds(&self.mode_info, cols); let second_line = keybinds(&self.mode_info, cols);
let first_line_color = match self.mode_info.palette.theme {
Theme::Light => self.mode_info.palette.black,
Theme::Dark => self.mode_info.palette.white,
};
let second_line_color = match self.mode_info.palette.theme {
Theme::Light => self.mode_info.palette.bg,
Theme::Dark => self.mode_info.palette.bg,
};
// [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!( println!(
"{}\x1B[38;2;{};{};{}m\u{1b}[0K", "{}\u{1b}[48;2;{};{};{}m\u{1b}[0K",
first_line, first_line,
first_line_color.0, self.mode_info.palette.fg.0,
first_line_color.1, self.mode_info.palette.fg.1,
first_line_color.2 self.mode_info.palette.fg.2
);
println!(
"{}\u{1b}[{};{};{}m\u{1b}[0K",
second_line,
second_line_color.0,
second_line_color.1,
second_line_color.2
); );
println!("\u{1b}[m{}\u{1b}[0K", second_line);
} }
} }

View file

@ -1,6 +1,6 @@
// use colored::*; // use colored::*;
use ansi_term::{ANSIStrings, Color::RGB, Style}; use ansi_term::{ANSIStrings, Color::RGB, Style};
use zellij_tile::prelude::*; use zellij_tile::{data::Theme, prelude::*};
use crate::colors::{BLACK, GREEN, ORANGE, WHITE}; use crate::colors::{BLACK, GREEN, ORANGE, WHITE};
use crate::{LinePart, MORE_MSG}; use crate::{LinePart, MORE_MSG};
@ -13,13 +13,13 @@ fn full_length_shortcut(
) -> LinePart { ) -> LinePart {
let separator = if is_first_shortcut { " " } else { " / " }; let separator = if is_first_shortcut { " " } else { " / " };
let separator = Style::new() let separator = Style::new()
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(palette.black.0, palette.black.1, palette.black.2))
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
.paint(separator); .paint(separator);
let shortcut_len = letter.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space let shortcut_len = letter.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space
let shortcut_left_separator = Style::new() let shortcut_left_separator = Style::new()
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(palette.black.0, palette.black.1, palette.black.2))
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
.paint("<"); .paint("<");
let shortcut = Style::new() let shortcut = Style::new()
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
@ -27,13 +27,13 @@ fn full_length_shortcut(
.bold() .bold()
.paint(letter); .paint(letter);
let shortcut_right_separator = Style::new() let shortcut_right_separator = Style::new()
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(palette.black.0, palette.black.1, palette.black.2))
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
.paint("> "); .paint("> ");
let description_len = description.chars().count(); let description_len = description.chars().count();
let description = Style::new() let description = Style::new()
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(palette.black.0, palette.black.1, palette.black.2))
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
.bold() .bold()
.paint(description); .paint(description);
let len = shortcut_len + description_len + separator.chars().count(); let len = shortcut_len + description_len + separator.chars().count();
@ -60,13 +60,13 @@ fn first_word_shortcut(
) -> LinePart { ) -> LinePart {
let separator = if is_first_shortcut { " " } else { " / " }; let separator = if is_first_shortcut { " " } else { " / " };
let separator = Style::new() let separator = Style::new()
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(palette.black.0, palette.black.1, palette.black.2))
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
.paint(separator); .paint(separator);
let shortcut_len = letter.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space let shortcut_len = letter.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space
let shortcut_left_separator = Style::new() let shortcut_left_separator = Style::new()
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(palette.black.0, palette.black.1, palette.black.2))
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
.paint("<"); .paint("<");
let shortcut = Style::new() let shortcut = Style::new()
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
@ -74,14 +74,14 @@ fn first_word_shortcut(
.bold() .bold()
.paint(letter); .paint(letter);
let shortcut_right_separator = Style::new() let shortcut_right_separator = Style::new()
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(palette.black.0, palette.black.1, palette.black.2))
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
.paint("> "); .paint("> ");
let description_first_word = description.split(' ').next().unwrap_or(""); let description_first_word = description.split(' ').next().unwrap_or("");
let description_first_word_length = description_first_word.chars().count(); let description_first_word_length = description_first_word.chars().count();
let description_first_word = Style::new() let description_first_word = Style::new()
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(palette.black.0, palette.black.1, palette.black.2))
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
.bold() .bold()
.paint(description_first_word); .paint(description_first_word);
let len = shortcut_len + description_first_word_length + separator.chars().count(); let len = shortcut_len + description_first_word_length + separator.chars().count();
@ -99,13 +99,129 @@ fn first_word_shortcut(
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(palette: Palette) -> LinePart { fn locked_interface_indication(palette: Palette) -> LinePart {
let locked_text = " -- INTERFACE LOCKED -- "; let locked_text = " -- INTERFACE LOCKED -- ";
let locked_text_len = locked_text.chars().count(); let locked_text_len = locked_text.chars().count();
let locked_styled_text = Style::new() let locked_styled_text = Style::new()
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(palette.black.0, palette.black.1, palette.black.2))
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
.bold() .bold()
.paint(locked_text); .paint(locked_text);
LinePart { LinePart {
@ -119,13 +235,13 @@ fn select_pane_shortcut(is_first_shortcut: bool, palette: Palette) -> LinePart {
let description = "Select pane"; let description = "Select pane";
let separator = if is_first_shortcut { " " } else { " / " }; let separator = if is_first_shortcut { " " } else { " / " };
let separator = Style::new() let separator = Style::new()
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(palette.black.0, palette.black.1, palette.black.2))
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
.paint(separator); .paint(separator);
let shortcut_len = shortcut.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space let shortcut_len = shortcut.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space
let shortcut_left_separator = Style::new() let shortcut_left_separator = Style::new()
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(palette.black.0, palette.black.1, palette.black.2))
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
.paint("<"); .paint("<");
let shortcut = Style::new() let shortcut = Style::new()
.on(RGB(palette.black.0, palette.black.1, palette.black.2)) .on(RGB(palette.black.0, palette.black.1, palette.black.2))
@ -133,13 +249,13 @@ fn select_pane_shortcut(is_first_shortcut: bool, palette: Palette) -> LinePart {
.bold() .bold()
.paint(shortcut); .paint(shortcut);
let shortcut_right_separator = Style::new() let shortcut_right_separator = Style::new()
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(palette.black.0, palette.black.1, palette.black.2))
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
.paint("> "); .paint("> ");
let description_len = description.chars().count(); let description_len = description.chars().count();
let description = Style::new() let description = Style::new()
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2)) .on(RGB(palette.black.0, palette.black.1, palette.black.2))
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2)) .fg(RGB(palette.white.0, palette.white.1, palette.white.2))
.bold() .bold()
.paint(description); .paint(description);
let len = shortcut_len + description_len + separator.chars().count(); let len = shortcut_len + description_len + separator.chars().count();
@ -160,7 +276,7 @@ fn select_pane_shortcut(is_first_shortcut: bool, palette: Palette) -> 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(help.palette), InputMode::Locked => locked_interface_indication(help.palette),
_ => { _ => {
let mut line_part = LinePart::default(); let mut line_part = LinePart::default();
@ -179,7 +295,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(help.palette), InputMode::Locked => locked_interface_indication(help.palette),
_ => { _ => {
let mut line_part = LinePart::default(); let mut line_part = LinePart::default();
@ -198,7 +314,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(help.palette); let line_part = locked_interface_indication(help.palette);
if line_part.len <= max_len { if line_part.len <= max_len {
@ -218,7 +341,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(), help.palette); let select_pane_shortcut = select_pane_shortcut(help.keybinds.is_empty(), help.palette);
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

@ -1,5 +1,5 @@
use crate::common::colors;
use crate::tab::Pane; use crate::tab::Pane;
use crate::utils::shared::colors;
use ansi_term::Colour::RGB; use ansi_term::Colour::RGB;
use std::collections::HashMap; use std::collections::HashMap;
use zellij_tile::data::{InputMode, Palette}; use zellij_tile::data::{InputMode, Palette};
@ -19,6 +19,13 @@ pub mod boundary_type {
pub const CROSS: &str = ""; pub const CROSS: &str = "";
} }
// pub mod colors {
// use ansi_term::Colour::{self, Fixed};
// pub const GREEN: Colour = Fixed(154);
// 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
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]

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

@ -380,8 +380,7 @@ impl TerminalPane {
self.mark_for_rerender(); self.mark_for_rerender();
} }
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();
} }
@ -497,8 +496,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
@ -507,8 +505,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' {
@ -600,8 +597,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' {
@ -611,8 +607,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' {
@ -632,8 +627,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
@ -672,8 +666,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?

View file

@ -17,7 +17,7 @@ use std::{
collections::{BTreeMap, HashSet}, collections::{BTreeMap, HashSet},
}; };
use std::{io::Write, sync::mpsc::channel}; use std::{io::Write, sync::mpsc::channel};
use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, colors}; use zellij_tile::data::{colors, Event, InputMode, ModeInfo, Palette};
const CURSOR_HEIGHT_WIDTH_RATIO: usize = 4; // this is not accurate and kind of a magic number, TODO: look into this const CURSOR_HEIGHT_WIDTH_RATIO: usize = 4; // this is not accurate and kind of a magic number, TODO: look into this
const MIN_TERMINAL_HEIGHT: usize = 2; const MIN_TERMINAL_HEIGHT: usize = 2;
@ -71,7 +71,7 @@ pub struct Tab {
should_clear_display_before_rendering: bool, should_clear_display_before_rendering: bool,
pub mode_info: ModeInfo, pub mode_info: ModeInfo,
pub input_mode: InputMode, pub input_mode: InputMode,
pub colors: Palette pub colors: Palette,
} }
#[derive(Clone, Debug, Default, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Serialize, Deserialize)]
@ -568,6 +568,9 @@ impl Tab {
None None
} }
} }
pub fn has_terminal_pid(&self, pid: RawFd) -> bool {
self.panes.contains_key(&PaneId::Terminal(pid))
}
pub fn handle_pty_event(&mut self, pid: RawFd, event: VteEvent) { pub fn handle_pty_event(&mut self, pid: RawFd, event: VteEvent) {
// 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
@ -638,8 +641,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 {
@ -1804,6 +1816,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;
@ -2071,47 +2139,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

@ -178,7 +178,9 @@ pub enum ScreenContext {
ResizeRight, ResizeRight,
ResizeDown, ResizeDown,
ResizeUp, ResizeUp,
MoveFocus, SwitchFocus,
FocusNextPane,
FocusPreviousPane,
MoveFocusLeft, MoveFocusLeft,
MoveFocusDown, MoveFocusDown,
MoveFocusUp, MoveFocusUp,
@ -218,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,

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,8 +1,9 @@
//! 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::{load_palette, AppInstruction, SenderWithContext, OPENCALLS}; use crate::common::input::config::Config;
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;
use crate::pty_bus::PtyInstruction; use crate::pty_bus::PtyInstruction;
@ -10,22 +11,23 @@ use crate::screen::ScreenInstruction;
use crate::wasm_vm::PluginInstruction; use crate::wasm_vm::PluginInstruction;
use crate::CommandIsExecuting; use crate::CommandIsExecuting;
use crate::common::utils::shared::load_palette;
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 +35,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 +44,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,41 +62,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(_) => {
// Mouse and unsupported events aren't implemented yet, // Mouse events aren't implemented yet,
// use a NoOp untill then. // 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;
}
} }
} }
@ -146,9 +154,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) => {
@ -296,6 +314,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>,
@ -305,6 +324,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 colors_transform::{Color, Rgb}; use colors_transform::{Color, Rgb};
@ -33,7 +34,7 @@ use os_input_output::OsApi;
use pty_bus::{PtyBus, PtyInstruction}; use pty_bus::{PtyBus, PtyInstruction};
use screen::{Screen, ScreenInstruction}; use screen::{Screen, ScreenInstruction};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use utils::consts::ZELLIJ_IPC_PIPE; use utils::{consts::ZELLIJ_IPC_PIPE, shared::load_palette};
use wasm_vm::PluginEnv; use wasm_vm::PluginEnv;
use wasm_vm::{wasi_stdout, wasi_write_string, zellij_imports, PluginInstruction}; use wasm_vm::{wasi_stdout, wasi_write_string, zellij_imports, PluginInstruction};
use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value}; use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
@ -119,88 +120,6 @@ pub enum AppInstruction {
Error(String), Error(String),
} }
pub mod colors {
pub const WHITE: (u8, u8, u8) = (238, 238, 238);
pub const GREEN: (u8, u8, u8) = (175, 255, 0);
pub const GRAY: (u8, u8, u8) = (68, 68, 68);
pub const BRIGHT_GRAY: (u8, u8, u8) = (138, 138, 138);
pub const RED: (u8, u8, u8) = (135, 0, 0);
pub const BLACK: (u8, u8, u8) = (0, 0, 0);
}
pub fn detect_theme(bg: (u8, u8, u8)) -> Theme {
let (r, g, b) = bg;
// HSP, P stands for perceived brightness
let hsp: f64 = (0.299 * (r as f64 * r as f64)
+ 0.587 * (g as f64 * g as f64)
+ 0.114 * (b as f64 * b as f64))
.sqrt();
match hsp > 127.5 {
true => Theme::Light,
false => Theme::Dark,
}
}
pub fn load_palette() -> Palette {
let palette = match Colors::new("xresources") {
Some(colors) => {
let fg = colors.fg.unwrap();
let fg_imm = &fg;
let fg_hex: &str = &fg_imm;
let fg = Rgb::from_hex_str(fg_hex).unwrap().as_tuple();
let fg = (fg.0 as u8, fg.1 as u8, fg.2 as u8);
let bg = colors.bg.unwrap();
let bg_imm = &bg;
let bg_hex: &str = &bg_imm;
let bg = Rgb::from_hex_str(bg_hex).unwrap().as_tuple();
let bg = (bg.0 as u8, bg.1 as u8, bg.2 as u8);
let colors: Vec<(u8, u8, u8)> = colors
.colors
.iter()
.map(|c| {
let c = c.clone();
let imm_str = &c.unwrap();
let hex_str: &str = &imm_str;
let rgb = Rgb::from_hex_str(hex_str).unwrap().as_tuple();
(rgb.0 as u8, rgb.1 as u8, rgb.2 as u8)
})
.collect();
let theme = detect_theme(bg);
debug_log_to_file(format!(
"{:?} {:?}, white: {:?}, black: {:?}, fg: {:?}",
theme, bg, colors[7], colors[0], fg
));
Palette {
theme,
fg,
bg,
black: colors[0],
red: colors[1],
green: colors[2],
yellow: colors[3],
blue: colors[4],
magenta: colors[5],
cyan: colors[6],
white: colors[7],
}
}
None => Palette {
theme: Theme::Dark,
fg: colors::BRIGHT_GRAY,
bg: colors::BLACK,
black: colors::BLACK,
red: colors::RED,
green: colors::GREEN,
yellow: colors::GRAY,
blue: colors::GRAY,
magenta: colors::GRAY,
cyan: colors::GRAY,
white: colors::WHITE,
},
};
palette
}
/// Start Zellij with the specified [`OsApi`] and command-line arguments. /// Start Zellij with the specified [`OsApi`] and command-line arguments.
// FIXME this should definitely be modularized and split into different functions. // FIXME this should definitely be modularized and split into different functions.
pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) { pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
@ -211,6 +130,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);
@ -360,10 +286,22 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
screen.send_pty_instructions.update(err_ctx); screen.send_pty_instructions.update(err_ctx);
match event { match event {
ScreenInstruction::Pty(pid, vte_event) => { ScreenInstruction::Pty(pid, vte_event) => {
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_event(pid, vte_event);
} 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_event(pid, vte_event);
break;
}
}
}
} }
ScreenInstruction::Render => { ScreenInstruction::Render => {
screen.render(); screen.render();
@ -398,9 +336,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();
} }
@ -661,7 +605,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();
} }
} }
@ -682,9 +626,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

@ -242,7 +242,13 @@ 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(())
} }

View file

@ -28,7 +28,9 @@ pub enum ScreenInstruction {
ResizeRight, ResizeRight,
ResizeDown, ResizeDown,
ResizeUp, ResizeUp,
MoveFocus, SwitchFocus,
FocusNextPane,
FocusPreviousPane,
MoveFocusLeft, MoveFocusLeft,
MoveFocusDown, MoveFocusDown,
MoveFocusUp, MoveFocusUp,

View file

@ -4,6 +4,10 @@ use std::{iter, str::from_utf8};
use strip_ansi_escapes::strip; use strip_ansi_escapes::strip;
use colors_transform::{Color, Rgb};
use xrdb::Colors;
use zellij_tile::data::{Palette, Theme};
fn ansi_len(s: &str) -> usize { fn ansi_len(s: &str) -> usize {
from_utf8(&strip(s.as_bytes()).unwrap()) from_utf8(&strip(s.as_bytes()).unwrap())
.unwrap() .unwrap()
@ -28,3 +32,72 @@ pub fn adjust_to_size(s: &str, rows: usize, columns: usize) -> String {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n\r") .join("\n\r")
} }
// Colors
pub mod colors {
pub const WHITE: (u8, u8, u8) = (238, 238, 238);
pub const GREEN: (u8, u8, u8) = (175, 255, 0);
pub const GRAY: (u8, u8, u8) = (68, 68, 68);
pub const BRIGHT_GRAY: (u8, u8, u8) = (138, 138, 138);
pub const RED: (u8, u8, u8) = (135, 0, 0);
pub const BLACK: (u8, u8, u8) = (0, 0, 0);
}
pub fn hex_to_rgb(hex: &Option<String>) -> (u8, u8, u8) {
let c = hex.clone();
let imm_str = &c.unwrap();
let hex_str: &str = &imm_str;
let rgb = Rgb::from_hex_str(hex_str).unwrap().as_tuple();
(rgb.0 as u8, rgb.1 as u8, rgb.2 as u8)
}
pub fn detect_theme(bg: (u8, u8, u8)) -> Theme {
let (r, g, b) = bg;
// HSP, P stands for perceived brightness
let hsp: f64 = (0.299 * (r as f64 * r as f64)
+ 0.587 * (g as f64 * g as f64)
+ 0.114 * (b as f64 * b as f64))
.sqrt();
match hsp > 127.5 {
true => Theme::Light,
false => Theme::Dark,
}
}
pub fn load_palette() -> Palette {
let palette = match Colors::new("xresources") {
Some(palette) => {
let fg = hex_to_rgb(&palette.fg);
let bg = hex_to_rgb(&palette.bg);
let colors: Vec<(u8, u8, u8)> = palette.colors.iter().map(|c| hex_to_rgb(c)).collect();
let theme = detect_theme(bg);
Palette {
theme,
fg,
bg,
black: colors[0],
red: colors[1],
green: colors[2],
yellow: colors[3],
blue: colors[4],
magenta: colors[5],
cyan: colors[6],
white: colors[7],
}
}
None => Palette {
theme: Theme::Dark,
fg: colors::BRIGHT_GRAY,
bg: colors::BLACK,
black: colors::BLACK,
red: colors::RED,
green: colors::GREEN,
yellow: colors::GRAY,
blue: colors::GRAY,
magenta: colors::GRAY,
cyan: colors::GRAY,
white: colors::WHITE,
},
};
palette
}

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,
} }
@ -61,7 +68,7 @@ impl Default for InputMode {
#[derive(Clone, Copy, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum Theme { pub enum Theme {
Light, Light,
Dark Dark,
} }
pub mod colors { pub mod colors {
pub const WHITE: (u8, u8, u8) = (238, 238, 238); pub const WHITE: (u8, u8, u8) = (238, 238, 238);