Viewing:
use std::fmt::{Display, Error, Formatter};
use ansi_term::{ANSIStrings, Style};
use zellij_tile::prelude::actions::Action;
use zellij_tile::prelude::*;
use zellij_tile_utils::style;
const TO_NORMAL: Action = Action::SwitchToMode(InputMode::Normal);
#[derive(Default)]
pub struct LinePart {
part: String,
len: usize,
}
impl Display for LinePart {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{}", self.part)
}
}
/// Get key from action pattern(s).
///
/// This function takes as arguments a `keymap` that is a `Vec<(Key, Vec<Action>)>` and contains
/// all keybindings for the current mode and one or more `p` patterns which match a sequence of
/// actions to search for. If within the keymap a sequence of actions matching `p` is found, all
/// keys that trigger the action pattern are returned as vector of `Vec<Key>`.
pub fn action_key(keymap: &[(Key, Vec<Action>)], action: &[Action]) -> Vec<Key> {
keymap
.iter()
.filter_map(|(key, acvec)| {
if acvec.as_slice() == action {
Some(*key)
} else {
None
}
})
.collect::<Vec<Key>>()
}
#[derive(Clone, Copy)]
pub struct SegmentStyle {
pub char_shortcut: Style,
}
#[derive(Clone, Copy)]
pub struct ColoredElements {
pub selected: SegmentStyle,
pub unselected: SegmentStyle,
pub unselected_alternate: SegmentStyle,
pub disabled: SegmentStyle,
}
fn color_elements(palette: Palette) -> ColoredElements {
let background = match palette.theme_hue {
ThemeHue::Dark => palette.black,
ThemeHue::Light => palette.white,
};
// let foreground = match palette.theme_hue {
// ThemeHue::Dark => palette.white,
// ThemeHue::Light => palette.black,
// };
let alternate_background_color = palette.black;
// if different_color_alternates {
// match palette.theme_hue {
// ThemeHue::Dark => palette.white,
// ThemeHue::Light => palette.black,
// }
// } else {
// palette.fg
// };
match palette.source {
PaletteSource::Default => ColoredElements {
selected: SegmentStyle {
char_shortcut: style!(palette.gold, background).bold(),
},
unselected: SegmentStyle {
char_shortcut: style!(palette.gray, palette.black).bold(),
},
unselected_alternate: SegmentStyle {
char_shortcut: style!(palette.gray, palette.black).bold(),
},
disabled: SegmentStyle {
char_shortcut: style!(background, palette.fg).dimmed().italic(),
},
},
PaletteSource::Xresources => ColoredElements {
selected: SegmentStyle {
char_shortcut: style!(palette.red, palette.green).bold(),
},
unselected: SegmentStyle {
char_shortcut: style!(palette.red, palette.fg).bold(),
},
unselected_alternate: SegmentStyle {
char_shortcut: style!(palette.red, alternate_background_color).bold(),
},
disabled: SegmentStyle {
char_shortcut: style!(background, palette.fg).dimmed(),
},
},
}
}
struct KeyShortcut {
mode: KeyMode,
action: KeyAction,
key: Option<Key>,
}
#[derive(PartialEq)]
enum KeyAction {
Lock,
Pane,
Tab,
Resize,
Search,
Quit,
Session,
Move,
Tmux,
}
enum KeyMode {
Unselected,
UnselectedAlternate,
Selected,
Disabled,
}
impl KeyShortcut {
pub fn new(mode: KeyMode, action: KeyAction, key: Option<Key>) -> Self {
KeyShortcut { mode, action, key }
}
pub fn letter_shortcut(&self, with_prefix: bool) -> String {
let key = match self.key {
Some(k) => k,
None => return String::from("?"),
};
if with_prefix {
format!("{}", key)
} else {
match key {
Key::F(c) => format!("{}", c),
Key::Ctrl(c) => format!("{}", c),
Key::Char(_) => format!("{}", key),
Key::Alt(c) => format!("{}", c),
_ => String::from("??"),
}
}
}
}
/// Generate short mode shortcut tile.
///
/// A short mode shortcut tile consists of a leading and trailing `separator` and a keybinding. For
/// example, the default short mode shortcut tile for "Locked" mode is: ` g `.
///
/// # Arguments
///
/// - `key`: A [`KeyShortcut`] that defines how the tile is displayed (active/disabled/...), what
/// action it belongs to (roughly equivalent to [`InputMode`]s) and the keybinding to trigger
/// this action.
/// - `palette`: A structure holding styling information.
/// - `separator`: The separator printed before and after the mode shortcut tile. The default is an
/// arrow head-like separator.
/// - `shared_super`: If set to true, all mode shortcut keybindings share a common modifier (see
/// [`get_common_modifier`]) and the modifier belonging to the keybinding is **not** printed in
/// the shortcut tile.
/// - `first_tile`: If set to true, the leading separator for this tile will be ommited so no gap
/// appears on the screen.
fn short_mode_shortcut(
key: &KeyShortcut,
palette: ColoredElements,
shared_super: bool,
) -> LinePart {
let key_binding = match (&key.mode, &key.key) {
(KeyMode::Disabled, None) => "".to_string(),
(_, None) => return LinePart::default(),
(_, Some(_)) => key.letter_shortcut(!shared_super),
};
let colors = match key.mode {
KeyMode::Unselected => palette.unselected,
KeyMode::UnselectedAlternate => palette.unselected_alternate,
KeyMode::Selected => palette.selected,
KeyMode::Disabled => palette.disabled,
};
let char_shortcut = colors.char_shortcut.paint(format!("{}", key_binding));
LinePart {
part: ANSIStrings(&[char_shortcut]).to_string(),
len: key_binding.chars().count(), // Key binding
}
}
fn key_indicators(max_len: usize, keys: &[KeyShortcut], palette: ColoredElements) -> LinePart {
// Print full-width hints
let mut line_part = LinePart {
part: String::from(""),
len: 0,
};
for ctrl_key in keys {
let key = short_mode_shortcut(ctrl_key, palette, true);
line_part.part = format!("{}{}", line_part.part, key.part);
line_part.len += key.len;
}
if line_part.len < max_len {
return line_part;
}
// Shortened doesn't fit, print nothing
LinePart::default()
}
pub fn to_char(kv: Vec<Key>) -> Option<Key> {
let key = kv
.iter()
.filter(|key| {
// These are general "keybindings" to get back to normal, they aren't interesting here.
!matches!(key, Key::Char('\n') | Key::Char(' ') | Key::Esc)
})
.collect::<Vec<&Key>>()
.into_iter()
.next();
// Maybe the user bound one of the ignored keys?
if key.is_none() {
return kv.first().cloned();
}
key.cloned()
}
/// Get the [`KeyShortcut`] for a specific [`InputMode`].
///
/// Iterates over the contents of `shortcuts` to find the [`KeyShortcut`] with the [`KeyAction`]
/// matching the [`InputMode`]. Returns a mutable reference to the entry in `shortcuts` if a match
/// is found or `None` otherwise.
///
/// In case multiple entries in `shortcuts` match `mode` (which shouldn't happen), the first match
/// is returned.
fn get_key_shortcut_for_mode<'a>(
shortcuts: &'a mut [KeyShortcut],
mode: &InputMode,
) -> Option<&'a mut KeyShortcut> {
let key_action = match mode {
InputMode::Normal | InputMode::Prompt | InputMode::Tmux => return None,
InputMode::Locked => KeyAction::Lock,
InputMode::Pane | InputMode::RenamePane => KeyAction::Pane,
InputMode::Tab | InputMode::RenameTab => KeyAction::Tab,
InputMode::Resize => KeyAction::Resize,
InputMode::Move => KeyAction::Move,
InputMode::Scroll | InputMode::Search | InputMode::EnterSearch => KeyAction::Search,
InputMode::Session => KeyAction::Session,
};
for shortcut in shortcuts.iter_mut() {
if shortcut.action == key_action {
return Some(shortcut);
}
}
None
}
pub fn modestatus(help: &ModeInfo, max_len: usize) -> LinePart {
let colored_elements = color_elements(help.style.colors);
let binds = &help.get_mode_keybinds();
// Unselect all by default
let mut default_keys = vec![
KeyShortcut::new(
KeyMode::Unselected,
KeyAction::Lock,
to_char(action_key(
binds,
&[Action::SwitchToMode(InputMode::Locked)],
)),
),
KeyShortcut::new(
KeyMode::UnselectedAlternate,
KeyAction::Pane,
to_char(action_key(binds, &[Action::SwitchToMode(InputMode::Pane)])),
),
KeyShortcut::new(
KeyMode::Unselected,
KeyAction::Tab,
to_char(action_key(binds, &[Action::SwitchToMode(InputMode::Tab)])),
),
KeyShortcut::new(
KeyMode::UnselectedAlternate,
KeyAction::Resize,
to_char(action_key(
binds,
&[Action::SwitchToMode(InputMode::Resize)],
)),
),
KeyShortcut::new(
KeyMode::Unselected,
KeyAction::Move,
to_char(action_key(binds, &[Action::SwitchToMode(InputMode::Move)])),
),
KeyShortcut::new(
KeyMode::UnselectedAlternate,
KeyAction::Search,
to_char(action_key(
binds,
&[Action::SwitchToMode(InputMode::Scroll)],
)),
),
KeyShortcut::new(
KeyMode::Unselected,
KeyAction::Session,
to_char(action_key(
binds,
&[Action::SwitchToMode(InputMode::Session)],
)),
),
KeyShortcut::new(
KeyMode::UnselectedAlternate,
KeyAction::Quit,
to_char(action_key(binds, &[Action::Quit])),
),
];
if let Some(key_shortcut) = get_key_shortcut_for_mode(&mut default_keys, &help.mode) {
key_shortcut.mode = KeyMode::Selected;
key_shortcut.key = to_char(action_key(binds, &[TO_NORMAL]));
}
// In locked mode we must disable all other mode keybindings
if help.mode == InputMode::Locked {
for key in default_keys.iter_mut().skip(1) {
key.mode = KeyMode::Disabled;
}
}
if help.mode == InputMode::Tmux {
// Tmux tile is hidden by default
default_keys.push(KeyShortcut::new(
KeyMode::Selected,
KeyAction::Tmux,
to_char(action_key(binds, &[TO_NORMAL])),
));
}
key_indicators(max_len, &default_keys, colored_elements)
}