mirror of
https://github.com/zellij-org/zellij.git
synced 2024-12-29 20:21:39 +03:00
wip: cleaning up a bit, need to map the colors to the right places in the palette
This commit is contained in:
commit
748a7ffa6e
38
README.md
38
README.md
@ -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.
|
||||
|
||||
# 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?
|
||||
|
||||
Zellij is in the last stages of being VT compatible. As much as modern terminals are.
|
||||
|
@ -30,16 +30,93 @@ _zellij() {
|
||||
'--help[Prints help information]' \
|
||||
'-V[Prints version information]' \
|
||||
'--version[Prints version information]' \
|
||||
":: :_zellij_commands" \
|
||||
"*::: :->zellij" \
|
||||
&& 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] )) ||
|
||||
_zellij_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 "$@"
|
||||
}
|
||||
(( $+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 "$@"
|
@ -13,6 +13,15 @@ _zellij() {
|
||||
cmd="zellij"
|
||||
;;
|
||||
|
||||
c)
|
||||
cmd+="__c"
|
||||
;;
|
||||
config)
|
||||
cmd+="__config"
|
||||
;;
|
||||
help)
|
||||
cmd+="__help"
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
@ -20,7 +29,7 @@ _zellij() {
|
||||
|
||||
case "${cmd}" in
|
||||
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
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
@ -63,6 +72,51 @@ _zellij() {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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 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" -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'
|
||||
|
@ -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 {
|
||||
let prefix_separator = Style::new()
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(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(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
|
||||
.paint(ARROW_SEPARATOR);
|
||||
let char_left_separator = Style::new()
|
||||
.bold()
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(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(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
|
||||
.bold()
|
||||
.paint(" <");
|
||||
let char_shortcut = Style::new()
|
||||
.bold()
|
||||
.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()
|
||||
.paint(letter.to_string());
|
||||
let char_right_separator = Style::new()
|
||||
.bold()
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(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(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
|
||||
.bold()
|
||||
.paint(">");
|
||||
let styled_text = Style::new()
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(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(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
|
||||
.bold()
|
||||
.paint(format!("{} ", text));
|
||||
let suffix_separator = Style::new()
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(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(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
|
||||
.paint(ARROW_SEPARATOR);
|
||||
LinePart {
|
||||
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 {
|
||||
let prefix_separator = Style::new()
|
||||
.fg(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
|
||||
.on(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(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
|
||||
.paint(ARROW_SEPARATOR);
|
||||
let char_left_separator = Style::new()
|
||||
.bold()
|
||||
@ -133,12 +147,12 @@ fn selected_mode_shortcut(letter: char, text: &str, palette: Palette) -> LinePar
|
||||
.bold()
|
||||
.paint(format!(">"));
|
||||
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))
|
||||
.bold()
|
||||
.paint(format!("{} ", text));
|
||||
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))
|
||||
.paint(ARROW_SEPARATOR);
|
||||
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 {
|
||||
let prefix_separator = Style::new()
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(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(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
|
||||
.paint(ARROW_SEPARATOR);
|
||||
let styled_text = Style::new()
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(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(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
|
||||
.dimmed()
|
||||
.paint(format!("{} ", text));
|
||||
let suffix_separator = Style::new()
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(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(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
|
||||
.paint(ARROW_SEPARATOR);
|
||||
LinePart {
|
||||
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 len = char_shortcut_text.chars().count() + 4; // 2 for the arrows, 2 for the padding
|
||||
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))
|
||||
.paint(ARROW_SEPARATOR);
|
||||
let char_shortcut = Style::new()
|
||||
@ -189,8 +203,8 @@ fn selected_mode_shortcut_single_letter(letter: char, palette: Palette) -> LineP
|
||||
.bold()
|
||||
.paint(char_shortcut_text);
|
||||
let suffix_separator = Style::new()
|
||||
.fg(RGB(palette.green.0, palette.green.1, palette.green.2))
|
||||
.on(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(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
|
||||
.paint(ARROW_SEPARATOR);
|
||||
LinePart {
|
||||
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 len = char_shortcut_text.chars().count() + 4; // 2 for the arrows, 2 for the padding
|
||||
let prefix_separator = Style::new()
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(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(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
|
||||
.paint(ARROW_SEPARATOR);
|
||||
let char_shortcut = Style::new()
|
||||
.bold()
|
||||
.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()
|
||||
.paint(char_shortcut_text);
|
||||
let suffix_separator = Style::new()
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(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(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
|
||||
.paint(ARROW_SEPARATOR);
|
||||
LinePart {
|
||||
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 {
|
||||
let prefix_text = " Ctrl + ";
|
||||
let prefix = Style::new()
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(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(get_bg(palette).0, get_bg(palette).1, get_bg(palette).2))
|
||||
.bold()
|
||||
.paint(prefix_text);
|
||||
LinePart {
|
||||
|
@ -2,7 +2,7 @@ mod first_line;
|
||||
mod second_line;
|
||||
|
||||
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 second_line::keybinds;
|
||||
@ -62,30 +62,15 @@ impl ZellijTile for State {
|
||||
let first_line = format!("{}{}", superkey, ctrl_keys);
|
||||
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;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!(
|
||||
"{}\x1B[38;2;{};{};{}m\u{1b}[0K",
|
||||
"{}\u{1b}[48;2;{};{};{}m\u{1b}[0K",
|
||||
first_line,
|
||||
first_line_color.0,
|
||||
first_line_color.1,
|
||||
first_line_color.2
|
||||
);
|
||||
println!(
|
||||
"{}\u{1b}[{};{};{}m\u{1b}[0K",
|
||||
second_line,
|
||||
second_line_color.0,
|
||||
second_line_color.1,
|
||||
second_line_color.2
|
||||
self.mode_info.palette.fg.0,
|
||||
self.mode_info.palette.fg.1,
|
||||
self.mode_info.palette.fg.2
|
||||
);
|
||||
println!("\u{1b}[m{}\u{1b}[0K", second_line);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
// use colored::*;
|
||||
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::{LinePart, MORE_MSG};
|
||||
@ -13,13 +13,13 @@ fn full_length_shortcut(
|
||||
) -> LinePart {
|
||||
let separator = if is_first_shortcut { " " } else { " / " };
|
||||
let separator = Style::new()
|
||||
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(RGB(palette.black.0, palette.black.1, palette.black.2))
|
||||
.fg(RGB(palette.white.0, palette.white.1, palette.white.2))
|
||||
.paint(separator);
|
||||
let shortcut_len = letter.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space
|
||||
let shortcut_left_separator = Style::new()
|
||||
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(RGB(palette.black.0, palette.black.1, palette.black.2))
|
||||
.fg(RGB(palette.white.0, palette.white.1, palette.white.2))
|
||||
.paint("<");
|
||||
let shortcut = Style::new()
|
||||
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
|
||||
@ -27,13 +27,13 @@ fn full_length_shortcut(
|
||||
.bold()
|
||||
.paint(letter);
|
||||
let shortcut_right_separator = Style::new()
|
||||
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(RGB(palette.black.0, palette.black.1, palette.black.2))
|
||||
.fg(RGB(palette.white.0, palette.white.1, palette.white.2))
|
||||
.paint("> ");
|
||||
let description_len = description.chars().count();
|
||||
let description = Style::new()
|
||||
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(RGB(palette.black.0, palette.black.1, palette.black.2))
|
||||
.fg(RGB(palette.white.0, palette.white.1, palette.white.2))
|
||||
.bold()
|
||||
.paint(description);
|
||||
let len = shortcut_len + description_len + separator.chars().count();
|
||||
@ -60,13 +60,13 @@ fn first_word_shortcut(
|
||||
) -> LinePart {
|
||||
let separator = if is_first_shortcut { " " } else { " / " };
|
||||
let separator = Style::new()
|
||||
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(RGB(palette.black.0, palette.black.1, palette.black.2))
|
||||
.fg(RGB(palette.white.0, palette.white.1, palette.white.2))
|
||||
.paint(separator);
|
||||
let shortcut_len = letter.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space
|
||||
let shortcut_left_separator = Style::new()
|
||||
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(RGB(palette.black.0, palette.black.1, palette.black.2))
|
||||
.fg(RGB(palette.white.0, palette.white.1, palette.white.2))
|
||||
.paint("<");
|
||||
let shortcut = Style::new()
|
||||
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
|
||||
@ -74,14 +74,14 @@ fn first_word_shortcut(
|
||||
.bold()
|
||||
.paint(letter);
|
||||
let shortcut_right_separator = Style::new()
|
||||
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(RGB(palette.black.0, palette.black.1, palette.black.2))
|
||||
.fg(RGB(palette.white.0, palette.white.1, palette.white.2))
|
||||
.paint("> ");
|
||||
let description_first_word = description.split(' ').next().unwrap_or("");
|
||||
let description_first_word_length = description_first_word.chars().count();
|
||||
let description_first_word = Style::new()
|
||||
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(RGB(palette.black.0, palette.black.1, palette.black.2))
|
||||
.fg(RGB(palette.white.0, palette.white.1, palette.white.2))
|
||||
.bold()
|
||||
.paint(description_first_word);
|
||||
let len = shortcut_len + description_first_word_length + separator.chars().count();
|
||||
@ -99,13 +99,129 @@ fn first_word_shortcut(
|
||||
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 {
|
||||
let locked_text = " -- INTERFACE LOCKED -- ";
|
||||
let locked_text_len = locked_text.chars().count();
|
||||
let locked_styled_text = Style::new()
|
||||
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(RGB(palette.black.0, palette.black.1, palette.black.2))
|
||||
.fg(RGB(palette.white.0, palette.white.1, palette.white.2))
|
||||
.bold()
|
||||
.paint(locked_text);
|
||||
LinePart {
|
||||
@ -119,13 +235,13 @@ fn select_pane_shortcut(is_first_shortcut: bool, palette: Palette) -> LinePart {
|
||||
let description = "Select pane";
|
||||
let separator = if is_first_shortcut { " " } else { " / " };
|
||||
let separator = Style::new()
|
||||
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(RGB(palette.black.0, palette.black.1, palette.black.2))
|
||||
.fg(RGB(palette.white.0, palette.white.1, palette.white.2))
|
||||
.paint(separator);
|
||||
let shortcut_len = shortcut.chars().count() + 3; // 2 for <>'s around shortcut, 1 for the space
|
||||
let shortcut_left_separator = Style::new()
|
||||
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(RGB(palette.black.0, palette.black.1, palette.black.2))
|
||||
.fg(RGB(palette.white.0, palette.white.1, palette.white.2))
|
||||
.paint("<");
|
||||
let shortcut = Style::new()
|
||||
.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()
|
||||
.paint(shortcut);
|
||||
let shortcut_right_separator = Style::new()
|
||||
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(RGB(palette.black.0, palette.black.1, palette.black.2))
|
||||
.fg(RGB(palette.white.0, palette.white.1, palette.white.2))
|
||||
.paint("> ");
|
||||
let description_len = description.chars().count();
|
||||
let description = Style::new()
|
||||
.on(RGB(palette.bg.0, palette.bg.1, palette.bg.2))
|
||||
.fg(RGB(palette.fg.0, palette.fg.1, palette.fg.2))
|
||||
.on(RGB(palette.black.0, palette.black.1, palette.black.2))
|
||||
.fg(RGB(palette.white.0, palette.white.1, palette.white.2))
|
||||
.bold()
|
||||
.paint(description);
|
||||
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 {
|
||||
match help.mode {
|
||||
InputMode::Normal => LinePart::default(),
|
||||
InputMode::Normal => quicknav_full(),
|
||||
InputMode::Locked => locked_interface_indication(help.palette),
|
||||
_ => {
|
||||
let mut line_part = LinePart::default();
|
||||
@ -179,7 +295,7 @@ fn full_shortcut_list(help: &ModeInfo) -> LinePart {
|
||||
|
||||
fn shortened_shortcut_list(help: &ModeInfo) -> LinePart {
|
||||
match help.mode {
|
||||
InputMode::Normal => LinePart::default(),
|
||||
InputMode::Normal => quicknav_medium(),
|
||||
InputMode::Locked => locked_interface_indication(help.palette),
|
||||
_ => {
|
||||
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 {
|
||||
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 => {
|
||||
let line_part = locked_interface_indication(help.palette);
|
||||
if line_part.len <= max_len {
|
||||
@ -218,7 +341,7 @@ fn best_effort_shortcut_list(help: &ModeInfo, max_len: usize) -> LinePart {
|
||||
break;
|
||||
}
|
||||
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);
|
||||
if line_part.len + select_pane_shortcut.len <= max_len {
|
||||
|
@ -23,7 +23,7 @@ impl ZellijTile for State {
|
||||
let next = self.selected().saturating_add(1);
|
||||
*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() {
|
||||
FsEntry::Dir(p, _) => {
|
||||
self.path = p;
|
||||
|
26
example/config.yaml
Normal file
26
example/config.yaml
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
keybinds:
|
||||
normal:
|
||||
- action: [GoToTab: 1,]
|
||||
key: [F: 1,]
|
||||
- action: [GoToTab: 2,]
|
||||
key: [F: 2,]
|
||||
- action: [GoToTab: 3,]
|
||||
key: [F: 3,]
|
||||
- action: [GoToTab: 4,]
|
||||
key: [F: 4,]
|
||||
- action: [NewTab,]
|
||||
key: [F: 5,]
|
||||
- action: [MoveFocus: Left,]
|
||||
key: [ Alt: h,]
|
||||
- action: [MoveFocus: Right,]
|
||||
key: [ Alt: l,]
|
||||
- action: [MoveFocus: Down,]
|
||||
key: [ Alt: j,]
|
||||
- action: [MoveFocus: Up,]
|
||||
key: [ Alt: k,]
|
||||
pane:
|
||||
- action: [ NewPane:, SwitchToMode: Normal,]
|
||||
key: [Char: 'n',]
|
||||
- action: [ NewPane: , ]
|
||||
key: [Char: 'N',]
|
17
src/cli.rs
17
src/cli.rs
@ -1,7 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[derive(StructOpt, Debug, Default)]
|
||||
#[derive(StructOpt, Default, Debug)]
|
||||
#[structopt(name = "zellij")]
|
||||
pub struct CliArgs {
|
||||
/// Send "split (direction h == horizontal / v == vertical)" to active zellij session
|
||||
@ -24,6 +24,21 @@ pub struct CliArgs {
|
||||
#[structopt(short, long)]
|
||||
pub layout: Option<PathBuf>,
|
||||
|
||||
#[structopt(subcommand)]
|
||||
pub config: Option<ConfigCli>,
|
||||
|
||||
#[structopt(short, long)]
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::common::colors;
|
||||
use crate::tab::Pane;
|
||||
use crate::utils::shared::colors;
|
||||
use ansi_term::Colour::RGB;
|
||||
use std::collections::HashMap;
|
||||
use zellij_tile::data::{InputMode, Palette};
|
||||
@ -19,6 +19,13 @@ pub mod boundary_type {
|
||||
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
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
@ -37,6 +37,14 @@ pub enum NamedColor {
|
||||
Magenta,
|
||||
Cyan,
|
||||
White,
|
||||
BrightBlack,
|
||||
BrightRed,
|
||||
BrightGreen,
|
||||
BrightYellow,
|
||||
BrightBlue,
|
||||
BrightMagenta,
|
||||
BrightCyan,
|
||||
BrightWhite,
|
||||
}
|
||||
|
||||
impl NamedColor {
|
||||
@ -50,6 +58,14 @@ impl NamedColor {
|
||||
NamedColor::Magenta => format!("{}", 35),
|
||||
NamedColor::Cyan => format!("{}", 36),
|
||||
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 {
|
||||
@ -62,6 +78,14 @@ impl NamedColor {
|
||||
NamedColor::Magenta => format!("{}", 45),
|
||||
NamedColor::Cyan => format!("{}", 46),
|
||||
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?
|
||||
}
|
||||
[49, ..] => *self = self.background(Some(AnsiCode::Reset)),
|
||||
[90, ..] => {
|
||||
*self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightBlack)))
|
||||
}
|
||||
[91, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightRed))),
|
||||
[92, ..] => {
|
||||
*self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightGreen)))
|
||||
}
|
||||
[93, ..] => {
|
||||
*self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightYellow)))
|
||||
}
|
||||
[94, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightBlue))),
|
||||
[95, ..] => {
|
||||
*self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightMagenta)))
|
||||
}
|
||||
[96, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightCyan))),
|
||||
[97, ..] => {
|
||||
*self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightWhite)))
|
||||
}
|
||||
[100, ..] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightBlack)))
|
||||
}
|
||||
[101, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightRed))),
|
||||
[102, ..] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightGreen)))
|
||||
}
|
||||
[103, ..] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightYellow)))
|
||||
}
|
||||
[104, ..] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightBlue)))
|
||||
}
|
||||
[105, ..] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightMagenta)))
|
||||
}
|
||||
[106, ..] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightCyan)))
|
||||
}
|
||||
[107, ..] => {
|
||||
*self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightWhite)))
|
||||
}
|
||||
_ => {
|
||||
// if this happens, it's a bug
|
||||
let _ = debug_log_to_file(format!("unhandled csi m code {:?}", ansi_params));
|
||||
|
@ -380,8 +380,7 @@ impl TerminalPane {
|
||||
self.mark_for_rerender();
|
||||
}
|
||||
fn add_newline(&mut self) {
|
||||
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
pad_character.styles = self.pending_styles;
|
||||
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
self.grid.add_canonical_line(pad_character);
|
||||
self.mark_for_rerender();
|
||||
}
|
||||
@ -497,8 +496,7 @@ impl vte::Perform for TerminalPane {
|
||||
} else {
|
||||
(params[0] as usize - 1, params[1] as usize - 1)
|
||||
};
|
||||
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
pad_character.styles = self.pending_styles;
|
||||
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
self.grid.move_cursor_to(col, row, pad_character);
|
||||
} else if c == 'A' {
|
||||
// move cursor up until edge of screen
|
||||
@ -507,8 +505,7 @@ impl vte::Perform for TerminalPane {
|
||||
} else if c == 'B' {
|
||||
// move cursor down until edge of screen
|
||||
let move_down_count = if params[0] == 0 { 1 } else { params[0] };
|
||||
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
pad_character.styles = self.pending_styles;
|
||||
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
self.grid
|
||||
.move_cursor_down(move_down_count as usize, pad_character);
|
||||
} else if c == 'D' {
|
||||
@ -600,8 +597,7 @@ impl vte::Perform for TerminalPane {
|
||||
} else {
|
||||
params[0] as usize
|
||||
};
|
||||
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
pad_character.styles = self.pending_styles;
|
||||
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
self.grid
|
||||
.delete_lines_in_scroll_region(line_count_to_delete, pad_character);
|
||||
} else if c == 'L' {
|
||||
@ -611,8 +607,7 @@ impl vte::Perform for TerminalPane {
|
||||
} else {
|
||||
params[0] as usize
|
||||
};
|
||||
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
pad_character.styles = self.pending_styles;
|
||||
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
self.grid
|
||||
.add_empty_lines_in_scroll_region(line_count_to_add, pad_character);
|
||||
} else if c == 'q' {
|
||||
@ -632,8 +627,7 @@ impl vte::Perform for TerminalPane {
|
||||
// minus 1 because this is 1 indexed
|
||||
params[0] as usize - 1
|
||||
};
|
||||
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
pad_character.styles = self.pending_styles;
|
||||
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
self.grid.move_cursor_to_line(line, pad_character);
|
||||
} else if c == 'P' {
|
||||
// erase characters
|
||||
@ -672,8 +666,7 @@ impl vte::Perform for TerminalPane {
|
||||
} else {
|
||||
params[0] as usize
|
||||
};
|
||||
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
pad_character.styles = self.pending_styles;
|
||||
let pad_character = EMPTY_TERMINAL_CHARACTER;
|
||||
self.grid
|
||||
.delete_lines_in_scroll_region(count, pad_character);
|
||||
// TODO: since delete_lines_in_scroll_region also adds lines, is the below redundant?
|
||||
|
@ -17,7 +17,7 @@ use std::{
|
||||
collections::{BTreeMap, HashSet},
|
||||
};
|
||||
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 MIN_TERMINAL_HEIGHT: usize = 2;
|
||||
@ -71,7 +71,7 @@ pub struct Tab {
|
||||
should_clear_display_before_rendering: bool,
|
||||
pub mode_info: ModeInfo,
|
||||
pub input_mode: InputMode,
|
||||
pub colors: Palette
|
||||
pub colors: Palette,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
@ -568,6 +568,9 @@ impl Tab {
|
||||
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) {
|
||||
// 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
|
||||
@ -638,8 +641,17 @@ impl Tab {
|
||||
}
|
||||
});
|
||||
self.panes_to_hide = pane_ids_to_hide.collect();
|
||||
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);
|
||||
if self.panes_to_hide.is_empty() {
|
||||
// nothing to do, pane is already as fullscreen as it can be, let's bail
|
||||
return;
|
||||
} else {
|
||||
let active_terminal = self.panes.get_mut(&active_pane_id).unwrap();
|
||||
active_terminal.override_size_and_position(
|
||||
expand_to.x,
|
||||
expand_to.y,
|
||||
&expand_to,
|
||||
);
|
||||
}
|
||||
}
|
||||
let active_terminal = self.panes.get(&active_pane_id).unwrap();
|
||||
if let PaneId::Terminal(active_pid) = active_pane_id {
|
||||
@ -1804,6 +1816,62 @@ impl Tab {
|
||||
}
|
||||
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) {
|
||||
if !self.has_selectable_panes() {
|
||||
return;
|
||||
@ -2071,47 +2139,79 @@ impl Tab {
|
||||
}
|
||||
}
|
||||
pub fn close_pane_without_rerender(&mut self, id: PaneId) {
|
||||
if let Some(terminal_to_close) = self.panes.get(&id) {
|
||||
let terminal_to_close_width = terminal_to_close.columns();
|
||||
let terminal_to_close_height = terminal_to_close.rows();
|
||||
if let Some(terminals) = self.panes_to_the_left_between_aligning_borders(id) {
|
||||
for terminal_id in terminals.iter() {
|
||||
self.increase_pane_width_right(&terminal_id, terminal_to_close_width + 1);
|
||||
// 1 for the border
|
||||
if self.fullscreen_is_active {
|
||||
self.toggle_active_pane_fullscreen();
|
||||
}
|
||||
if let Some(pane_to_close) = self.panes.get(&id) {
|
||||
let pane_to_close_width = pane_to_close.columns();
|
||||
let pane_to_close_height = pane_to_close.rows();
|
||||
if let Some(panes) = self.panes_to_the_left_between_aligning_borders(id) {
|
||||
if panes.iter().all(|p| {
|
||||
let pane = self.panes.get(p).unwrap();
|
||||
pane.can_increase_width_by(pane_to_close_width + 1)
|
||||
}) {
|
||||
for pane_id in panes.iter() {
|
||||
self.increase_pane_width_right(&pane_id, pane_to_close_width + 1);
|
||||
// 1 for the border
|
||||
}
|
||||
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);
|
||||
if self.active_terminal.is_none() {
|
||||
self.active_terminal = self.next_active_pane(self.get_pane_ids());
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn close_focused_pane(&mut self) {
|
||||
|
@ -23,7 +23,7 @@ impl CommandIsExecuting {
|
||||
let (lock, cvar) = &*self.closing_pane;
|
||||
let mut closing_pane = lock.lock().unwrap();
|
||||
*closing_pane = false;
|
||||
cvar.notify_one();
|
||||
cvar.notify_all();
|
||||
}
|
||||
pub fn opening_new_pane(&mut self) {
|
||||
let (lock, _cvar) = &*self.opening_new_pane;
|
||||
@ -34,7 +34,7 @@ impl CommandIsExecuting {
|
||||
let (lock, cvar) = &*self.opening_new_pane;
|
||||
let mut opening_new_pane = lock.lock().unwrap();
|
||||
*opening_new_pane = false;
|
||||
cvar.notify_one();
|
||||
cvar.notify_all();
|
||||
}
|
||||
pub fn wait_until_pane_is_closed(&self) {
|
||||
let (lock, cvar) = &*self.closing_pane;
|
||||
|
@ -178,7 +178,9 @@ pub enum ScreenContext {
|
||||
ResizeRight,
|
||||
ResizeDown,
|
||||
ResizeUp,
|
||||
MoveFocus,
|
||||
SwitchFocus,
|
||||
FocusNextPane,
|
||||
FocusPreviousPane,
|
||||
MoveFocusLeft,
|
||||
MoveFocusDown,
|
||||
MoveFocusUp,
|
||||
@ -218,7 +220,9 @@ impl From<&ScreenInstruction> for ScreenContext {
|
||||
ScreenInstruction::ResizeRight => ScreenContext::ResizeRight,
|
||||
ScreenInstruction::ResizeDown => ScreenContext::ResizeDown,
|
||||
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::MoveFocusDown => ScreenContext::MoveFocusDown,
|
||||
ScreenInstruction::MoveFocusUp => ScreenContext::MoveFocusUp,
|
||||
|
@ -1,9 +1,10 @@
|
||||
//! Definition of the actions that can be bound to keys.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zellij_tile::data::InputMode;
|
||||
|
||||
/// The four directions (left, right, up, down).
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub enum Direction {
|
||||
Left,
|
||||
Right,
|
||||
@ -12,7 +13,7 @@ pub enum Direction {
|
||||
}
|
||||
|
||||
/// Actions that can be bound to keys.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub enum Action {
|
||||
/// Quit Zellij.
|
||||
Quit,
|
||||
@ -23,8 +24,10 @@ pub enum Action {
|
||||
/// Resize focus pane in specified direction.
|
||||
Resize(Direction),
|
||||
/// Switch focus to next pane in specified direction.
|
||||
SwitchFocus(Direction),
|
||||
FocusNextPane,
|
||||
FocusPreviousPane,
|
||||
/// Move the focus pane in specified direction.
|
||||
SwitchFocus,
|
||||
MoveFocus(Direction),
|
||||
/// Scroll up in focus pane.
|
||||
ScrollUp,
|
||||
|
158
src/common/input/config.rs
Normal file
158
src/common/input/config.rs
Normal file
@ -0,0 +1,158 @@
|
||||
//! Deserializes configuration options.
|
||||
use std::error;
|
||||
use std::fmt::{self, Display};
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use super::keybinds::{Keybinds, KeybindsFromYaml};
|
||||
use crate::cli::ConfigCli;
|
||||
|
||||
use directories_next::ProjectDirs;
|
||||
use serde::Deserialize;
|
||||
|
||||
type ConfigResult = Result<Config, ConfigError>;
|
||||
|
||||
/// Intermediate deserialisation config struct
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ConfigFromYaml {
|
||||
pub keybinds: Option<KeybindsFromYaml>,
|
||||
}
|
||||
|
||||
/// Main configuration.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Config {
|
||||
pub keybinds: Keybinds,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ConfigError {
|
||||
// Deserialisation error
|
||||
Serde(serde_yaml::Error),
|
||||
// Io error
|
||||
Io(io::Error),
|
||||
// Io error with path context
|
||||
IoPath(io::Error, PathBuf),
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
let keybinds = Keybinds::default();
|
||||
Config { keybinds }
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Uses defaults, but lets config override them.
|
||||
pub fn from_yaml(yaml_config: &str) -> ConfigResult {
|
||||
let config_from_yaml: ConfigFromYaml = serde_yaml::from_str(&yaml_config)?;
|
||||
let keybinds = Keybinds::get_default_keybinds_with_config(config_from_yaml.keybinds);
|
||||
Ok(Config { keybinds })
|
||||
}
|
||||
|
||||
/// Deserializes from given path.
|
||||
#[allow(unused_must_use)]
|
||||
pub fn new(path: &Path) -> ConfigResult {
|
||||
match File::open(path) {
|
||||
Ok(mut file) => {
|
||||
let mut yaml_config = String::new();
|
||||
file.read_to_string(&mut yaml_config)
|
||||
.map_err(|e| ConfigError::IoPath(e, path.to_path_buf()))?;
|
||||
Ok(Config::from_yaml(&yaml_config)?)
|
||||
}
|
||||
Err(e) => Err(ConfigError::IoPath(e, path.into())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserializes the config from a default platform specific path,
|
||||
/// merges the default configuration - options take precedence.
|
||||
fn from_default_path() -> ConfigResult {
|
||||
let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap();
|
||||
let mut config_path: PathBuf = project_dirs.config_dir().to_owned();
|
||||
config_path.push("config.yaml");
|
||||
|
||||
match Config::new(&config_path) {
|
||||
Ok(config) => Ok(config),
|
||||
Err(ConfigError::IoPath(_, _)) => Ok(Config::default()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Entry point of the configuration
|
||||
#[cfg(not(test))]
|
||||
pub fn from_cli_config(cli_config: Option<ConfigCli>) -> ConfigResult {
|
||||
match cli_config {
|
||||
Some(ConfigCli::Config { clean, .. }) if clean => Ok(Config::default()),
|
||||
Some(ConfigCli::Config { path, .. }) if path.is_some() => {
|
||||
Ok(Config::new(&path.unwrap())?)
|
||||
}
|
||||
Some(_) | None => Ok(Config::from_default_path()?),
|
||||
}
|
||||
}
|
||||
|
||||
//#[allow(unused_must_use)]
|
||||
/// In order not to mess up tests from changing configurations
|
||||
#[cfg(test)]
|
||||
pub fn from_cli_config(_: Option<ConfigCli>) -> ConfigResult {
|
||||
Ok(Config::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ConfigError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ConfigError::Io(ref err) => write!(formatter, "IoError: {}", err),
|
||||
ConfigError::IoPath(ref err, ref path) => {
|
||||
write!(formatter, "IoError: {}, File: {}", err, path.display(),)
|
||||
}
|
||||
ConfigError::Serde(ref err) => write!(formatter, "Deserialisation error: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ConfigError {
|
||||
fn cause(&self) -> Option<&dyn error::Error> {
|
||||
match *self {
|
||||
ConfigError::Io(ref err) => Some(err),
|
||||
ConfigError::IoPath(ref err, _) => Some(err),
|
||||
ConfigError::Serde(ref err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for ConfigError {
|
||||
fn from(err: io::Error) -> ConfigError {
|
||||
ConfigError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_yaml::Error> for ConfigError {
|
||||
fn from(err: serde_yaml::Error) -> ConfigError {
|
||||
ConfigError::Serde(err)
|
||||
}
|
||||
}
|
||||
|
||||
// The unit test location.
|
||||
#[cfg(test)]
|
||||
mod config_test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn clean_option_equals_default_config() {
|
||||
let no_file = PathBuf::from(r"../fixtures/config/config.yamlll");
|
||||
let cli_config = ConfigCli::Config {
|
||||
path: Some(no_file),
|
||||
clean: true,
|
||||
};
|
||||
let config = Config::from_cli_config(Some(cli_config)).unwrap();
|
||||
let default = Config::default();
|
||||
assert_eq!(config, default);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_config_option_file_equals_default_config() {
|
||||
let config = Config::from_cli_config(None).unwrap();
|
||||
let default = Config::default();
|
||||
assert_eq!(config, default);
|
||||
}
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
//! Main input logic.
|
||||
|
||||
use super::actions::Action;
|
||||
use super::keybinds::get_default_keybinds;
|
||||
use crate::common::{load_palette, AppInstruction, SenderWithContext, OPENCALLS};
|
||||
use super::keybinds::Keybinds;
|
||||
use crate::common::input::config::Config;
|
||||
use crate::common::{AppInstruction, SenderWithContext, OPENCALLS};
|
||||
use crate::errors::ContextType;
|
||||
use crate::os_input_output::OsApi;
|
||||
use crate::pty_bus::PtyInstruction;
|
||||
@ -10,22 +11,23 @@ use crate::screen::ScreenInstruction;
|
||||
use crate::wasm_vm::PluginInstruction;
|
||||
use crate::CommandIsExecuting;
|
||||
|
||||
use crate::common::utils::shared::load_palette;
|
||||
use termion::input::{TermRead, TermReadEventsAndRaw};
|
||||
use zellij_tile::data::{Event, InputMode, Key, ModeInfo};
|
||||
|
||||
use super::keybinds::key_to_actions;
|
||||
|
||||
/// Handles the dispatching of [`Action`]s according to the current
|
||||
/// [`InputMode`], and keep tracks of the current [`InputMode`].
|
||||
struct InputHandler {
|
||||
/// The current input mode
|
||||
mode: InputMode,
|
||||
os_input: Box<dyn OsApi>,
|
||||
config: Config,
|
||||
command_is_executing: CommandIsExecuting,
|
||||
send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
send_app_instructions: SenderWithContext<AppInstruction>,
|
||||
should_exit: bool,
|
||||
}
|
||||
|
||||
impl InputHandler {
|
||||
@ -33,6 +35,7 @@ impl InputHandler {
|
||||
fn new(
|
||||
os_input: Box<dyn OsApi>,
|
||||
command_is_executing: CommandIsExecuting,
|
||||
config: Config,
|
||||
send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
@ -41,11 +44,13 @@ impl InputHandler {
|
||||
InputHandler {
|
||||
mode: InputMode::Normal,
|
||||
os_input,
|
||||
config,
|
||||
command_is_executing,
|
||||
send_screen_instructions,
|
||||
send_pty_instructions,
|
||||
send_plugin_instructions,
|
||||
send_app_instructions,
|
||||
should_exit: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,41 +62,44 @@ impl InputHandler {
|
||||
self.send_pty_instructions.update(err_ctx);
|
||||
self.send_app_instructions.update(err_ctx);
|
||||
self.send_screen_instructions.update(err_ctx);
|
||||
if let Ok(keybinds) = get_default_keybinds() {
|
||||
'input_loop: loop {
|
||||
//@@@ I think this should actually just iterate over stdin directly
|
||||
let stdin_buffer = self.os_input.read_from_stdin();
|
||||
for key_result in stdin_buffer.events_and_raw() {
|
||||
match key_result {
|
||||
Ok((event, raw_bytes)) => match event {
|
||||
termion::event::Event::Key(key) => {
|
||||
let key = cast_termion_key(key);
|
||||
// FIXME this explicit break is needed because the current test
|
||||
// framework relies on it to not create dead threads that loop
|
||||
// and eat up CPUs. Do not remove until the test framework has
|
||||
// been revised. Sorry about this (@categorille)
|
||||
let mut should_break = false;
|
||||
for action in key_to_actions(&key, raw_bytes, &self.mode, &keybinds)
|
||||
{
|
||||
should_break |= self.dispatch_action(action);
|
||||
}
|
||||
if should_break {
|
||||
break 'input_loop;
|
||||
}
|
||||
let keybinds = self.config.keybinds.clone();
|
||||
let alt_left_bracket = vec![27, 91];
|
||||
loop {
|
||||
if self.should_exit {
|
||||
break;
|
||||
}
|
||||
let stdin_buffer = self.os_input.read_from_stdin();
|
||||
for key_result in stdin_buffer.events_and_raw() {
|
||||
match key_result {
|
||||
Ok((event, raw_bytes)) => match event {
|
||||
termion::event::Event::Key(key) => {
|
||||
let key = cast_termion_key(key);
|
||||
self.handle_key(&key, raw_bytes, &keybinds);
|
||||
}
|
||||
termion::event::Event::Unsupported(unsupported_key) => {
|
||||
// we have to do this because of a bug in termion
|
||||
// this should be a key event and not an unsupported event
|
||||
if unsupported_key == alt_left_bracket {
|
||||
let key = Key::Alt('[');
|
||||
self.handle_key(&key, raw_bytes, &keybinds);
|
||||
}
|
||||
termion::event::Event::Mouse(_)
|
||||
| termion::event::Event::Unsupported(_) => {
|
||||
// Mouse and unsupported events aren't implemented yet,
|
||||
// use a NoOp untill then.
|
||||
}
|
||||
},
|
||||
Err(err) => panic!("Encountered read error: {:?}", err),
|
||||
}
|
||||
}
|
||||
termion::event::Event::Mouse(_) => {
|
||||
// Mouse events aren't implemented yet,
|
||||
// use a NoOp untill then.
|
||||
}
|
||||
},
|
||||
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();
|
||||
}
|
||||
Action::SwitchFocus(_) => {
|
||||
Action::SwitchFocus => {
|
||||
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();
|
||||
}
|
||||
Action::MoveFocus(direction) => {
|
||||
@ -296,6 +314,7 @@ pub fn get_mode_info(mode: InputMode) -> ModeInfo {
|
||||
/// its [`InputHandler::handle_input()`] loop.
|
||||
pub fn input_loop(
|
||||
os_input: Box<dyn OsApi>,
|
||||
config: Config,
|
||||
command_is_executing: CommandIsExecuting,
|
||||
send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
@ -305,6 +324,7 @@ pub fn input_loop(
|
||||
let _handler = InputHandler::new(
|
||||
os_input,
|
||||
command_is_executing,
|
||||
config,
|
||||
send_screen_instructions,
|
||||
send_pty_instructions,
|
||||
send_plugin_instructions,
|
||||
|
@ -1,268 +1,415 @@
|
||||
//! Mapping of inputs to sequences of actions.
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::actions::{Action, Direction};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
use strum::IntoEnumIterator;
|
||||
use zellij_tile::data::*;
|
||||
|
||||
type Keybinds = HashMap<InputMode, ModeKeybinds>;
|
||||
type ModeKeybinds = HashMap<Key, Vec<Action>>;
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Keybinds(HashMap<InputMode, ModeKeybinds>);
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct ModeKeybinds(HashMap<Key, Vec<Action>>);
|
||||
|
||||
/// Populates the default hashmap of keybinds.
|
||||
/// @@@khs26 What about an input config file?
|
||||
pub fn get_default_keybinds() -> Result<Keybinds, String> {
|
||||
let mut defaults = Keybinds::new();
|
||||
/// Intermediate struct used for deserialisation
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||
pub struct KeybindsFromYaml(HashMap<InputMode, Vec<KeyActionFromYaml>>);
|
||||
|
||||
for mode in InputMode::iter() {
|
||||
defaults.insert(mode, get_defaults_for_mode(&mode));
|
||||
}
|
||||
|
||||
Ok(defaults)
|
||||
/// Intermediate struct used for deserialisation
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||
pub struct KeyActionFromYaml {
|
||||
action: Vec<Action>,
|
||||
key: Vec<Key>,
|
||||
}
|
||||
|
||||
/// Returns the default keybinds for a givent [`InputMode`].
|
||||
fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds {
|
||||
let mut defaults = ModeKeybinds::new();
|
||||
impl Default for Keybinds {
|
||||
fn default() -> Keybinds {
|
||||
let mut defaults = Keybinds::new();
|
||||
|
||||
match *mode {
|
||||
InputMode::Normal => {
|
||||
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::Scroll)],
|
||||
);
|
||||
defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
|
||||
for mode in InputMode::iter() {
|
||||
defaults
|
||||
.0
|
||||
.insert(mode, Keybinds::get_defaults_for_mode(&mode));
|
||||
}
|
||||
InputMode::Locked => {
|
||||
defaults.insert(
|
||||
Key::Ctrl('g'),
|
||||
vec![Action::SwitchToMode(InputMode::Normal)],
|
||||
);
|
||||
defaults
|
||||
}
|
||||
}
|
||||
|
||||
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)]);
|
||||
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)]);
|
||||
/// Merges two Keybinds structs into one Keybinds struct
|
||||
/// `other` overrides the ModeKeybinds of `self`.
|
||||
fn merge_keybinds(&self, other: Keybinds) -> Keybinds {
|
||||
let mut keybinds = Keybinds::new();
|
||||
|
||||
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)]);
|
||||
}
|
||||
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(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())]);
|
||||
for mode in InputMode::iter() {
|
||||
let mut mode_keybinds = ModeKeybinds::new();
|
||||
if let Some(keybind) = self.0.get(&mode) {
|
||||
mode_keybinds.0.extend(keybind.0.clone());
|
||||
};
|
||||
if let Some(keybind) = other.0.get(&mode) {
|
||||
mode_keybinds.0.extend(keybind.0.clone());
|
||||
}
|
||||
if !mode_keybinds.0.is_empty() {
|
||||
keybinds.0.insert(mode, mode_keybinds);
|
||||
}
|
||||
}
|
||||
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
|
||||
.get(mode)
|
||||
.unwrap_or_else(|| unreachable!("Unrecognized mode: {:?}", mode))
|
||||
.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),
|
||||
}
|
||||
|
||||
/// Returns the default keybinds for a given [`InputMode`].
|
||||
fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds {
|
||||
let mut defaults = HashMap::new();
|
||||
|
||||
match *mode {
|
||||
InputMode::Normal => {
|
||||
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::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;
|
||||
|
@ -1,5 +1,6 @@
|
||||
//! The way terminal iput is handled.
|
||||
//! The way terminal input is handled.
|
||||
|
||||
pub mod actions;
|
||||
pub mod config;
|
||||
pub mod handler;
|
||||
pub mod keybinds;
|
||||
|
135
src/common/input/unit/keybinds_test.rs
Normal file
135
src/common/input/unit/keybinds_test.rs
Normal file
@ -0,0 +1,135 @@
|
||||
use super::super::actions::*;
|
||||
use super::super::keybinds::*;
|
||||
use zellij_tile::data::Key;
|
||||
|
||||
#[test]
|
||||
fn merge_keybinds_merges_different_keys() {
|
||||
let mut mode_keybinds_self = ModeKeybinds::new();
|
||||
mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]);
|
||||
let mut mode_keybinds_other = ModeKeybinds::new();
|
||||
mode_keybinds_other
|
||||
.0
|
||||
.insert(Key::Backspace, vec![Action::NoOp]);
|
||||
|
||||
let mut mode_keybinds_expected = ModeKeybinds::new();
|
||||
mode_keybinds_expected
|
||||
.0
|
||||
.insert(Key::F(1), vec![Action::NoOp]);
|
||||
mode_keybinds_expected
|
||||
.0
|
||||
.insert(Key::Backspace, vec![Action::NoOp]);
|
||||
|
||||
let mode_keybinds_merged = mode_keybinds_self.merge(mode_keybinds_other);
|
||||
|
||||
assert_eq!(mode_keybinds_expected, mode_keybinds_merged);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_mode_keybinds_overwrites_same_keys() {
|
||||
let mut mode_keybinds_self = ModeKeybinds::new();
|
||||
mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]);
|
||||
let mut mode_keybinds_other = ModeKeybinds::new();
|
||||
mode_keybinds_other
|
||||
.0
|
||||
.insert(Key::F(1), vec![Action::GoToTab(1)]);
|
||||
|
||||
let mut mode_keybinds_expected = ModeKeybinds::new();
|
||||
mode_keybinds_expected
|
||||
.0
|
||||
.insert(Key::F(1), vec![Action::GoToTab(1)]);
|
||||
|
||||
let mode_keybinds_merged = mode_keybinds_self.merge(mode_keybinds_other);
|
||||
|
||||
assert_eq!(mode_keybinds_expected, mode_keybinds_merged);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_keybinds_merges() {
|
||||
let mut mode_keybinds_self = ModeKeybinds::new();
|
||||
mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]);
|
||||
let mut mode_keybinds_other = ModeKeybinds::new();
|
||||
mode_keybinds_other
|
||||
.0
|
||||
.insert(Key::Backspace, vec![Action::NoOp]);
|
||||
let mut keybinds_self = Keybinds::new();
|
||||
keybinds_self
|
||||
.0
|
||||
.insert(InputMode::Normal, mode_keybinds_self.clone());
|
||||
let mut keybinds_other = Keybinds::new();
|
||||
keybinds_other
|
||||
.0
|
||||
.insert(InputMode::Resize, mode_keybinds_other.clone());
|
||||
let mut keybinds_expected = Keybinds::new();
|
||||
keybinds_expected
|
||||
.0
|
||||
.insert(InputMode::Normal, mode_keybinds_self);
|
||||
keybinds_expected
|
||||
.0
|
||||
.insert(InputMode::Resize, mode_keybinds_other);
|
||||
|
||||
assert_eq!(
|
||||
keybinds_expected,
|
||||
keybinds_self.merge_keybinds(keybinds_other)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_keybinds_overwrites_same_keys() {
|
||||
let mut mode_keybinds_self = ModeKeybinds::new();
|
||||
mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]);
|
||||
mode_keybinds_self.0.insert(Key::F(2), vec![Action::NoOp]);
|
||||
mode_keybinds_self.0.insert(Key::F(3), vec![Action::NoOp]);
|
||||
let mut mode_keybinds_other = ModeKeybinds::new();
|
||||
mode_keybinds_other
|
||||
.0
|
||||
.insert(Key::F(1), vec![Action::GoToTab(1)]);
|
||||
mode_keybinds_other
|
||||
.0
|
||||
.insert(Key::F(2), vec![Action::GoToTab(2)]);
|
||||
mode_keybinds_other
|
||||
.0
|
||||
.insert(Key::F(3), vec![Action::GoToTab(3)]);
|
||||
let mut keybinds_self = Keybinds::new();
|
||||
keybinds_self
|
||||
.0
|
||||
.insert(InputMode::Normal, mode_keybinds_self.clone());
|
||||
let mut keybinds_other = Keybinds::new();
|
||||
keybinds_other
|
||||
.0
|
||||
.insert(InputMode::Normal, mode_keybinds_other.clone());
|
||||
let mut keybinds_expected = Keybinds::new();
|
||||
keybinds_expected
|
||||
.0
|
||||
.insert(InputMode::Normal, mode_keybinds_other);
|
||||
|
||||
assert_eq!(
|
||||
keybinds_expected,
|
||||
keybinds_self.merge_keybinds(keybinds_other)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_keyaction_from_yaml_to_mode_keybindings() {
|
||||
let actions = vec![Action::NoOp, Action::GoToTab(1)];
|
||||
let keyaction = KeyActionFromYaml {
|
||||
action: actions.clone(),
|
||||
key: vec![Key::F(1), Key::Backspace, Key::Char('t')],
|
||||
};
|
||||
|
||||
let mut expected = ModeKeybinds::new();
|
||||
expected.0.insert(Key::F(1), actions.clone());
|
||||
expected.0.insert(Key::Backspace, actions.clone());
|
||||
expected.0.insert(Key::Char('t'), actions);
|
||||
|
||||
assert_eq!(expected, ModeKeybinds::from(keyaction));
|
||||
}
|
||||
|
||||
//#[test]
|
||||
//fn from_keybinds_from_yaml_to_keybinds(){
|
||||
//let mut keybinds_from_yaml = KeybindsFromYaml(HashMap<InputMode, Vec<KeyActionFromYaml>>);
|
||||
//let actions = vec![Action::NoOp, Action::GoToTab(1), ];
|
||||
//let keyaction = KeyActionFromYaml {
|
||||
//action : actions.clone(),
|
||||
//key : vec![ Key::F(1), Key::Backspace , Key::Char('t'), ],
|
||||
//};
|
||||
//}
|
@ -22,6 +22,7 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::cli::CliArgs;
|
||||
use crate::common::input::config::Config;
|
||||
use crate::layout::Layout;
|
||||
use crate::panes::PaneId;
|
||||
use colors_transform::{Color, Rgb};
|
||||
@ -33,7 +34,7 @@ use os_input_output::OsApi;
|
||||
use pty_bus::{PtyBus, PtyInstruction};
|
||||
use screen::{Screen, ScreenInstruction};
|
||||
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::{wasi_stdout, wasi_write_string, zellij_imports, PluginInstruction};
|
||||
use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
|
||||
@ -119,88 +120,6 @@ pub enum AppInstruction {
|
||||
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.
|
||||
// FIXME this should definitely be modularized and split into different functions.
|
||||
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())
|
||||
.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 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);
|
||||
match event {
|
||||
ScreenInstruction::Pty(pid, vte_event) => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.handle_pty_event(pid, vte_event);
|
||||
let active_tab = screen.get_active_tab_mut().unwrap();
|
||||
if active_tab.has_terminal_pid(pid) {
|
||||
// it's most likely that this event is directed at the active tab
|
||||
// look there first
|
||||
active_tab.handle_pty_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 => {
|
||||
screen.render();
|
||||
@ -398,9 +336,15 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||
ScreenInstruction::ResizeUp => {
|
||||
screen.get_active_tab_mut().unwrap().resize_up();
|
||||
}
|
||||
ScreenInstruction::MoveFocus => {
|
||||
ScreenInstruction::SwitchFocus => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus();
|
||||
}
|
||||
ScreenInstruction::FocusNextPane => {
|
||||
screen.get_active_tab_mut().unwrap().focus_next_pane();
|
||||
}
|
||||
ScreenInstruction::FocusPreviousPane => {
|
||||
screen.get_active_tab_mut().unwrap().focus_previous_pane();
|
||||
}
|
||||
ScreenInstruction::MoveFocusLeft => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus_left();
|
||||
}
|
||||
@ -661,7 +605,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
|
||||
}
|
||||
ApiCommand::MoveFocus => {
|
||||
send_screen_instructions
|
||||
.send(ScreenInstruction::MoveFocus)
|
||||
.send(ScreenInstruction::FocusNextPane)
|
||||
.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_plugin_instructions = send_plugin_instructions.clone();
|
||||
let os_input = os_input.clone();
|
||||
let config = config;
|
||||
move || {
|
||||
input_loop(
|
||||
os_input,
|
||||
config,
|
||||
command_is_executing,
|
||||
send_screen_instructions,
|
||||
send_pty_instructions,
|
||||
|
@ -242,7 +242,13 @@ impl OsApi for OsInputOutput {
|
||||
Box::new(stdout)
|
||||
}
|
||||
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();
|
||||
Ok(())
|
||||
}
|
||||
|
@ -28,7 +28,9 @@ pub enum ScreenInstruction {
|
||||
ResizeRight,
|
||||
ResizeDown,
|
||||
ResizeUp,
|
||||
MoveFocus,
|
||||
SwitchFocus,
|
||||
FocusNextPane,
|
||||
FocusPreviousPane,
|
||||
MoveFocusLeft,
|
||||
MoveFocusDown,
|
||||
MoveFocusUp,
|
||||
|
@ -4,6 +4,10 @@ use std::{iter, str::from_utf8};
|
||||
|
||||
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 {
|
||||
from_utf8(&strip(s.as_bytes()).unwrap())
|
||||
.unwrap()
|
||||
@ -28,3 +32,72 @@ pub fn adjust_to_size(s: &str, rows: usize, columns: usize) -> String {
|
||||
.collect::<Vec<_>>()
|
||||
.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
|
||||
}
|
||||
|
@ -37,18 +37,25 @@ pub enum Event {
|
||||
pub enum InputMode {
|
||||
/// In `Normal` mode, input is always written to the terminal, except for the shortcuts leading
|
||||
/// to other modes
|
||||
#[serde(alias = "normal")]
|
||||
Normal,
|
||||
/// In `Locked` mode, input is always written to the terminal and all shortcuts are disabled
|
||||
/// except the one leading back to normal mode
|
||||
#[serde(alias = "locked")]
|
||||
Locked,
|
||||
/// `Resize` mode allows resizing the different existing panes.
|
||||
#[serde(alias = "resize")]
|
||||
Resize,
|
||||
/// `Pane` mode allows creating and closing panes, as well as moving between them.
|
||||
#[serde(alias = "pane")]
|
||||
Pane,
|
||||
/// `Tab` mode allows creating and closing tabs, as well as moving between them.
|
||||
#[serde(alias = "tab")]
|
||||
Tab,
|
||||
/// `Scroll` mode allows scrolling up and down within a pane.
|
||||
#[serde(alias = "scroll")]
|
||||
Scroll,
|
||||
#[serde(alias = "renametab")]
|
||||
RenameTab,
|
||||
}
|
||||
|
||||
@ -61,7 +68,7 @@ impl Default for InputMode {
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub enum Theme {
|
||||
Light,
|
||||
Dark
|
||||
Dark,
|
||||
}
|
||||
pub mod colors {
|
||||
pub const WHITE: (u8, u8, u8) = (238, 238, 238);
|
||||
|
Loading…
Reference in New Issue
Block a user