zellij/default-plugins/configuration/src/rebind_leaders_screen.rs
Aram Drevekenin 3f006e14ff
feat(ui): rebind keys UI (#3686)
* rebind action working

* functional ui

* responsive ui

* some refactoring

* properly reset ui state

* minor fixes

* style(fmt): rustfmt

* style(fmt): remove dead code
2024-10-21 13:54:30 +02:00

1262 lines
48 KiB
Rust

use std::collections::BTreeSet;
use zellij_tile::prelude::*;
use crate::ui_components::{back_to_presets, info_line};
use crate::{POSSIBLE_MODIFIERS, WIDTH_BREAKPOINTS};
#[derive(Debug)]
pub struct RebindLeadersScreen {
selected_primary_key_index: usize,
selected_secondary_key_index: usize,
main_leader_selected: bool,
rebinding_main_leader: bool,
browsing_primary_modifier: bool,
browsing_secondary_modifier: bool,
main_leader: Option<KeyWithModifier>,
primary_modifier: BTreeSet<KeyModifier>,
secondary_modifier: BTreeSet<KeyModifier>,
latest_mode_info: Option<ModeInfo>,
notification: Option<String>,
is_rebinding_for_presets: bool,
ui_is_dirty: bool,
}
impl Default for RebindLeadersScreen {
fn default() -> Self {
let mut primary_modifier = BTreeSet::new();
primary_modifier.insert(KeyModifier::Ctrl);
let mut secondary_modifier = BTreeSet::new();
secondary_modifier.insert(KeyModifier::Alt);
RebindLeadersScreen {
selected_primary_key_index: 0,
selected_secondary_key_index: 0,
main_leader_selected: false,
rebinding_main_leader: false,
browsing_primary_modifier: false,
browsing_secondary_modifier: false,
main_leader: None,
primary_modifier,
secondary_modifier,
latest_mode_info: None,
notification: None,
is_rebinding_for_presets: false,
ui_is_dirty: false,
}
}
}
impl RebindLeadersScreen {
pub fn with_rebinding_for_presets(mut self) -> Self {
self.is_rebinding_for_presets = true;
self
}
pub fn with_mode_info(mut self, latest_mode_info: Option<ModeInfo>) -> Self {
self.latest_mode_info = latest_mode_info;
self.hard_reset_ui_state();
self
}
pub fn update_mode_info(&mut self, mode_info: ModeInfo) {
self.latest_mode_info = Some(mode_info);
if !self.ui_is_dirty {
self.set_main_leader_from_keybindings();
self.set_primary_and_secondary_modifiers_from_keybindings();
}
}
pub fn primary_and_secondary_modifiers(
&self,
) -> (BTreeSet<KeyModifier>, BTreeSet<KeyModifier>) {
(
self.primary_modifier.clone(),
self.secondary_modifier.clone(),
)
}
fn set_main_leader_from_keybindings(&mut self) {
self.main_leader = self.latest_mode_info.as_ref().and_then(|mode_info| {
mode_info
.keybinds
.iter()
.find_map(|m| {
if m.0 == InputMode::Locked {
Some(m.1.clone())
} else {
None
}
})
.and_then(|k| {
k.into_iter().find_map(|(k, a)| {
if a == &[actions::Action::SwitchToMode(InputMode::Normal)] {
Some(k)
} else {
None
}
})
})
});
}
fn set_primary_and_secondary_modifiers_from_keybindings(&mut self) {
let mut primary_modifier = self.latest_mode_info.as_ref().and_then(|mode_info| {
mode_info
.keybinds
.iter()
.find_map(|m| {
if m.0 == InputMode::Locked {
Some(m.1.clone())
} else {
None
}
})
.and_then(|k| {
k.into_iter().find_map(|(k, a)| {
if a == &[actions::Action::SwitchToMode(InputMode::Normal)] {
Some(k.key_modifiers.clone())
} else {
None
}
})
})
});
let mut secondary_modifier = self.latest_mode_info.as_ref().and_then(|mode_info| {
let base_mode = mode_info.base_mode.unwrap_or(InputMode::Normal);
mode_info
.keybinds
.iter()
.find_map(|m| {
if m.0 == base_mode {
Some(m.1.clone())
} else {
None
}
})
.and_then(|k| {
k.into_iter().find_map(|(k, a)| {
if a == &[actions::Action::NewPane(None, None, false)] {
Some(k.key_modifiers.clone())
} else {
None
}
})
})
});
if let Some(primary_modifier) = primary_modifier.take() {
self.primary_modifier = primary_modifier;
}
if let Some(secondary_modifier) = secondary_modifier.take() {
self.secondary_modifier = secondary_modifier;
}
}
pub fn set_unlock_toggle_selected(&mut self) {
self.main_leader_selected = true;
self.selected_primary_key_index = 0;
self.selected_secondary_key_index = 0;
self.rebinding_main_leader = false;
self.browsing_primary_modifier = false;
self.browsing_secondary_modifier = false;
}
pub fn set_primary_modifier_selected(&mut self) {
self.browsing_primary_modifier = true;
self.main_leader_selected = false;
self.selected_primary_key_index = 0;
self.selected_secondary_key_index = 0;
self.rebinding_main_leader = false;
self.browsing_secondary_modifier = false;
}
pub fn set_secondary_modifier_selected(&mut self) {
self.browsing_secondary_modifier = true;
self.browsing_primary_modifier = false;
self.main_leader_selected = false;
self.selected_primary_key_index = 0;
self.selected_secondary_key_index = 0;
self.rebinding_main_leader = false;
}
pub fn set_rebinding_unlock_toggle(&mut self) {
self.rebinding_main_leader = true;
self.browsing_secondary_modifier = false;
self.browsing_primary_modifier = false;
self.main_leader_selected = false;
self.selected_primary_key_index = 0;
self.selected_secondary_key_index = 0;
}
pub fn move_secondary_index_down(&mut self) {
if self.selected_secondary_key_index < POSSIBLE_MODIFIERS.len().saturating_sub(1) {
self.selected_secondary_key_index += 1;
} else {
self.set_unlock_toggle_selected();
}
}
pub fn move_secondary_index_up(&mut self) {
if self.selected_secondary_key_index > 0 {
self.selected_secondary_key_index -= 1;
} else {
self.set_unlock_toggle_selected();
}
}
pub fn move_selection_for_default_preset(&mut self, key: &KeyWithModifier) {
if self.browsing_primary_modifier {
if key.bare_key == BareKey::Left && key.has_no_modifiers() {
self.browsing_primary_modifier = false;
self.browsing_secondary_modifier = true;
self.selected_secondary_key_index = self.selected_primary_key_index;
} else if key.bare_key == BareKey::Right && key.has_no_modifiers() {
self.browsing_primary_modifier = false;
self.browsing_secondary_modifier = true;
self.selected_secondary_key_index = self.selected_primary_key_index;
} else if key.bare_key == BareKey::Down && key.has_no_modifiers() {
if self.selected_primary_key_index < POSSIBLE_MODIFIERS.len().saturating_sub(1) {
self.selected_primary_key_index += 1;
}
} else if key.bare_key == BareKey::Up && key.has_no_modifiers() {
if self.selected_primary_key_index > 0 {
self.selected_primary_key_index -= 1;
}
}
} else if self.browsing_secondary_modifier {
if key.bare_key == BareKey::Left && key.has_no_modifiers() {
self.browsing_secondary_modifier = false;
self.browsing_primary_modifier = true;
self.selected_primary_key_index = self.selected_secondary_key_index;
} else if key.bare_key == BareKey::Right && key.has_no_modifiers() {
self.browsing_secondary_modifier = false;
self.browsing_primary_modifier = true;
self.selected_primary_key_index = self.selected_secondary_key_index;
} else if key.bare_key == BareKey::Down && key.has_no_modifiers() {
if self.selected_secondary_key_index < POSSIBLE_MODIFIERS.len().saturating_sub(1) {
self.selected_secondary_key_index += 1;
}
} else if key.bare_key == BareKey::Up && key.has_no_modifiers() {
if self.selected_secondary_key_index > 0 {
self.selected_secondary_key_index -= 1;
}
}
} else {
self.set_primary_modifier_selected();
}
}
pub fn move_selection_for_unlock_first(&mut self, key: &KeyWithModifier) {
if self.browsing_secondary_modifier {
if (key.bare_key == BareKey::Left || key.bare_key == BareKey::Right)
&& key.has_no_modifiers()
{
self.set_unlock_toggle_selected();
} else if key.bare_key == BareKey::Down && key.has_no_modifiers() {
self.move_secondary_index_down();
} else if key.bare_key == BareKey::Up && key.has_no_modifiers() {
self.move_secondary_index_up();
}
} else if self.main_leader_selected {
if (key.bare_key == BareKey::Down
|| key.bare_key == BareKey::Up
|| key.bare_key == BareKey::Right
|| key.bare_key == BareKey::Left)
&& key.has_no_modifiers()
{
self.set_secondary_modifier_selected();
}
} else {
self.set_unlock_toggle_selected();
}
}
pub fn render(
&mut self,
rows: usize,
cols: usize,
ui_size: usize,
notification: &Option<String>,
) {
if self.is_rebinding_for_presets {
back_to_presets();
}
let notification = notification.clone().or_else(|| self.notification.clone());
if self.currently_in_unlock_first() {
self.render_unlock_first(rows, cols);
} else {
self.render_default_preset(rows, cols);
}
let warning_text = self.warning_text(cols);
info_line(rows, cols, ui_size, &notification, &warning_text, None);
}
fn render_unlock_first(&mut self, rows: usize, cols: usize) {
self.render_screen_title_unlock_first(rows, cols);
self.render_unlock_toggle(rows, cols);
self.render_secondary_modifier_selector(rows, cols);
self.render_help_text(rows, cols);
}
fn render_default_preset(&mut self, rows: usize, cols: usize) {
self.render_screen_title_default_preset(rows, cols);
self.render_primary_modifier_selector(rows, cols);
self.render_secondary_modifier_selector(rows, cols);
self.render_help_text(rows, cols);
}
fn render_primary_modifier_selector(&self, rows: usize, cols: usize) {
let screen_width = if cols >= WIDTH_BREAKPOINTS.0 {
WIDTH_BREAKPOINTS.0
} else {
WIDTH_BREAKPOINTS.1
};
let base_x = cols.saturating_sub(screen_width) / 2;
let base_y = rows.saturating_sub(7) / 2;
let primary_modifier_key_text = self.primary_modifier_text();
let (primary_modifier_text, primary_modifier_start_position) =
if cols >= WIDTH_BREAKPOINTS.0 {
(format!("Primary: {}", primary_modifier_key_text), 9)
} else {
(format!("{}", primary_modifier_key_text), 0)
};
print_text_with_coordinates(
Text::new(primary_modifier_text).color_range(3, primary_modifier_start_position..),
base_x,
base_y + 4,
None,
None,
);
print_nested_list_with_coordinates(
POSSIBLE_MODIFIERS
.iter()
.enumerate()
.map(|(i, m)| {
let item = if self.primary_modifier.contains(m) {
NestedListItem::new(m.to_string()).color_range(3, ..)
} else {
NestedListItem::new(m.to_string())
};
if self.browsing_primary_modifier && self.selected_primary_key_index == i {
item.selected()
} else {
item
}
})
.collect(),
base_x,
base_y + 5,
Some(screen_width / 2),
None,
);
}
pub fn render_screen_title_unlock_first(&self, rows: usize, cols: usize) {
let screen_width = if cols >= WIDTH_BREAKPOINTS.0 {
WIDTH_BREAKPOINTS.0
} else {
WIDTH_BREAKPOINTS.1
};
let leader_keys_text = if cols >= WIDTH_BREAKPOINTS.0 {
"Rebind leader keys (Non-Colliding preset)"
} else if cols >= WIDTH_BREAKPOINTS.1 {
"Rebind leader keys (Non-Colliding)"
} else {
"Rebind leader keys"
};
let base_x = cols.saturating_sub(screen_width) / 2;
let base_y = rows.saturating_sub(10) / 2;
let explanation_text_1 = if cols >= WIDTH_BREAKPOINTS.0 {
"Unlock toggle - used to expose the other modes (eg. PANE, TAB)"
} else if cols >= WIDTH_BREAKPOINTS.1 {
"Unlock toggle - expose other modes"
} else {
""
};
let explanation_text_2 = if cols >= WIDTH_BREAKPOINTS.0 {
"Secondary modifier - prefixes common actions (eg. New Pane)"
} else if cols >= WIDTH_BREAKPOINTS.1 {
"Secondary modifier - common actions"
} else {
""
};
print_text_with_coordinates(
Text::new(leader_keys_text).color_range(2, ..),
base_x,
base_y,
None,
None,
);
print_text_with_coordinates(
Text::new(explanation_text_1).color_range(1, ..=12),
base_x,
base_y + 2,
None,
None,
);
print_text_with_coordinates(
Text::new(explanation_text_2).color_range(1, ..=17),
base_x,
base_y + 3,
None,
None,
);
}
fn render_screen_title_default_preset(&self, rows: usize, cols: usize) {
let screen_width = if cols >= WIDTH_BREAKPOINTS.0 {
WIDTH_BREAKPOINTS.0
} else {
WIDTH_BREAKPOINTS.1
};
let leader_keys_text = if cols >= WIDTH_BREAKPOINTS.0 {
"Rebind leader keys (Default preset)"
} else {
"Rebind leader keys"
};
let base_x = cols.saturating_sub(screen_width) / 2;
let base_y = rows.saturating_sub(10) / 2;
let explanation_text_1 = if cols >= WIDTH_BREAKPOINTS.0 {
"Primary - the modifier used to switch modes (eg. PANE, TAB)"
} else if cols >= WIDTH_BREAKPOINTS.1 {
"Primary - used to switch modes"
} else {
""
};
let explanation_text_2 = if cols >= WIDTH_BREAKPOINTS.0 {
"Secondary - the modifier used for common actions (eg. New Pane)"
} else if cols >= WIDTH_BREAKPOINTS.1 {
"Secondary - common actions"
} else {
""
};
print_text_with_coordinates(
Text::new(leader_keys_text).color_range(2, ..),
base_x,
base_y,
None,
None,
);
print_text_with_coordinates(
Text::new(explanation_text_1).color_range(1, ..=6),
base_x,
base_y + 2,
None,
None,
);
print_text_with_coordinates(
Text::new(explanation_text_2).color_range(1, ..=8),
base_x,
base_y + 3,
None,
None,
);
}
fn render_unlock_toggle(&self, rows: usize, cols: usize) {
let screen_width = if cols >= WIDTH_BREAKPOINTS.0 {
WIDTH_BREAKPOINTS.0
} else {
WIDTH_BREAKPOINTS.1
};
let base_x = cols.saturating_sub(screen_width) / 2;
let base_y = rows.saturating_sub(10) / 2;
if let Some(main_leader_key_text) = self.main_leader_text() {
let main_leader_key_text = if self.rebinding_main_leader {
"...".to_owned()
} else {
main_leader_key_text
};
let (primary_modifier_text, primary_modifier_start_position) =
if cols >= WIDTH_BREAKPOINTS.0 {
(format!("Unlock Toggle: {}", main_leader_key_text), 15)
} else {
(format!("{}", main_leader_key_text), 0)
};
let mut primary_modifier =
Text::new(primary_modifier_text).color_range(3, primary_modifier_start_position..);
if self.main_leader_selected {
primary_modifier = primary_modifier.selected();
}
print_text_with_coordinates(primary_modifier, base_x, base_y + 5, None, None);
if self.rebinding_main_leader {
let first_bulletin = "[Enter new key] eg.";
let second_bulletin = "\"Ctrl g\", \"Alt g\",";
let third_bulletin = "\"Alt ESC\", \"Ctrl SPACE\"";
print_nested_list_with_coordinates(
vec![
NestedListItem::new(first_bulletin).color_range(3, ..=14),
NestedListItem::new(second_bulletin),
NestedListItem::new(third_bulletin),
],
base_x,
base_y + 6,
None,
None,
);
}
}
}
fn main_leader_text(&self) -> Option<String> {
self.main_leader.as_ref().map(|m| format!("{}", m))
}
fn render_secondary_modifier_selector(&mut self, rows: usize, cols: usize) {
let screen_width = if cols >= WIDTH_BREAKPOINTS.0 {
WIDTH_BREAKPOINTS.0
} else {
WIDTH_BREAKPOINTS.1
};
let base_x = cols.saturating_sub(screen_width) / 2;
let base_y = rows.saturating_sub(10) / 2;
let secondary_modifier_key_text = self.secondary_modifier_text();
let (secondary_modifier_text, secondary_modifier_start_position) =
if cols >= WIDTH_BREAKPOINTS.0 {
if self.currently_in_unlock_first() {
(
format!("Secondary Modifier: {}", secondary_modifier_key_text),
20,
)
} else {
(format!("Secondary: {}", secondary_modifier_key_text), 11)
}
} else {
(format!("{}", secondary_modifier_key_text), 0)
};
let secondary_modifier_menu_x_coords = base_x + (screen_width / 2);
print_text_with_coordinates(
Text::new(secondary_modifier_text).color_range(0, secondary_modifier_start_position..),
secondary_modifier_menu_x_coords,
base_y + 5,
None,
None,
);
print_nested_list_with_coordinates(
POSSIBLE_MODIFIERS
.iter()
.enumerate()
.map(|(i, m)| {
let item = if self.secondary_modifier.contains(m) {
NestedListItem::new(m.to_string()).color_range(0, ..)
} else {
NestedListItem::new(m.to_string())
};
if self.browsing_secondary_modifier && self.selected_secondary_key_index == i {
item.selected()
} else {
item
}
})
.collect(),
secondary_modifier_menu_x_coords,
base_y + 6,
Some(screen_width / 2),
None,
);
}
fn primary_modifier_text(&self) -> String {
if self.primary_modifier.is_empty() {
"<UNBOUND>".to_owned()
} else {
self.primary_modifier
.iter()
.map(|m| m.to_string())
.collect::<Vec<_>>()
.join("-")
}
}
fn secondary_modifier_text(&self) -> String {
if self.secondary_modifier.is_empty() {
"<UNBOUND>".to_owned()
} else {
self.secondary_modifier
.iter()
.map(|m| m.to_string())
.collect::<Vec<_>>()
.join("-")
}
}
fn render_help_text(&self, rows: usize, cols: usize) {
if self.is_rebinding_for_presets {
return self.render_help_text_for_presets_rebinding(rows, cols);
}
let help_text_long = "Help: <←↓↑→> - navigate, <SPACE> - select, <ENTER> - apply, <INSERT> - save, <Ctrl c> - reset, <ESC> - close";
let help_text_medium = "Help: <←↓↑→/SPACE> - navigate/select, <ENTER/INS> - apply/save, <Ctrl c> - reset, <ESC> - close";
let help_text_short = "Help: <←↓↑→>/<SPACE>/<ENTER> select/<INS> save/<Ctrl c> reset/<ESC>";
let help_text_minimum = "<←↓↑→>/<SPACE>/<ENTER>/<INS>/<Ctrl c>/<ESC>";
if cols >= help_text_long.chars().count() {
print_text_with_coordinates(
Text::new(help_text_long)
.color_range(2, 6..=12)
.color_range(2, 25..=31)
.color_range(2, 43..=49)
.color_range(2, 60..=67)
.color_range(2, 77..=84)
.color_range(2, 95..=99),
0,
rows,
None,
None,
);
} else if cols >= help_text_medium.chars().count() {
print_text_with_coordinates(
Text::new(help_text_medium)
.color_range(2, 6..=17)
.color_range(2, 38..=48)
.color_range(2, 64..=72)
.color_range(2, 82..=86),
0,
rows,
None,
None,
);
} else if cols >= help_text_short.chars().count() {
print_text_with_coordinates(
Text::new(help_text_short)
.color_range(2, 6..=11)
.color_range(2, 13..=19)
.color_range(2, 21..=27)
.color_range(2, 36..=40)
.color_range(2, 47..=54)
.color_range(2, 62..=66),
0,
rows,
None,
None,
);
} else {
print_text_with_coordinates(
Text::new(help_text_minimum)
.color_range(2, ..=5)
.color_range(2, 7..=13)
.color_range(2, 15..=21)
.color_range(2, 23..=27)
.color_range(2, 29..=36)
.color_range(2, 38..=42),
0,
rows,
None,
None,
);
}
}
fn render_help_text_for_presets_rebinding(&self, rows: usize, cols: usize) {
let help_text_long = "Help: <←↓↑→> - navigate, <SPACE> - select, <ENTER> - apply to presets in previous screen";
let help_text_medium = "Help: <←↓↑→> - navigate, <SPACE> - select, <ENTER> - apply";
let help_text_short = "<←↓↑→/SPACE> - navigate/select, <ENTER> - apply";
let help_text_minimum = "<←↓↑→>/<SPACE>/<ENTER>";
if cols >= help_text_long.chars().count() {
print_text_with_coordinates(
Text::new(help_text_long)
.color_range(2, 6..=12)
.color_range(2, 25..=31)
.color_range(2, 43..=49),
0,
rows,
None,
None,
);
} else if cols >= help_text_medium.chars().count() {
print_text_with_coordinates(
Text::new(help_text_medium)
.color_range(2, 6..=12)
.color_range(2, 25..=31)
.color_range(2, 43..=49),
0,
rows,
None,
None,
);
} else if cols >= help_text_short.chars().count() {
print_text_with_coordinates(
Text::new(help_text_short)
.color_range(2, 1..=4)
.color_range(2, 6..=10)
.color_range(2, 32..=38),
0,
rows,
None,
None,
);
} else {
print_text_with_coordinates(
Text::new(help_text_minimum)
.color_range(2, ..=5)
.color_range(2, 7..=13)
.color_range(2, 15..=21),
0,
rows,
None,
None,
);
}
}
pub fn handle_key(&mut self, key: KeyWithModifier) -> bool {
if let Some(notification) = self.notification.take() {
drop(notification);
true
} else if self.currently_in_unlock_first() {
self.handle_unlock_first_key(key)
} else {
self.handle_default_preset_key(key)
}
}
fn currently_in_unlock_first(&self) -> bool {
if self.is_rebinding_for_presets {
false
} else {
self.latest_mode_info
.as_ref()
.map(|m| m.base_mode == Some(InputMode::Locked))
.unwrap_or(false)
}
}
fn handle_default_preset_key(&mut self, key: KeyWithModifier) -> bool {
let should_render = true;
if key.bare_key == BareKey::Insert
&& key.has_no_modifiers()
&& !self.is_rebinding_for_presets
{
let write_to_disk = true;
self.rebind_keys(write_to_disk);
self.hard_reset_ui_state();
} else if key.bare_key == BareKey::Enter && key.has_no_modifiers() {
let write_to_disk = false;
self.rebind_keys(write_to_disk);
self.hard_reset_ui_state();
} else if key.is_key_with_ctrl_modifier(BareKey::Char('c')) {
self.hard_reset_ui_state();
} else if key.bare_key == BareKey::Esc && key.has_no_modifiers() {
close_self();
} else if key.bare_key == BareKey::Char(' ') && key.has_no_modifiers() {
if self.browsing_primary_modifier {
let selected_primary_key_index = self.selected_primary_key_index;
self.toggle_primary_modifier(selected_primary_key_index);
} else if self.browsing_secondary_modifier {
let selected_secondary_key_index = self.selected_secondary_key_index;
self.toggle_secondary_modifier(selected_secondary_key_index);
}
} else if (key.bare_key == BareKey::Left
|| key.bare_key == BareKey::Right
|| key.bare_key == BareKey::Up
|| key.bare_key == BareKey::Down)
&& key.has_no_modifiers()
{
self.move_selection_for_default_preset(&key);
} else if self.rebinding_main_leader {
self.soft_reset_ui_state();
self.main_leader = Some(key.clone());
self.ui_is_dirty = true;
}
should_render
}
fn toggle_secondary_modifier(&mut self, secondary_modifier_index: usize) {
if let Some(selected_modifier) = POSSIBLE_MODIFIERS.get(secondary_modifier_index) {
if self.secondary_modifier.contains(selected_modifier) {
self.secondary_modifier.remove(selected_modifier);
} else {
self.secondary_modifier.insert(*selected_modifier);
}
self.ui_is_dirty = true;
}
}
fn toggle_primary_modifier(&mut self, primary_modifier_index: usize) {
if let Some(selected_modifier) = POSSIBLE_MODIFIERS.get(primary_modifier_index) {
if self.primary_modifier.contains(selected_modifier) {
self.primary_modifier.remove(selected_modifier);
} else {
self.primary_modifier.insert(*selected_modifier);
}
self.ui_is_dirty = true;
}
}
fn rebind_keys(&mut self, write_to_disk: bool) {
let mut keys_to_unbind = vec![];
let mut keys_to_bind = vec![];
if self.currently_in_unlock_first() {
if let Some(unlock_key) = &self.main_leader {
self.bind_unlock_key(&mut keys_to_unbind, &mut keys_to_bind, unlock_key);
}
self.bind_all_secondary_actions(&mut keys_to_unbind, &mut keys_to_bind);
} else {
self.bind_all_secondary_actions(&mut keys_to_unbind, &mut keys_to_bind);
self.bind_all_primary_actions(&mut keys_to_unbind, &mut keys_to_bind);
}
if write_to_disk {
self.notification = Some("Configuration applied and saved to disk.".to_owned());
} else {
self.notification = Some("Configuration applied to current session.".to_owned());
}
rebind_keys(keys_to_unbind, keys_to_bind, write_to_disk);
}
fn bind_all_primary_actions(
&self,
keys_to_unbind: &mut Vec<(InputMode, KeyWithModifier)>,
keys_to_bind: &mut Vec<(InputMode, KeyWithModifier, Vec<actions::Action>)>,
) {
self.bind_primary_switch_to_mode_action(
keys_to_unbind,
keys_to_bind,
InputMode::Locked,
KeyWithModifier::new_with_modifiers(BareKey::Char('g'), self.primary_modifier.clone()),
);
self.bind_primary_switch_to_mode_action(
keys_to_unbind,
keys_to_bind,
InputMode::Pane,
KeyWithModifier::new_with_modifiers(BareKey::Char('p'), self.primary_modifier.clone()),
);
self.bind_primary_switch_to_mode_action(
keys_to_unbind,
keys_to_bind,
InputMode::Tab,
KeyWithModifier::new_with_modifiers(BareKey::Char('t'), self.primary_modifier.clone()),
);
self.bind_primary_switch_to_mode_action(
keys_to_unbind,
keys_to_bind,
InputMode::Resize,
KeyWithModifier::new_with_modifiers(BareKey::Char('n'), self.primary_modifier.clone()),
);
self.bind_primary_switch_to_mode_action(
keys_to_unbind,
keys_to_bind,
InputMode::Move,
KeyWithModifier::new_with_modifiers(BareKey::Char('h'), self.primary_modifier.clone()),
);
self.bind_primary_switch_to_mode_action(
keys_to_unbind,
keys_to_bind,
InputMode::Scroll,
KeyWithModifier::new_with_modifiers(BareKey::Char('s'), self.primary_modifier.clone()),
);
self.bind_primary_switch_to_mode_action(
keys_to_unbind,
keys_to_bind,
InputMode::Session,
KeyWithModifier::new_with_modifiers(BareKey::Char('o'), self.primary_modifier.clone()),
);
self.bind_quit_action(
keys_to_unbind,
keys_to_bind,
KeyWithModifier::new_with_modifiers(BareKey::Char('q'), self.primary_modifier.clone()),
);
}
fn bind_quit_action(
&self,
keys_to_unbind: &mut Vec<(InputMode, KeyWithModifier)>,
keys_to_bind: &mut Vec<(InputMode, KeyWithModifier, Vec<actions::Action>)>,
new_key: KeyWithModifier,
) {
let all_relevant_modes = vec![
InputMode::Normal,
InputMode::Pane,
InputMode::Tab,
InputMode::Resize,
InputMode::Move,
InputMode::Search,
InputMode::Scroll,
InputMode::Session,
];
for mode in &all_relevant_modes {
for current_keybind in self.get_current_keybinds(*mode, &[actions::Action::Quit]) {
keys_to_unbind.push((*mode, current_keybind));
}
keys_to_bind.push((*mode, new_key.clone(), vec![actions::Action::Quit]));
}
}
fn get_current_keybinds(
&self,
in_mode: InputMode,
actions: &[actions::Action],
) -> Vec<KeyWithModifier> {
self.latest_mode_info
.as_ref()
.and_then(|m_i| {
m_i.keybinds
.iter()
.find_map(|m| if m.0 == in_mode { Some(&m.1) } else { None })
})
.map(|k| {
k.into_iter()
.filter_map(|(k, a)| if a == actions { Some(k.clone()) } else { None })
.collect::<Vec<_>>()
})
.unwrap_or_else(Default::default)
}
fn bind_primary_switch_to_mode_action(
&self,
keys_to_unbind: &mut Vec<(InputMode, KeyWithModifier)>,
keys_to_bind: &mut Vec<(InputMode, KeyWithModifier, Vec<actions::Action>)>,
target_mode: InputMode,
new_key: KeyWithModifier,
) {
let all_relevant_modes = vec![
InputMode::Locked,
InputMode::Normal,
InputMode::Pane,
InputMode::Tab,
InputMode::Resize,
InputMode::Move,
InputMode::Search,
InputMode::Scroll,
InputMode::Session,
];
for mode in &all_relevant_modes {
if mode == &target_mode {
for current_keybind in self.get_current_keybinds(
*mode,
&[actions::Action::SwitchToMode(InputMode::Normal)],
) {
if current_keybind.bare_key != BareKey::Enter
&& current_keybind.bare_key != BareKey::Esc
{
keys_to_unbind.push((*mode, current_keybind));
}
}
} else {
for current_keybind in
self.get_current_keybinds(*mode, &[actions::Action::SwitchToMode(target_mode)])
{
keys_to_unbind.push((*mode, current_keybind));
}
}
}
for mode in &all_relevant_modes {
if mode == &target_mode {
keys_to_bind.push((
*mode,
new_key.clone(),
vec![actions::Action::SwitchToMode(InputMode::Normal)],
));
} else if mode != &InputMode::Locked {
keys_to_bind.push((
*mode,
new_key.clone(),
vec![actions::Action::SwitchToMode(target_mode)],
));
}
}
}
fn bind_all_secondary_actions(
&self,
keys_to_unbind: &mut Vec<(InputMode, KeyWithModifier)>,
keys_to_bind: &mut Vec<(InputMode, KeyWithModifier, Vec<actions::Action>)>,
) {
self.bind_actions(
keys_to_unbind,
keys_to_bind,
&[actions::Action::NewPane(None, None, false)],
KeyWithModifier::new_with_modifiers(
BareKey::Char('n'),
self.secondary_modifier.clone(),
),
);
self.bind_actions(
keys_to_unbind,
keys_to_bind,
&[actions::Action::ToggleFloatingPanes],
KeyWithModifier::new_with_modifiers(
BareKey::Char('f'),
self.secondary_modifier.clone(),
),
);
self.bind_actions(
keys_to_unbind,
keys_to_bind,
&[actions::Action::MoveTab(Direction::Left)],
KeyWithModifier::new_with_modifiers(
BareKey::Char('i'),
self.secondary_modifier.clone(),
),
);
self.bind_actions(
keys_to_unbind,
keys_to_bind,
&[actions::Action::MoveTab(Direction::Right)],
KeyWithModifier::new_with_modifiers(
BareKey::Char('o'),
self.secondary_modifier.clone(),
),
);
self.bind_actions(
keys_to_unbind,
keys_to_bind,
&[actions::Action::MoveFocusOrTab(Direction::Left)],
KeyWithModifier::new_with_modifiers(
BareKey::Char('h'),
self.secondary_modifier.clone(),
),
);
self.bind_actions(
keys_to_unbind,
keys_to_bind,
&[actions::Action::MoveFocusOrTab(Direction::Left)],
KeyWithModifier::new_with_modifiers(BareKey::Left, self.secondary_modifier.clone()),
);
self.bind_actions(
keys_to_unbind,
keys_to_bind,
&[actions::Action::MoveFocusOrTab(Direction::Right)],
KeyWithModifier::new_with_modifiers(
BareKey::Char('l'),
self.secondary_modifier.clone(),
),
);
self.bind_actions(
keys_to_unbind,
keys_to_bind,
&[actions::Action::MoveFocusOrTab(Direction::Right)],
KeyWithModifier::new_with_modifiers(BareKey::Right, self.secondary_modifier.clone()),
);
self.bind_actions(
keys_to_unbind,
keys_to_bind,
&[actions::Action::MoveFocus(Direction::Down)],
KeyWithModifier::new_with_modifiers(
BareKey::Char('j'),
self.secondary_modifier.clone(),
),
);
self.bind_actions(
keys_to_unbind,
keys_to_bind,
&[actions::Action::MoveFocus(Direction::Down)],
KeyWithModifier::new_with_modifiers(BareKey::Down, self.secondary_modifier.clone()),
);
self.bind_actions(
keys_to_unbind,
keys_to_bind,
&[actions::Action::MoveFocus(Direction::Up)],
KeyWithModifier::new_with_modifiers(
BareKey::Char('k'),
self.secondary_modifier.clone(),
),
);
self.bind_actions(
keys_to_unbind,
keys_to_bind,
&[actions::Action::MoveFocus(Direction::Up)],
KeyWithModifier::new_with_modifiers(BareKey::Up, self.secondary_modifier.clone()),
);
self.bind_actions(
keys_to_unbind,
keys_to_bind,
&[actions::Action::Resize(Resize::Increase, None)],
KeyWithModifier::new_with_modifiers(
BareKey::Char('+'),
self.secondary_modifier.clone(),
),
);
self.bind_actions(
keys_to_unbind,
keys_to_bind,
&[actions::Action::Resize(Resize::Increase, None)],
KeyWithModifier::new_with_modifiers(
BareKey::Char('='),
self.secondary_modifier.clone(),
),
);
self.bind_actions(
keys_to_unbind,
keys_to_bind,
&[actions::Action::Resize(Resize::Decrease, None)],
KeyWithModifier::new_with_modifiers(
BareKey::Char('-'),
self.secondary_modifier.clone(),
),
);
self.bind_actions(
keys_to_unbind,
keys_to_bind,
&[actions::Action::PreviousSwapLayout],
KeyWithModifier::new_with_modifiers(
BareKey::Char('['),
self.secondary_modifier.clone(),
),
);
self.bind_actions(
keys_to_unbind,
keys_to_bind,
&[actions::Action::NextSwapLayout],
KeyWithModifier::new_with_modifiers(
BareKey::Char(']'),
self.secondary_modifier.clone(),
),
);
}
fn bind_actions(
&self,
keys_to_unbind: &mut Vec<(InputMode, KeyWithModifier)>,
keys_to_bind: &mut Vec<(InputMode, KeyWithModifier, Vec<actions::Action>)>,
actions: &[actions::Action],
key: KeyWithModifier,
) {
for current_keybind in self.get_current_keybinds(InputMode::Normal, actions) {
keys_to_unbind.push((InputMode::Normal, current_keybind));
}
for current_keybind in self.get_current_keybinds(InputMode::Locked, actions) {
keys_to_unbind.push((InputMode::Locked, current_keybind));
}
keys_to_bind.push((InputMode::Normal, key.clone(), actions.to_vec()));
keys_to_bind.push((InputMode::Locked, key, actions.to_vec()));
}
fn bind_unlock_key(
&self,
keys_to_unbind: &mut Vec<(InputMode, KeyWithModifier)>,
keys_to_bind: &mut Vec<(InputMode, KeyWithModifier, Vec<actions::Action>)>,
unlock_key: &KeyWithModifier,
) {
if let Some(previous_unlock_key) = self.get_current_keybind(
InputMode::Locked,
&[actions::Action::SwitchToMode(InputMode::Normal)],
) {
keys_to_unbind.push((InputMode::Locked, previous_unlock_key.clone()));
keys_to_unbind.push((InputMode::Normal, previous_unlock_key.clone()));
keys_to_unbind.push((InputMode::Pane, previous_unlock_key.clone()));
keys_to_unbind.push((InputMode::Tab, previous_unlock_key.clone()));
keys_to_unbind.push((InputMode::Resize, previous_unlock_key.clone()));
keys_to_unbind.push((InputMode::Move, previous_unlock_key.clone()));
keys_to_unbind.push((InputMode::Search, previous_unlock_key.clone()));
keys_to_unbind.push((InputMode::Scroll, previous_unlock_key.clone()));
keys_to_unbind.push((InputMode::Session, previous_unlock_key.clone()));
}
keys_to_bind.push((
InputMode::Locked,
unlock_key.clone(),
vec![actions::Action::SwitchToMode(InputMode::Normal)],
));
keys_to_bind.push((
InputMode::Normal,
unlock_key.clone(),
vec![actions::Action::SwitchToMode(InputMode::Locked)],
));
keys_to_bind.push((
InputMode::Pane,
unlock_key.clone(),
vec![actions::Action::SwitchToMode(InputMode::Locked)],
));
keys_to_bind.push((
InputMode::Tab,
unlock_key.clone(),
vec![actions::Action::SwitchToMode(InputMode::Locked)],
));
keys_to_bind.push((
InputMode::Resize,
unlock_key.clone(),
vec![actions::Action::SwitchToMode(InputMode::Locked)],
));
keys_to_bind.push((
InputMode::Move,
unlock_key.clone(),
vec![actions::Action::SwitchToMode(InputMode::Locked)],
));
keys_to_bind.push((
InputMode::Search,
unlock_key.clone(),
vec![actions::Action::SwitchToMode(InputMode::Locked)],
));
keys_to_bind.push((
InputMode::Scroll,
unlock_key.clone(),
vec![actions::Action::SwitchToMode(InputMode::Locked)],
));
keys_to_bind.push((
InputMode::Session,
unlock_key.clone(),
vec![actions::Action::SwitchToMode(InputMode::Locked)],
));
}
fn get_current_keybind(
&self,
in_mode: InputMode,
actions: &[actions::Action],
) -> Option<KeyWithModifier> {
self.latest_mode_info
.as_ref()
.and_then(|m_i| {
m_i.keybinds
.iter()
.find_map(|m| if m.0 == in_mode { Some(&m.1) } else { None })
})
.and_then(|k| {
k.into_iter()
.find_map(|(k, a)| if a == actions { Some(k) } else { None })
})
.cloned()
}
fn soft_reset_ui_state(&mut self) {
let mut latest_mode_info = self.latest_mode_info.take();
let notification = self.notification.take();
let primary_modifier = self.primary_modifier.clone();
let secondary_modifier = self.secondary_modifier.clone();
let main_leader = self.main_leader.clone();
let ui_is_dirty = self.ui_is_dirty;
let is_rebinding_for_presets = self.is_rebinding_for_presets;
*self = Default::default();
if let Some(latest_mode_info) = latest_mode_info.take() {
self.update_mode_info(latest_mode_info);
}
self.notification = notification;
self.primary_modifier = primary_modifier;
self.secondary_modifier = secondary_modifier;
self.main_leader = main_leader;
self.ui_is_dirty = ui_is_dirty;
self.is_rebinding_for_presets = is_rebinding_for_presets;
}
fn hard_reset_ui_state(&mut self) {
let mut latest_mode_info = self.latest_mode_info.take();
let notification = self.notification.take();
let is_rebinding_for_presets = self.is_rebinding_for_presets;
*self = Default::default();
if let Some(latest_mode_info) = latest_mode_info.take() {
self.update_mode_info(latest_mode_info);
}
self.notification = notification;
self.is_rebinding_for_presets = is_rebinding_for_presets;
}
fn handle_unlock_first_key(&mut self, key: KeyWithModifier) -> bool {
if key.bare_key == BareKey::Insert
&& key.has_no_modifiers()
&& !self.is_rebinding_for_presets
{
let write_to_disk = true;
self.rebind_keys(write_to_disk);
self.hard_reset_ui_state();
} else if key.bare_key == BareKey::Enter && key.has_no_modifiers() {
let write_to_disk = false;
self.rebind_keys(write_to_disk);
self.hard_reset_ui_state();
} else if key.is_key_with_ctrl_modifier(BareKey::Char('c')) {
self.hard_reset_ui_state();
} else if key.bare_key == BareKey::Esc && key.has_no_modifiers() {
if self.rebinding_main_leader {
self.soft_reset_ui_state();
} else {
close_self();
}
} else if key.bare_key == BareKey::Char(' ') && key.has_no_modifiers() {
if self.main_leader_selected {
self.set_rebinding_unlock_toggle();
} else if self.browsing_secondary_modifier {
let selected_secondary_key_index = self.selected_secondary_key_index;
self.toggle_secondary_modifier(selected_secondary_key_index);
}
} else if (key.bare_key == BareKey::Left
|| key.bare_key == BareKey::Right
|| key.bare_key == BareKey::Up
|| key.bare_key == BareKey::Down)
&& key.has_no_modifiers()
{
self.move_selection_for_unlock_first(&key);
} else if self.rebinding_main_leader {
self.soft_reset_ui_state();
self.main_leader = Some(key.clone());
self.ui_is_dirty = true;
}
true
}
fn warning_text(&self, max_width: usize) -> Option<String> {
if self.needs_kitty_support() {
if max_width >= 38 {
Some(String::from("Warning: requires supporting terminal."))
} else {
Some(String::from("Requires supporting terminal"))
}
} else if self.primary_modifier.is_empty() && self.secondary_modifier.is_empty() {
if max_width >= 49 {
Some(String::from(
"Warning: no leaders defined. UI will be disabled.",
))
} else {
Some(String::from("No leaders. UI will be unusable."))
}
} else {
None
}
}
fn needs_kitty_support(&self) -> bool {
self.primary_modifier.len() > 1
|| self.secondary_modifier.len() > 1
|| self.primary_modifier.contains(&KeyModifier::Super)
|| self.secondary_modifier.contains(&KeyModifier::Super)
}
}