* work * work * working * get default mode from server and some ui responsiveness * work * finish design and get tests to pass * get e2e tests to pass * add classic layout * add classic layout assets * fix e2e tests * style(fmt): rustfmt * fix plugin system test * style(fmt): some cleanups
1440 lines
51 KiB
Rust
1440 lines
51 KiB
Rust
use ansi_term::{ANSIString, ANSIStrings};
|
|
use ansi_term::{
|
|
Color::{Fixed, RGB},
|
|
Style,
|
|
};
|
|
use std::collections::HashMap;
|
|
use zellij_tile::prelude::actions::Action;
|
|
use zellij_tile::prelude::*;
|
|
use zellij_tile_utils::palette_match;
|
|
|
|
use crate::first_line::{to_char, KeyAction, KeyMode, KeyShortcut};
|
|
use crate::{action_key, action_key_group, color_elements, MORE_MSG, TO_NORMAL};
|
|
use crate::{ColoredElements, LinePart};
|
|
use unicode_width::UnicodeWidthStr;
|
|
|
|
pub fn one_line_ui(
|
|
help: &ModeInfo,
|
|
tab_info: Option<&TabInfo>,
|
|
mut max_len: usize,
|
|
separator: &str,
|
|
base_mode_is_locked: bool,
|
|
) -> LinePart {
|
|
let mut line_part_to_render = LinePart::default();
|
|
let mut append = |line_part: &LinePart, max_len: &mut usize| {
|
|
line_part_to_render.append(line_part);
|
|
*max_len = max_len.saturating_sub(line_part.len);
|
|
};
|
|
|
|
render_mode_key_indicators(help, max_len, separator, base_mode_is_locked)
|
|
.map(|mode_key_indicators| append(&mode_key_indicators, &mut max_len))
|
|
.and_then(|_| match help.mode {
|
|
InputMode::Normal | InputMode::Locked => render_secondary_info(help, tab_info, max_len)
|
|
.map(|secondary_info| append(&secondary_info, &mut max_len)),
|
|
_ => add_keygroup_separator(help, max_len)
|
|
.map(|key_group_separator| append(&key_group_separator, &mut max_len))
|
|
.and_then(|_| keybinds(help, max_len))
|
|
.map(|keybinds| append(&keybinds, &mut max_len)),
|
|
});
|
|
line_part_to_render
|
|
}
|
|
|
|
fn to_base_mode(base_mode: InputMode) -> Action {
|
|
Action::SwitchToMode(base_mode)
|
|
}
|
|
|
|
fn base_mode_locked_mode_indicators(help: &ModeInfo) -> HashMap<InputMode, Vec<KeyShortcut>> {
|
|
let locked_binds = &help.get_keybinds_for_mode(InputMode::Locked);
|
|
let normal_binds = &help.get_keybinds_for_mode(InputMode::Normal);
|
|
let pane_binds = &help.get_keybinds_for_mode(InputMode::Pane);
|
|
let tab_binds = &help.get_keybinds_for_mode(InputMode::Tab);
|
|
let resize_binds = &help.get_keybinds_for_mode(InputMode::Resize);
|
|
let move_binds = &help.get_keybinds_for_mode(InputMode::Move);
|
|
let scroll_binds = &help.get_keybinds_for_mode(InputMode::Scroll);
|
|
let session_binds = &help.get_keybinds_for_mode(InputMode::Session);
|
|
HashMap::from([
|
|
(
|
|
InputMode::Locked,
|
|
vec![KeyShortcut::new(
|
|
KeyMode::Unselected,
|
|
KeyAction::Unlock,
|
|
to_char(action_key(
|
|
locked_binds,
|
|
&[Action::SwitchToMode(InputMode::Normal)],
|
|
)),
|
|
)],
|
|
),
|
|
(
|
|
InputMode::Normal,
|
|
vec![
|
|
KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Unlock,
|
|
to_char(action_key(
|
|
normal_binds,
|
|
&[Action::SwitchToMode(InputMode::Locked)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::UnselectedAlternate,
|
|
KeyAction::Pane,
|
|
to_char(action_key(
|
|
normal_binds,
|
|
&[Action::SwitchToMode(InputMode::Pane)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::Unselected,
|
|
KeyAction::Tab,
|
|
to_char(action_key(
|
|
normal_binds,
|
|
&[Action::SwitchToMode(InputMode::Tab)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::UnselectedAlternate,
|
|
KeyAction::Resize,
|
|
to_char(action_key(
|
|
normal_binds,
|
|
&[Action::SwitchToMode(InputMode::Resize)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::Unselected,
|
|
KeyAction::Move,
|
|
to_char(action_key(
|
|
normal_binds,
|
|
&[Action::SwitchToMode(InputMode::Move)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::UnselectedAlternate,
|
|
KeyAction::Search,
|
|
to_char(action_key(
|
|
normal_binds,
|
|
&[Action::SwitchToMode(InputMode::Scroll)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::Unselected,
|
|
KeyAction::Session,
|
|
to_char(action_key(
|
|
normal_binds,
|
|
&[Action::SwitchToMode(InputMode::Session)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::UnselectedAlternate,
|
|
KeyAction::Quit,
|
|
to_char(action_key(normal_binds, &[Action::Quit])),
|
|
),
|
|
],
|
|
),
|
|
(
|
|
InputMode::Pane,
|
|
vec![
|
|
KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Unlock,
|
|
to_char(action_key(
|
|
pane_binds,
|
|
&[Action::SwitchToMode(InputMode::Locked)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Pane,
|
|
to_char(action_key(
|
|
pane_binds,
|
|
&[Action::SwitchToMode(InputMode::Normal)],
|
|
)),
|
|
),
|
|
],
|
|
),
|
|
(
|
|
InputMode::Tab,
|
|
vec![
|
|
KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Unlock,
|
|
to_char(action_key(
|
|
tab_binds,
|
|
&[Action::SwitchToMode(InputMode::Locked)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Tab,
|
|
to_char(action_key(
|
|
tab_binds,
|
|
&[Action::SwitchToMode(InputMode::Normal)],
|
|
)),
|
|
),
|
|
],
|
|
),
|
|
(
|
|
InputMode::Resize,
|
|
vec![
|
|
KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Unlock,
|
|
to_char(action_key(
|
|
resize_binds,
|
|
&[Action::SwitchToMode(InputMode::Locked)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Resize,
|
|
to_char(action_key(
|
|
resize_binds,
|
|
&[Action::SwitchToMode(InputMode::Normal)],
|
|
)),
|
|
),
|
|
],
|
|
),
|
|
(
|
|
InputMode::Move,
|
|
vec![
|
|
KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Unlock,
|
|
to_char(action_key(
|
|
move_binds,
|
|
&[Action::SwitchToMode(InputMode::Locked)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Move,
|
|
to_char(action_key(
|
|
move_binds,
|
|
&[Action::SwitchToMode(InputMode::Normal)],
|
|
)),
|
|
),
|
|
],
|
|
),
|
|
(
|
|
InputMode::Scroll,
|
|
vec![
|
|
KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Unlock,
|
|
to_char(action_key(
|
|
scroll_binds,
|
|
&[Action::SwitchToMode(InputMode::Locked)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Search,
|
|
to_char(action_key(
|
|
scroll_binds,
|
|
&[Action::SwitchToMode(InputMode::Normal)],
|
|
)),
|
|
),
|
|
],
|
|
),
|
|
(
|
|
InputMode::Session,
|
|
vec![
|
|
KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Unlock,
|
|
to_char(action_key(
|
|
session_binds,
|
|
&[Action::SwitchToMode(InputMode::Locked)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Session,
|
|
to_char(action_key(
|
|
session_binds,
|
|
&[Action::SwitchToMode(InputMode::Normal)],
|
|
)),
|
|
),
|
|
],
|
|
),
|
|
])
|
|
}
|
|
|
|
fn base_mode_normal_mode_indicators(help: &ModeInfo) -> HashMap<InputMode, Vec<KeyShortcut>> {
|
|
let locked_binds = &help.get_keybinds_for_mode(InputMode::Locked);
|
|
let normal_binds = &help.get_keybinds_for_mode(InputMode::Normal);
|
|
let pane_binds = &help.get_keybinds_for_mode(InputMode::Pane);
|
|
let tab_binds = &help.get_keybinds_for_mode(InputMode::Tab);
|
|
let resize_binds = &help.get_keybinds_for_mode(InputMode::Resize);
|
|
let move_binds = &help.get_keybinds_for_mode(InputMode::Move);
|
|
let scroll_binds = &help.get_keybinds_for_mode(InputMode::Scroll);
|
|
let session_binds = &help.get_keybinds_for_mode(InputMode::Session);
|
|
HashMap::from([
|
|
(
|
|
InputMode::Locked,
|
|
vec![KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Lock,
|
|
to_char(action_key(
|
|
locked_binds,
|
|
&[Action::SwitchToMode(InputMode::Normal)],
|
|
)),
|
|
)],
|
|
),
|
|
(
|
|
InputMode::Normal,
|
|
vec![
|
|
KeyShortcut::new(
|
|
KeyMode::Unselected,
|
|
KeyAction::Lock,
|
|
to_char(action_key(
|
|
normal_binds,
|
|
&[Action::SwitchToMode(InputMode::Locked)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::UnselectedAlternate,
|
|
KeyAction::Pane,
|
|
to_char(action_key(
|
|
normal_binds,
|
|
&[Action::SwitchToMode(InputMode::Pane)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::Unselected,
|
|
KeyAction::Tab,
|
|
to_char(action_key(
|
|
normal_binds,
|
|
&[Action::SwitchToMode(InputMode::Tab)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::UnselectedAlternate,
|
|
KeyAction::Resize,
|
|
to_char(action_key(
|
|
normal_binds,
|
|
&[Action::SwitchToMode(InputMode::Resize)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::Unselected,
|
|
KeyAction::Move,
|
|
to_char(action_key(
|
|
normal_binds,
|
|
&[Action::SwitchToMode(InputMode::Move)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::UnselectedAlternate,
|
|
KeyAction::Search,
|
|
to_char(action_key(
|
|
normal_binds,
|
|
&[Action::SwitchToMode(InputMode::Scroll)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::Unselected,
|
|
KeyAction::Session,
|
|
to_char(action_key(
|
|
normal_binds,
|
|
&[Action::SwitchToMode(InputMode::Session)],
|
|
)),
|
|
),
|
|
KeyShortcut::new(
|
|
KeyMode::UnselectedAlternate,
|
|
KeyAction::Quit,
|
|
to_char(action_key(normal_binds, &[Action::Quit])),
|
|
),
|
|
],
|
|
),
|
|
(
|
|
InputMode::Pane,
|
|
vec![KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Pane,
|
|
to_char(action_key(
|
|
pane_binds,
|
|
&[Action::SwitchToMode(InputMode::Normal)],
|
|
)),
|
|
)],
|
|
),
|
|
(
|
|
InputMode::Tab,
|
|
vec![KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Tab,
|
|
to_char(action_key(
|
|
tab_binds,
|
|
&[Action::SwitchToMode(InputMode::Normal)],
|
|
)),
|
|
)],
|
|
),
|
|
(
|
|
InputMode::Resize,
|
|
vec![KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Resize,
|
|
to_char(action_key(
|
|
resize_binds,
|
|
&[Action::SwitchToMode(InputMode::Normal)],
|
|
)),
|
|
)],
|
|
),
|
|
(
|
|
InputMode::Move,
|
|
vec![KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Move,
|
|
to_char(action_key(
|
|
move_binds,
|
|
&[Action::SwitchToMode(InputMode::Normal)],
|
|
)),
|
|
)],
|
|
),
|
|
(
|
|
InputMode::Scroll,
|
|
vec![KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Search,
|
|
to_char(action_key(
|
|
scroll_binds,
|
|
&[Action::SwitchToMode(InputMode::Normal)],
|
|
)),
|
|
)],
|
|
),
|
|
(
|
|
InputMode::Session,
|
|
vec![KeyShortcut::new(
|
|
KeyMode::Selected,
|
|
KeyAction::Session,
|
|
to_char(action_key(
|
|
session_binds,
|
|
&[Action::SwitchToMode(InputMode::Normal)],
|
|
)),
|
|
)],
|
|
),
|
|
])
|
|
}
|
|
|
|
fn render_mode_key_indicators(
|
|
help: &ModeInfo,
|
|
max_len: usize,
|
|
separator: &str,
|
|
base_mode_is_locked: bool,
|
|
) -> Option<LinePart> {
|
|
let mut line_part_to_render = LinePart::default();
|
|
let supports_arrow_fonts = !help.capabilities.arrow_fonts;
|
|
let colored_elements = color_elements(help.style.colors, !supports_arrow_fonts);
|
|
let default_keys = if base_mode_is_locked {
|
|
base_mode_locked_mode_indicators(help)
|
|
} else {
|
|
base_mode_normal_mode_indicators(help)
|
|
};
|
|
match common_modifiers_in_all_modes(&default_keys) {
|
|
Some(modifiers) => {
|
|
if let Some(default_keys) = default_keys.get(&help.mode) {
|
|
let keys_without_common_modifiers: Vec<KeyShortcut> = default_keys
|
|
.iter()
|
|
.map(|key_shortcut| {
|
|
let key = key_shortcut
|
|
.get_key()
|
|
.map(|k| k.strip_common_modifiers(&modifiers));
|
|
let mode = key_shortcut.get_mode();
|
|
let action = key_shortcut.get_action();
|
|
KeyShortcut::new(mode, action, key)
|
|
})
|
|
.collect();
|
|
render_common_modifiers(
|
|
&colored_elements,
|
|
help,
|
|
&modifiers,
|
|
&mut line_part_to_render,
|
|
separator,
|
|
);
|
|
|
|
let full_shortcut_list =
|
|
full_inline_keys_modes_shortcut_list(&keys_without_common_modifiers, help);
|
|
|
|
if line_part_to_render.len + full_shortcut_list.len <= max_len {
|
|
line_part_to_render.append(&full_shortcut_list);
|
|
} else {
|
|
let shortened_shortcut_list = shortened_inline_keys_modes_shortcut_list(
|
|
&keys_without_common_modifiers,
|
|
help,
|
|
);
|
|
if line_part_to_render.len + shortened_shortcut_list.len <= max_len {
|
|
line_part_to_render.append(&shortened_shortcut_list);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
None => {
|
|
if let Some(default_keys) = default_keys.get(&help.mode) {
|
|
let full_shortcut_list = full_modes_shortcut_list(&default_keys, help);
|
|
if line_part_to_render.len + full_shortcut_list.len <= max_len {
|
|
line_part_to_render.append(&full_shortcut_list);
|
|
} else {
|
|
let shortened_shortcut_list =
|
|
shortened_modes_shortcut_list(&default_keys, help);
|
|
if line_part_to_render.len + shortened_shortcut_list.len <= max_len {
|
|
line_part_to_render.append(&shortened_shortcut_list);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
}
|
|
if line_part_to_render.len <= max_len {
|
|
Some(line_part_to_render)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn full_inline_keys_modes_shortcut_list(
|
|
keys_without_common_modifiers: &Vec<KeyShortcut>,
|
|
help: &ModeInfo,
|
|
) -> LinePart {
|
|
let mut full_shortcut_list = LinePart::default();
|
|
for key in keys_without_common_modifiers {
|
|
let is_selected = key.is_selected();
|
|
let shortcut = add_shortcut_with_inline_key(
|
|
help,
|
|
&key.full_text(),
|
|
key.key
|
|
.as_ref()
|
|
.map(|k| vec![k.clone()])
|
|
.unwrap_or_else(|| vec![]),
|
|
is_selected,
|
|
);
|
|
full_shortcut_list.append(&shortcut);
|
|
}
|
|
full_shortcut_list
|
|
}
|
|
|
|
fn shortened_inline_keys_modes_shortcut_list(
|
|
keys_without_common_modifiers: &Vec<KeyShortcut>,
|
|
help: &ModeInfo,
|
|
) -> LinePart {
|
|
let mut shortened_shortcut_list = LinePart::default();
|
|
for key in keys_without_common_modifiers {
|
|
let is_selected = key.is_selected();
|
|
let shortcut = add_shortcut_with_key_only(
|
|
help,
|
|
key.key
|
|
.as_ref()
|
|
.map(|k| vec![k.clone()])
|
|
.unwrap_or_else(|| vec![]),
|
|
is_selected,
|
|
);
|
|
shortened_shortcut_list.append(&shortcut);
|
|
}
|
|
shortened_shortcut_list
|
|
}
|
|
|
|
fn full_modes_shortcut_list(default_keys: &Vec<KeyShortcut>, help: &ModeInfo) -> LinePart {
|
|
let mut full_shortcut_list = LinePart::default();
|
|
for key in default_keys {
|
|
let is_selected = key.is_selected();
|
|
full_shortcut_list.append(&add_shortcut(
|
|
help,
|
|
&key.full_text(),
|
|
&key.key
|
|
.as_ref()
|
|
.map(|k| vec![k.clone()])
|
|
.unwrap_or_else(|| vec![]),
|
|
is_selected,
|
|
Some(3),
|
|
));
|
|
}
|
|
full_shortcut_list
|
|
}
|
|
|
|
fn shortened_modes_shortcut_list(default_keys: &Vec<KeyShortcut>, help: &ModeInfo) -> LinePart {
|
|
let mut shortened_shortcut_list = LinePart::default();
|
|
for key in default_keys {
|
|
let is_selected = key.is_selected();
|
|
shortened_shortcut_list.append(&add_shortcut(
|
|
help,
|
|
&key.short_text(),
|
|
&key.key
|
|
.as_ref()
|
|
.map(|k| vec![k.clone()])
|
|
.unwrap_or_else(|| vec![]),
|
|
is_selected,
|
|
Some(3),
|
|
));
|
|
}
|
|
shortened_shortcut_list
|
|
}
|
|
|
|
fn common_modifiers_in_all_modes(
|
|
key_shortcuts: &HashMap<InputMode, Vec<KeyShortcut>>,
|
|
) -> Option<Vec<KeyModifier>> {
|
|
let Some(mut common_modifiers) = key_shortcuts.iter().next().and_then(|k| {
|
|
k.1.iter()
|
|
.next()
|
|
.and_then(|k| k.get_key().map(|k| k.key_modifiers.clone()))
|
|
}) else {
|
|
return None;
|
|
};
|
|
for (_mode, key_shortcuts) in key_shortcuts {
|
|
if key_shortcuts.is_empty() {
|
|
return None;
|
|
}
|
|
let Some(mut common_modifiers_for_mode) = key_shortcuts
|
|
.iter()
|
|
.next()
|
|
.unwrap()
|
|
.get_key()
|
|
.map(|k| k.key_modifiers.clone())
|
|
else {
|
|
return None;
|
|
};
|
|
for key in key_shortcuts {
|
|
let Some(key) = key.get_key() else {
|
|
return None;
|
|
};
|
|
common_modifiers_for_mode = common_modifiers_for_mode
|
|
.intersection(&key.key_modifiers)
|
|
.cloned()
|
|
.collect();
|
|
}
|
|
common_modifiers = common_modifiers
|
|
.intersection(&common_modifiers_for_mode)
|
|
.cloned()
|
|
.collect();
|
|
}
|
|
if common_modifiers.is_empty() {
|
|
return None;
|
|
}
|
|
Some(common_modifiers.into_iter().collect())
|
|
}
|
|
|
|
fn render_common_modifiers(
|
|
palette: &ColoredElements,
|
|
mode_info: &ModeInfo,
|
|
common_modifiers: &Vec<KeyModifier>,
|
|
line_part_to_render: &mut LinePart,
|
|
separator: &str,
|
|
) {
|
|
let prefix_text = if mode_info.capabilities.arrow_fonts {
|
|
// Add extra space in simplified ui
|
|
format!(
|
|
" {} + ",
|
|
common_modifiers
|
|
.iter()
|
|
.map(|m| m.to_string())
|
|
.collect::<Vec<_>>()
|
|
.join("-")
|
|
)
|
|
} else {
|
|
format!(
|
|
" {} +",
|
|
common_modifiers
|
|
.iter()
|
|
.map(|m| m.to_string())
|
|
.collect::<Vec<_>>()
|
|
.join("-")
|
|
)
|
|
};
|
|
|
|
let suffix_separator = palette.superkey_suffix_separator.paint(separator);
|
|
line_part_to_render.part = format!(
|
|
"{}{}{}",
|
|
line_part_to_render.part,
|
|
serialize_text(&Text::new(&prefix_text)),
|
|
suffix_separator
|
|
);
|
|
line_part_to_render.len += prefix_text.chars().count() + separator.chars().count();
|
|
}
|
|
|
|
fn render_secondary_info(
|
|
help: &ModeInfo,
|
|
tab_info: Option<&TabInfo>,
|
|
max_len: usize,
|
|
) -> Option<LinePart> {
|
|
let mut secondary_info = LinePart::default();
|
|
let supports_arrow_fonts = !help.capabilities.arrow_fonts;
|
|
let colored_elements = color_elements(help.style.colors, !supports_arrow_fonts);
|
|
let secondary_keybinds = secondary_keybinds(&help, tab_info, max_len);
|
|
secondary_info.append(&secondary_keybinds);
|
|
let remaining_space = max_len.saturating_sub(secondary_info.len).saturating_sub(1); // 1 for the end padding of the line
|
|
let mut padding = String::new();
|
|
let mut padding_len = 0;
|
|
for _ in 0..remaining_space {
|
|
padding.push_str(&ANSIStrings(&[colored_elements.superkey_prefix.paint(" ")]).to_string());
|
|
padding_len += 1;
|
|
}
|
|
secondary_info.part = format!("{}{}", padding, secondary_info.part);
|
|
secondary_info.len += padding_len;
|
|
if secondary_info.len <= max_len {
|
|
Some(secondary_info)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn secondary_keybinds(help: &ModeInfo, tab_info: Option<&TabInfo>, max_len: usize) -> LinePart {
|
|
let mut secondary_info = LinePart::default();
|
|
let binds = &help.get_mode_keybinds();
|
|
// New Pane
|
|
let new_pane_action_key = action_key(binds, &[Action::NewPane(None, None)]);
|
|
let mut new_pane_key_to_display = new_pane_action_key
|
|
.iter()
|
|
.find(|k| k.is_key_with_alt_modifier(BareKey::Char('n')))
|
|
.or_else(|| new_pane_action_key.iter().next());
|
|
let new_pane_key_to_display =
|
|
if let Some(new_pane_key_to_display) = new_pane_key_to_display.take() {
|
|
vec![new_pane_key_to_display.clone()]
|
|
} else {
|
|
vec![]
|
|
};
|
|
|
|
// Move focus
|
|
let mut move_focus_shortcuts: Vec<KeyWithModifier> = vec![];
|
|
|
|
// Left
|
|
let move_focus_left_action_key = action_key(binds, &[Action::MoveFocusOrTab(Direction::Left)]);
|
|
let move_focus_left_key = move_focus_left_action_key
|
|
.iter()
|
|
.find(|k| k.is_key_with_alt_modifier(BareKey::Left))
|
|
.or_else(|| move_focus_left_action_key.iter().next());
|
|
if let Some(move_focus_left_key) = move_focus_left_key {
|
|
move_focus_shortcuts.push(move_focus_left_key.clone());
|
|
}
|
|
// Down
|
|
let move_focus_left_action_key = action_key(binds, &[Action::MoveFocus(Direction::Down)]);
|
|
let move_focus_left_key = move_focus_left_action_key
|
|
.iter()
|
|
.find(|k| k.is_key_with_alt_modifier(BareKey::Down))
|
|
.or_else(|| move_focus_left_action_key.iter().next());
|
|
if let Some(move_focus_left_key) = move_focus_left_key {
|
|
move_focus_shortcuts.push(move_focus_left_key.clone());
|
|
}
|
|
// Up
|
|
let move_focus_left_action_key = action_key(binds, &[Action::MoveFocus(Direction::Up)]);
|
|
let move_focus_left_key = move_focus_left_action_key
|
|
.iter()
|
|
.find(|k| k.is_key_with_alt_modifier(BareKey::Up))
|
|
.or_else(|| move_focus_left_action_key.iter().next());
|
|
if let Some(move_focus_left_key) = move_focus_left_key {
|
|
move_focus_shortcuts.push(move_focus_left_key.clone());
|
|
}
|
|
// Right
|
|
let move_focus_left_action_key = action_key(binds, &[Action::MoveFocusOrTab(Direction::Right)]);
|
|
let move_focus_left_key = move_focus_left_action_key
|
|
.iter()
|
|
.find(|k| k.is_key_with_alt_modifier(BareKey::Right))
|
|
.or_else(|| move_focus_left_action_key.iter().next());
|
|
if let Some(move_focus_left_key) = move_focus_left_key {
|
|
move_focus_shortcuts.push(move_focus_left_key.clone());
|
|
}
|
|
|
|
let toggle_floating_action_key = action_key(binds, &[Action::ToggleFloatingPanes]);
|
|
let mut toggle_floating_action_key = toggle_floating_action_key
|
|
.iter()
|
|
.find(|k| k.is_key_with_alt_modifier(BareKey::Char('f')))
|
|
.or_else(|| toggle_floating_action_key.iter().next());
|
|
let toggle_floating_key_to_display =
|
|
if let Some(toggle_floating_key_to_display) = toggle_floating_action_key.take() {
|
|
vec![toggle_floating_key_to_display.clone()]
|
|
} else {
|
|
vec![]
|
|
};
|
|
let are_floating_panes_visible = tab_info
|
|
.map(|t| t.are_floating_panes_visible)
|
|
.unwrap_or(false);
|
|
|
|
let common_modifiers = get_common_modifiers(
|
|
[
|
|
new_pane_key_to_display.clone(),
|
|
move_focus_shortcuts.clone(),
|
|
toggle_floating_key_to_display.clone(),
|
|
]
|
|
.iter()
|
|
.flatten()
|
|
.collect(),
|
|
);
|
|
let no_common_modifier = common_modifiers.is_empty();
|
|
|
|
if no_common_modifier {
|
|
secondary_info.append(&add_shortcut(
|
|
help,
|
|
"New Pane",
|
|
&new_pane_key_to_display,
|
|
false,
|
|
Some(0),
|
|
));
|
|
secondary_info.append(&add_shortcut(
|
|
help,
|
|
"Change Focus",
|
|
&move_focus_shortcuts,
|
|
false,
|
|
Some(0),
|
|
));
|
|
secondary_info.append(&add_shortcut(
|
|
help,
|
|
"Floating",
|
|
&toggle_floating_key_to_display,
|
|
are_floating_panes_visible,
|
|
Some(0),
|
|
));
|
|
} else {
|
|
let modifier_str = text_as_line_part_with_emphasis(
|
|
format!(
|
|
"{} + ",
|
|
common_modifiers
|
|
.iter()
|
|
.map(|m| m.to_string())
|
|
.collect::<Vec<_>>()
|
|
.join("-")
|
|
),
|
|
0,
|
|
);
|
|
secondary_info.append(&modifier_str);
|
|
let new_pane_key_to_display: Vec<KeyWithModifier> = new_pane_key_to_display
|
|
.iter()
|
|
.map(|k| k.strip_common_modifiers(&common_modifiers))
|
|
.collect();
|
|
let move_focus_shortcuts: Vec<KeyWithModifier> = move_focus_shortcuts
|
|
.iter()
|
|
.map(|k| k.strip_common_modifiers(&common_modifiers))
|
|
.collect();
|
|
let toggle_floating_key_to_display: Vec<KeyWithModifier> = toggle_floating_key_to_display
|
|
.iter()
|
|
.map(|k| k.strip_common_modifiers(&common_modifiers))
|
|
.collect();
|
|
secondary_info.append(&add_shortcut_with_inline_key(
|
|
help,
|
|
"New Pane",
|
|
new_pane_key_to_display,
|
|
false,
|
|
));
|
|
secondary_info.append(&add_shortcut_with_inline_key(
|
|
help,
|
|
"Change Focus",
|
|
move_focus_shortcuts,
|
|
false,
|
|
));
|
|
secondary_info.append(&add_shortcut_with_inline_key(
|
|
help,
|
|
"Floating",
|
|
toggle_floating_key_to_display,
|
|
are_floating_panes_visible,
|
|
));
|
|
}
|
|
|
|
if secondary_info.len <= max_len {
|
|
secondary_info
|
|
} else {
|
|
let mut short_line = LinePart::default();
|
|
if no_common_modifier {
|
|
short_line.append(&add_shortcut(
|
|
help,
|
|
"New",
|
|
&new_pane_key_to_display,
|
|
false,
|
|
Some(0),
|
|
));
|
|
short_line.append(&add_shortcut(
|
|
help,
|
|
"Focus",
|
|
&move_focus_shortcuts,
|
|
false,
|
|
Some(0),
|
|
));
|
|
short_line.append(&add_shortcut(
|
|
help,
|
|
"Floating",
|
|
&toggle_floating_key_to_display,
|
|
are_floating_panes_visible,
|
|
Some(0),
|
|
));
|
|
} else {
|
|
let modifier_str = text_as_line_part_with_emphasis(
|
|
format!(
|
|
"{} + ",
|
|
common_modifiers
|
|
.iter()
|
|
.map(|m| m.to_string())
|
|
.collect::<Vec<_>>()
|
|
.join("-")
|
|
),
|
|
0,
|
|
);
|
|
short_line.append(&modifier_str);
|
|
let new_pane_key_to_display: Vec<KeyWithModifier> = new_pane_key_to_display
|
|
.iter()
|
|
.map(|k| k.strip_common_modifiers(&common_modifiers))
|
|
.collect();
|
|
let move_focus_shortcuts: Vec<KeyWithModifier> = move_focus_shortcuts
|
|
.iter()
|
|
.map(|k| k.strip_common_modifiers(&common_modifiers))
|
|
.collect();
|
|
let toggle_floating_key_to_display: Vec<KeyWithModifier> =
|
|
toggle_floating_key_to_display
|
|
.iter()
|
|
.map(|k| k.strip_common_modifiers(&common_modifiers))
|
|
.collect();
|
|
short_line.append(&add_shortcut_with_inline_key(
|
|
help,
|
|
"New",
|
|
new_pane_key_to_display,
|
|
false,
|
|
));
|
|
short_line.append(&add_shortcut_with_inline_key(
|
|
help,
|
|
"Focus",
|
|
move_focus_shortcuts,
|
|
false,
|
|
));
|
|
short_line.append(&add_shortcut_with_inline_key(
|
|
help,
|
|
"Floating",
|
|
toggle_floating_key_to_display,
|
|
are_floating_panes_visible,
|
|
));
|
|
}
|
|
short_line
|
|
}
|
|
}
|
|
|
|
fn text_as_line_part_with_emphasis(text: String, emphases_index: usize) -> LinePart {
|
|
let part = serialize_text(&Text::new(&text).color_range(emphases_index, ..));
|
|
LinePart {
|
|
part,
|
|
len: text.width(),
|
|
}
|
|
}
|
|
|
|
fn keybinds(help: &ModeInfo, max_width: usize) -> Option<LinePart> {
|
|
let full_shortcut_list = full_shortcut_list(help);
|
|
if full_shortcut_list.len <= max_width {
|
|
return Some(full_shortcut_list);
|
|
}
|
|
let shortened_shortcut_list = shortened_shortcut_list(help);
|
|
if shortened_shortcut_list.len <= max_width {
|
|
return Some(shortened_shortcut_list);
|
|
}
|
|
Some(best_effort_shortcut_list(help, max_width))
|
|
}
|
|
|
|
fn add_shortcut(
|
|
help: &ModeInfo,
|
|
text: &str,
|
|
keys: &Vec<KeyWithModifier>,
|
|
selected: bool,
|
|
key_color_index: Option<usize>,
|
|
) -> LinePart {
|
|
let mut ret = LinePart::default();
|
|
if keys.is_empty() {
|
|
return ret;
|
|
}
|
|
|
|
ret.append(&style_key_with_modifier(&keys, key_color_index)); // TODO: alternate
|
|
//
|
|
let ribbon = if selected {
|
|
serialize_ribbon(&Text::new(format!("{}", text)).selected())
|
|
} else {
|
|
serialize_ribbon(&Text::new(format!("{}", text)))
|
|
};
|
|
ret.part = format!("{}{}", ret.part, ribbon);
|
|
let supports_arrow_fonts = !help.capabilities.arrow_fonts;
|
|
ret.len += if supports_arrow_fonts {
|
|
text.width() + 4 // padding and arrow fonts
|
|
} else {
|
|
text.width() + 2 // padding
|
|
};
|
|
ret
|
|
}
|
|
|
|
fn add_shortcut_with_inline_key(
|
|
help: &ModeInfo,
|
|
text: &str,
|
|
key: Vec<KeyWithModifier>,
|
|
is_selected: bool,
|
|
) -> LinePart {
|
|
let capabilities = help.capabilities;
|
|
|
|
let mut ret = LinePart::default();
|
|
if key.is_empty() {
|
|
return ret;
|
|
}
|
|
|
|
let key_separator = match key
|
|
.iter()
|
|
.map(|k| k.to_string())
|
|
.collect::<Vec<_>>()
|
|
.join("")
|
|
.as_str()
|
|
{
|
|
"HJKL" => "",
|
|
"hjkl" => "",
|
|
"←↓↑→" => "",
|
|
"←→" => "",
|
|
"↓↑" => "",
|
|
"[]" => "",
|
|
_ => "|",
|
|
};
|
|
|
|
let key_string = format!(
|
|
"{}",
|
|
key.iter()
|
|
.map(|k| k.to_string())
|
|
.collect::<Vec<_>>()
|
|
.join(key_separator)
|
|
);
|
|
|
|
let ribbon = if is_selected {
|
|
serialize_ribbon(
|
|
&Text::new(format!("<{}> {}", key_string, text))
|
|
.color_range(0, 1..key_string.width() + 1)
|
|
.selected(),
|
|
)
|
|
} else {
|
|
serialize_ribbon(
|
|
&Text::new(format!("<{}> {}", key_string, text))
|
|
.color_range(0, 1..key_string.width() + 1),
|
|
)
|
|
};
|
|
ret.part = ribbon;
|
|
let supports_arrow_fonts = !capabilities.arrow_fonts;
|
|
ret.len += if supports_arrow_fonts {
|
|
text.width() + key_string.width() + 7 // padding, group boundaries and arrow fonts
|
|
} else {
|
|
text.width() + key_string.width() + 5 // padding and group boundaries
|
|
};
|
|
|
|
ret
|
|
}
|
|
|
|
fn add_shortcut_with_key_only(
|
|
help: &ModeInfo,
|
|
key: Vec<KeyWithModifier>,
|
|
is_selected: bool,
|
|
) -> LinePart {
|
|
let mut ret = LinePart::default();
|
|
if key.is_empty() {
|
|
return ret;
|
|
}
|
|
|
|
let key_string = format!(
|
|
"{}",
|
|
key.iter()
|
|
.map(|k| k.to_string())
|
|
.collect::<Vec<_>>()
|
|
.join("-")
|
|
);
|
|
|
|
let ribbon = if is_selected {
|
|
serialize_ribbon(
|
|
&Text::new(format!("{}", key_string))
|
|
.color_range(0, ..)
|
|
.selected(),
|
|
)
|
|
} else {
|
|
serialize_ribbon(&Text::new(format!("{}", key_string)).color_range(0, ..))
|
|
};
|
|
ret.part = ribbon;
|
|
let supports_arrow_fonts = !help.capabilities.arrow_fonts;
|
|
ret.len += if supports_arrow_fonts {
|
|
key_string.width() + 4 // 4 => arrow fonts + padding
|
|
} else {
|
|
key_string.width() + 2 // 2 => padding
|
|
};
|
|
ret
|
|
}
|
|
|
|
fn add_keygroup_separator(help: &ModeInfo, max_len: usize) -> Option<LinePart> {
|
|
let supports_arrow_fonts = !help.capabilities.arrow_fonts;
|
|
let separator = if supports_arrow_fonts {
|
|
crate::ARROW_SEPARATOR
|
|
} else {
|
|
" "
|
|
};
|
|
let palette = help.style.colors;
|
|
|
|
let mut ret = LinePart::default();
|
|
|
|
let separator_color = palette_match!(palette.orange);
|
|
let bg_color = palette_match!(palette.black);
|
|
let mut bits: Vec<ANSIString> = vec![];
|
|
let mode_help_text = match help.mode {
|
|
InputMode::RenamePane => Some("RENAMING PANE"),
|
|
InputMode::RenameTab => Some("RENAMING TAB"),
|
|
InputMode::EnterSearch => Some("ENTERING SEARCH TERM"),
|
|
InputMode::Search => Some("SEARCHING"),
|
|
_ => None,
|
|
};
|
|
if let Some(mode_help_text) = mode_help_text {
|
|
bits.push(
|
|
Style::new()
|
|
.fg(separator_color)
|
|
.bold()
|
|
.paint(format!(" {} ", mode_help_text)),
|
|
);
|
|
ret.len += mode_help_text.width() + 2; // 2 => padding
|
|
}
|
|
bits.push(
|
|
Style::new()
|
|
.fg(bg_color)
|
|
.on(separator_color)
|
|
.bold()
|
|
.paint(format!("{}", separator)),
|
|
);
|
|
bits.push(
|
|
Style::new()
|
|
.fg(separator_color)
|
|
.on(separator_color)
|
|
.bold()
|
|
.paint(format!(" ")),
|
|
);
|
|
bits.push(
|
|
Style::new()
|
|
.fg(separator_color)
|
|
.on(bg_color)
|
|
.bold()
|
|
.paint(format!("{}", separator)),
|
|
);
|
|
ret.part = format!("{}{}", ret.part, ANSIStrings(&bits));
|
|
ret.len += 3; // padding and arrow fonts
|
|
|
|
if ret.len <= max_len {
|
|
Some(ret)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn full_shortcut_list(help: &ModeInfo) -> LinePart {
|
|
match help.mode {
|
|
InputMode::Normal => LinePart::default(),
|
|
InputMode::Locked => LinePart::default(),
|
|
_ => full_shortcut_list_nonstandard_mode(help),
|
|
}
|
|
}
|
|
|
|
fn full_shortcut_list_nonstandard_mode(help: &ModeInfo) -> LinePart {
|
|
let mut line_part = LinePart::default();
|
|
let keys_and_hints = get_keys_and_hints(help);
|
|
|
|
for (long, _short, keys) in keys_and_hints.into_iter() {
|
|
line_part.append(&add_shortcut(help, &long, &keys.to_vec(), false, Some(2)));
|
|
}
|
|
line_part
|
|
}
|
|
|
|
#[rustfmt::skip]
|
|
fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec<KeyWithModifier>)> {
|
|
use Action as A;
|
|
use InputMode as IM;
|
|
use Direction as Dir;
|
|
use actions::SearchDirection as SDir;
|
|
use actions::SearchOption as SOpt;
|
|
|
|
let mut old_keymap = mi.get_mode_keybinds();
|
|
let s = |string: &str| string.to_string();
|
|
|
|
// Find a keybinding to get back to "Normal" input mode. In this case we prefer '\n' over other
|
|
// choices. Do it here before we dedupe the keymap below!
|
|
let base_mode = mi.base_mode;
|
|
let to_basemode_keys = base_mode.map(|b| action_key(&old_keymap, &[to_base_mode(b)])).unwrap_or_else(|| action_key(&old_keymap, &[TO_NORMAL]));
|
|
let to_basemode_key = if to_basemode_keys.contains(&KeyWithModifier::new(BareKey::Enter)) {
|
|
vec![KeyWithModifier::new(BareKey::Enter)]
|
|
} else {
|
|
// Yield `vec![key]` if `to_normal_keys` has at least one key, or an empty vec otherwise.
|
|
to_basemode_keys.into_iter().take(1).collect()
|
|
};
|
|
|
|
// Sort and deduplicate the keybindings first. We sort after the `Key`s, and deduplicate by
|
|
// their `Action` vectors. An unstable sort is fine here because if the user maps anything to
|
|
// the same key again, anything will happen...
|
|
old_keymap.sort_unstable_by(|(keya, _), (keyb, _)| keya.partial_cmp(keyb).unwrap());
|
|
|
|
let mut known_actions: Vec<Vec<Action>> = vec![];
|
|
let mut km = vec![];
|
|
for (key, acvec) in old_keymap {
|
|
if known_actions.contains(&acvec) {
|
|
// This action is known already
|
|
continue;
|
|
} else {
|
|
known_actions.push(acvec.to_vec());
|
|
km.push((key, acvec));
|
|
}
|
|
}
|
|
|
|
if mi.mode == IM::Pane { vec![
|
|
(s("New"), s("New"), single_action_key(&km, &[A::NewPane(None, None), TO_NORMAL])),
|
|
(s("Change Focus"), s("Move"),
|
|
action_key_group(&km, &[&[A::MoveFocus(Dir::Left)], &[A::MoveFocus(Dir::Down)],
|
|
&[A::MoveFocus(Dir::Up)], &[A::MoveFocus(Dir::Right)]])),
|
|
(s("Close"), s("Close"), single_action_key(&km, &[A::CloseFocus, TO_NORMAL])),
|
|
(s("Rename"), s("Rename"),
|
|
single_action_key(&km, &[A::SwitchToMode(IM::RenamePane), A::PaneNameInput(vec![0])])),
|
|
(s("Toggle Fullscreen"), s("Fullscreen"), single_action_key(&km, &[A::ToggleFocusFullscreen, TO_NORMAL])),
|
|
(s("Toggle Floating"), s("Floating"),
|
|
single_action_key(&km, &[A::ToggleFloatingPanes, TO_NORMAL])),
|
|
(s("Toggle Embed"), s("Embed"), single_action_key(&km, &[A::TogglePaneEmbedOrFloating, TO_NORMAL])),
|
|
(s("Select pane"), s("Select"), to_basemode_key),
|
|
]} else if mi.mode == IM::Tab {
|
|
// With the default bindings, "Move focus" for tabs is tricky: It binds all the arrow keys
|
|
// to moving tabs focus (left/up go left, right/down go right). Since we sort the keys
|
|
// above and then dedpulicate based on the actions, we will end up with LeftArrow for
|
|
// "left" and DownArrow for "right". What we really expect is to see LeftArrow and
|
|
// RightArrow.
|
|
// FIXME: So for lack of a better idea we just check this case manually here.
|
|
let old_keymap = mi.get_mode_keybinds();
|
|
let focus_keys_full: Vec<KeyWithModifier> = action_key_group(&old_keymap,
|
|
&[&[A::GoToPreviousTab], &[A::GoToNextTab]]);
|
|
let focus_keys = if focus_keys_full.contains(&KeyWithModifier::new(BareKey::Left))
|
|
&& focus_keys_full.contains(&KeyWithModifier::new(BareKey::Right)) {
|
|
vec![KeyWithModifier::new(BareKey::Left), KeyWithModifier::new(BareKey::Right)]
|
|
} else {
|
|
action_key_group(&km, &[&[A::GoToPreviousTab], &[A::GoToNextTab]])
|
|
};
|
|
|
|
vec![
|
|
(s("New"), s("New"), single_action_key(&km, &[A::NewTab(None, vec![], None, None, None), TO_NORMAL])),
|
|
(s("Change focus"), s("Move"), focus_keys),
|
|
(s("Close"), s("Close"), single_action_key(&km, &[A::CloseTab, TO_NORMAL])),
|
|
(s("Rename"), s("Rename"),
|
|
single_action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])),
|
|
(s("Sync"), s("Sync"), single_action_key(&km, &[A::ToggleActiveSyncTab, TO_NORMAL])),
|
|
(s("Break pane to new tab"), s("Break out"), single_action_key(&km, &[A::BreakPane, TO_NORMAL])),
|
|
(s("Break pane left/right"), s("Break"), action_key_group(&km, &[
|
|
&[Action::BreakPaneLeft, TO_NORMAL],
|
|
&[Action::BreakPaneRight, TO_NORMAL],
|
|
])),
|
|
(s("Toggle"), s("Toggle"), single_action_key(&km, &[A::ToggleTab])),
|
|
(s("Select pane"), s("Select"), to_basemode_key),
|
|
]} else if mi.mode == IM::Resize { vec![
|
|
(s("Increase/Decrease size"), s("Increase/Decrease"),
|
|
action_key_group(&km, &[
|
|
&[A::Resize(Resize::Increase, None)],
|
|
&[A::Resize(Resize::Decrease, None)]
|
|
])),
|
|
(s("Increase to"), s("Increase"), action_key_group(&km, &[
|
|
&[A::Resize(Resize::Increase, Some(Dir::Left))],
|
|
&[A::Resize(Resize::Increase, Some(Dir::Down))],
|
|
&[A::Resize(Resize::Increase, Some(Dir::Up))],
|
|
&[A::Resize(Resize::Increase, Some(Dir::Right))]
|
|
])),
|
|
(s("Decrease from"), s("Decrease"), action_key_group(&km, &[
|
|
&[A::Resize(Resize::Decrease, Some(Dir::Left))],
|
|
&[A::Resize(Resize::Decrease, Some(Dir::Down))],
|
|
&[A::Resize(Resize::Decrease, Some(Dir::Up))],
|
|
&[A::Resize(Resize::Decrease, Some(Dir::Right))]
|
|
])),
|
|
(s("Select pane"), s("Select"), to_basemode_key),
|
|
]} else if mi.mode == IM::Move { vec![
|
|
(s("Switch Location"), s("Move"), action_key_group(&km, &[
|
|
&[Action::MovePane(Some(Dir::Left))], &[Action::MovePane(Some(Dir::Down))],
|
|
&[Action::MovePane(Some(Dir::Up))], &[Action::MovePane(Some(Dir::Right))]])),
|
|
]} else if mi.mode == IM::Scroll { vec![
|
|
(s("Enter search term"), s("Search"),
|
|
action_key(&km, &[A::SwitchToMode(IM::EnterSearch), A::SearchInput(vec![0])])),
|
|
(s("Scroll"), s("Scroll"),
|
|
action_key_group(&km, &[&[Action::ScrollDown], &[Action::ScrollUp]])),
|
|
(s("Scroll page"), s("Scroll"),
|
|
action_key_group(&km, &[&[Action::PageScrollDown], &[Action::PageScrollUp]])),
|
|
(s("Scroll half page"), s("Scroll"),
|
|
action_key_group(&km, &[&[Action::HalfPageScrollDown], &[Action::HalfPageScrollUp]])),
|
|
(s("Edit scrollback in default editor"), s("Edit"),
|
|
single_action_key(&km, &[Action::EditScrollback, TO_NORMAL])),
|
|
(s("Select pane"), s("Select"), to_basemode_key),
|
|
]} else if mi.mode == IM::EnterSearch { vec![
|
|
(s("When done"), s("Done"), action_key(&km, &[A::SwitchToMode(IM::Search)])),
|
|
(s("Cancel"), s("Cancel"),
|
|
action_key(&km, &[A::SearchInput(vec![27]), A::SwitchToMode(IM::Scroll)])),
|
|
]} else if mi.mode == IM::Search { vec![
|
|
(s("Enter Search term"), s("Search"),
|
|
action_key(&km, &[A::SwitchToMode(IM::EnterSearch), A::SearchInput(vec![0])])),
|
|
(s("Scroll"), s("Scroll"),
|
|
action_key_group(&km, &[&[Action::ScrollDown], &[Action::ScrollUp]])),
|
|
(s("Scroll page"), s("Scroll"),
|
|
action_key_group(&km, &[&[Action::PageScrollDown], &[Action::PageScrollUp]])),
|
|
(s("Scroll half page"), s("Scroll"),
|
|
action_key_group(&km, &[&[Action::HalfPageScrollDown], &[Action::HalfPageScrollUp]])),
|
|
(s("Search down"), s("Down"), action_key(&km, &[A::Search(SDir::Down)])),
|
|
(s("Search up"), s("Up"), action_key(&km, &[A::Search(SDir::Up)])),
|
|
(s("Case sensitive"), s("Case"),
|
|
action_key(&km, &[A::SearchToggleOption(SOpt::CaseSensitivity)])),
|
|
(s("Wrap"), s("Wrap"),
|
|
action_key(&km, &[A::SearchToggleOption(SOpt::Wrap)])),
|
|
(s("Whole words"), s("Whole"),
|
|
action_key(&km, &[A::SearchToggleOption(SOpt::WholeWord)])),
|
|
]} else if mi.mode == IM::Session { vec![
|
|
(s("Detach"), s("Detach"), action_key(&km, &[Action::Detach])),
|
|
(s("Session Manager"), s("Manager"), session_manager_key(&km)),
|
|
(s("Select pane"), s("Select"), to_basemode_key),
|
|
]} else if mi.mode == IM::Tmux { vec![
|
|
(s("Move focus"), s("Move"), action_key_group(&km, &[
|
|
&[A::MoveFocus(Dir::Left)], &[A::MoveFocus(Dir::Down)],
|
|
&[A::MoveFocus(Dir::Up)], &[A::MoveFocus(Dir::Right)]])),
|
|
(s("Split down"), s("Down"), action_key(&km, &[A::NewPane(Some(Dir::Down), None), TO_NORMAL])),
|
|
(s("Split right"), s("Right"), action_key(&km, &[A::NewPane(Some(Dir::Right), None), TO_NORMAL])),
|
|
(s("Fullscreen"), s("Fullscreen"), action_key(&km, &[A::ToggleFocusFullscreen, TO_NORMAL])),
|
|
(s("New tab"), s("New"), action_key(&km, &[A::NewTab(None, vec![], None, None, None), TO_NORMAL])),
|
|
(s("Rename tab"), s("Rename"),
|
|
action_key(&km, &[A::SwitchToMode(IM::RenameTab), A::TabNameInput(vec![0])])),
|
|
(s("Previous Tab"), s("Previous"), action_key(&km, &[A::GoToPreviousTab, TO_NORMAL])),
|
|
(s("Next Tab"), s("Next"), action_key(&km, &[A::GoToNextTab, TO_NORMAL])),
|
|
(s("Select pane"), s("Select"), to_basemode_key),
|
|
]} else if matches!(mi.mode, IM::RenamePane | IM::RenameTab) { vec![
|
|
(s("When done"), s("Done"), to_basemode_key),
|
|
]} else { vec![] }
|
|
}
|
|
|
|
fn shortened_shortcut_list_nonstandard_mode(help: &ModeInfo) -> LinePart {
|
|
let mut line_part = LinePart::default();
|
|
let keys_and_hints = get_keys_and_hints(help);
|
|
|
|
for (_, short, keys) in keys_and_hints.into_iter() {
|
|
line_part.append(&add_shortcut(help, &short, &keys.to_vec(), false, Some(2)));
|
|
}
|
|
line_part
|
|
}
|
|
|
|
fn shortened_shortcut_list(help: &ModeInfo) -> LinePart {
|
|
match help.mode {
|
|
InputMode::Normal => LinePart::default(),
|
|
InputMode::Locked => LinePart::default(),
|
|
_ => shortened_shortcut_list_nonstandard_mode(help),
|
|
}
|
|
}
|
|
|
|
fn best_effort_shortcut_list(help: &ModeInfo, max_len: usize) -> LinePart {
|
|
let mut line_part = LinePart::default();
|
|
let keys_and_hints = get_keys_and_hints(help);
|
|
for (_, short, keys) in keys_and_hints.into_iter() {
|
|
let shortcut = add_shortcut(help, &short, &keys.to_vec(), false, Some(2));
|
|
if line_part.len + shortcut.len + MORE_MSG.chars().count() > max_len {
|
|
line_part.part = format!("{}{}", line_part.part, MORE_MSG);
|
|
line_part.len += MORE_MSG.chars().count();
|
|
break;
|
|
} else {
|
|
line_part.append(&shortcut);
|
|
}
|
|
}
|
|
line_part
|
|
}
|
|
|
|
fn single_action_key(
|
|
keymap: &[(KeyWithModifier, Vec<Action>)],
|
|
action: &[Action],
|
|
) -> Vec<KeyWithModifier> {
|
|
let mut matching = keymap.iter().find_map(|(key, acvec)| {
|
|
if acvec.iter().next() == action.iter().next() {
|
|
Some(key.clone())
|
|
} else {
|
|
None
|
|
}
|
|
});
|
|
if let Some(matching) = matching.take() {
|
|
vec![matching]
|
|
} else {
|
|
vec![]
|
|
}
|
|
}
|
|
|
|
fn session_manager_key(keymap: &[(KeyWithModifier, Vec<Action>)]) -> Vec<KeyWithModifier> {
|
|
let mut matching = keymap.iter().find_map(|(key, acvec)| {
|
|
let has_match = acvec
|
|
.iter()
|
|
.find(|a| a.launches_plugin("session-manager"))
|
|
.is_some();
|
|
if has_match {
|
|
Some(key.clone())
|
|
} else {
|
|
None
|
|
}
|
|
});
|
|
if let Some(matching) = matching.take() {
|
|
vec![matching]
|
|
} else {
|
|
vec![]
|
|
}
|
|
}
|
|
|
|
fn style_key_with_modifier(keyvec: &[KeyWithModifier], color_index: Option<usize>) -> LinePart {
|
|
if keyvec.is_empty() {
|
|
return LinePart::default();
|
|
}
|
|
|
|
let common_modifiers = get_common_modifiers(keyvec.iter().collect());
|
|
|
|
let no_common_modifier = common_modifiers.is_empty();
|
|
let modifier_str = common_modifiers
|
|
.iter()
|
|
.map(|m| m.to_string())
|
|
.collect::<Vec<_>>()
|
|
.join("-");
|
|
|
|
// Prints the keys
|
|
let key = keyvec
|
|
.iter()
|
|
.map(|key| {
|
|
if no_common_modifier || keyvec.len() == 1 {
|
|
format!("{}", key)
|
|
} else {
|
|
format!("{}", key.strip_common_modifiers(&common_modifiers))
|
|
}
|
|
})
|
|
.collect::<Vec<String>>();
|
|
|
|
// Special handling of some pre-defined keygroups
|
|
let key_string = key.join("");
|
|
let key_separator = match &key_string[..] {
|
|
"HJKL" => "",
|
|
"hjkl" => "",
|
|
"←↓↑→" => "",
|
|
"←→" => "",
|
|
"↓↑" => "",
|
|
"[]" => "",
|
|
_ => "|",
|
|
};
|
|
|
|
if no_common_modifier || key.len() == 1 {
|
|
let key_string_text = format!(" {} ", key.join(key_separator));
|
|
let text = if let Some(color_index) = color_index {
|
|
Text::new(&key_string_text).color_range(color_index, ..)
|
|
} else {
|
|
Text::new(&key_string_text)
|
|
};
|
|
LinePart {
|
|
part: serialize_text(&text),
|
|
len: key_string_text.width(),
|
|
}
|
|
} else {
|
|
let key_string_without_modifier = format!("{}", key.join(key_separator));
|
|
let key_string_text = format!(" {} <{}> ", modifier_str, key_string_without_modifier);
|
|
let text = if let Some(color_index) = color_index {
|
|
Text::new(&key_string_text)
|
|
.color_range(color_index, ..modifier_str.width() + 1)
|
|
.color_range(
|
|
color_index,
|
|
modifier_str.width() + 3
|
|
..modifier_str.width() + 3 + key_string_without_modifier.width(),
|
|
)
|
|
} else {
|
|
Text::new(&key_string_text)
|
|
};
|
|
LinePart {
|
|
part: serialize_text(&text),
|
|
len: key_string_text.width(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_common_modifiers(mut keyvec: Vec<&KeyWithModifier>) -> Vec<KeyModifier> {
|
|
if keyvec.is_empty() {
|
|
return vec![];
|
|
}
|
|
let mut common_modifiers = keyvec.pop().unwrap().key_modifiers.clone();
|
|
for key in keyvec {
|
|
common_modifiers = common_modifiers
|
|
.intersection(&key.key_modifiers)
|
|
.cloned()
|
|
.collect();
|
|
}
|
|
common_modifiers.into_iter().collect()
|
|
}
|