diff --git a/Cargo.lock b/Cargo.lock index cfb94204..6588cffb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -749,6 +749,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "configuration" +version = "0.1.0" +dependencies = [ + "ansi_term", + "chrono", + "zellij-tile", +] + [[package]] name = "console" version = "0.15.0" diff --git a/Cargo.toml b/Cargo.toml index 26987d01..63a663fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ members = [ "default-plugins/tab-bar", "default-plugins/fixture-plugin-for-tests", "default-plugins/session-manager", + "default-plugins/configuration", "zellij-client", "zellij-server", "zellij-utils", diff --git a/default-plugins/configuration/.cargo/config.toml b/default-plugins/configuration/.cargo/config.toml new file mode 100644 index 00000000..bc255e30 --- /dev/null +++ b/default-plugins/configuration/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-wasi" \ No newline at end of file diff --git a/default-plugins/configuration/.gitignore b/default-plugins/configuration/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/default-plugins/configuration/.gitignore @@ -0,0 +1 @@ +/target diff --git a/default-plugins/configuration/Cargo.toml b/default-plugins/configuration/Cargo.toml new file mode 100644 index 00000000..52242f3d --- /dev/null +++ b/default-plugins/configuration/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "configuration" +version = "0.1.0" +authors = ["Aram Drevekenin "] +edition = "2021" +license = "MIT" + +[dependencies] +ansi_term = "0.12.1" +zellij-tile = { path = "../../zellij-tile" } +chrono = "0.4.0" diff --git a/default-plugins/configuration/LICENSE.md b/default-plugins/configuration/LICENSE.md new file mode 120000 index 00000000..f0608a63 --- /dev/null +++ b/default-plugins/configuration/LICENSE.md @@ -0,0 +1 @@ +../../LICENSE.md \ No newline at end of file diff --git a/default-plugins/configuration/src/main.rs b/default-plugins/configuration/src/main.rs new file mode 100644 index 00000000..db0219c5 --- /dev/null +++ b/default-plugins/configuration/src/main.rs @@ -0,0 +1,1987 @@ +use zellij_tile::prelude::*; + +use std::collections::{BTreeMap, BTreeSet}; + +struct State { + userspace_configuration: BTreeMap, + selected_index: Option, + selected_primary_key_index: usize, + selected_secondary_key_index: usize, + remapping_leaders: bool, + primary_modifier: BTreeSet, + secondary_modifier: BTreeSet, + possible_modifiers: Vec, + browsing_secondary_modifier: bool, + mode_color_index: usize, + preset_color_index: usize, + primary_leader_key_color_index: usize, + secondary_leader_key_color_index: usize, +} + +impl Default for State { + 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); + State { + userspace_configuration: BTreeMap::new(), + selected_index: None, + selected_primary_key_index: 0, + selected_secondary_key_index: 0, + remapping_leaders: false, + primary_modifier, + secondary_modifier, + possible_modifiers: vec![ + KeyModifier::Ctrl, + KeyModifier::Alt, + KeyModifier::Super, + KeyModifier::Shift, + ], + browsing_secondary_modifier: false, + primary_leader_key_color_index: 3, + secondary_leader_key_color_index: 0, + mode_color_index: 2, + preset_color_index: 1, + } + } +} + +register_plugin!(State); + +impl ZellijPlugin for State { + fn load(&mut self, configuration: BTreeMap) { + self.userspace_configuration = configuration; + // we need the ReadApplicationState permission to receive the ModeUpdate and TabUpdate + // events + // we need the RunCommands permission to run "cargo test" in a floating window + request_permission(&[ + PermissionType::ReadApplicationState, + PermissionType::RunCommands, + PermissionType::ReadCliPipes, + PermissionType::MessageAndLaunchOtherPlugins, + PermissionType::Reconfigure, + PermissionType::ChangeApplicationState, + ]); + subscribe(&[ + EventType::ModeUpdate, + EventType::TabUpdate, + EventType::Key, + EventType::Timer, + EventType::PermissionRequestResult, + ]); + } + fn update(&mut self, event: Event) -> bool { + let mut should_render = false; + match event { + Event::PermissionRequestResult(_) => { + should_render = true; + }, + Event::Key(key) => { + if self.remapping_leaders { + should_render = self.handle_remapping_screen_key(key); + } else { + should_render = self.handle_main_screen_key(key); + } + }, + _ => (), + }; + should_render + } + fn render(&mut self, rows: usize, cols: usize) { + if self.remapping_leaders { + self.render_remapping_leaders_screen(rows, cols); + } else { + self.render_main_screen(rows, cols); + } + } +} + +impl State { + fn handle_remapping_screen_key(&mut self, key: KeyWithModifier) -> bool { + let mut should_render = false; + if self.browsing_secondary_modifier { + if key.bare_key == BareKey::Left && key.has_no_modifiers() { + self.browsing_secondary_modifier = false; + self.selected_primary_key_index = self.selected_secondary_key_index; + should_render = true; + } else if key.bare_key == BareKey::Right && key.has_no_modifiers() { + self.browsing_secondary_modifier = false; + self.selected_primary_key_index = self.selected_secondary_key_index; + should_render = true; + } else if key.bare_key == BareKey::Down && key.has_no_modifiers() { + if self.selected_secondary_key_index + < self.possible_modifiers.len().saturating_sub(1) + { + self.selected_secondary_key_index += 1; + } else { + self.selected_secondary_key_index = 0; + } + should_render = true; + } 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.selected_secondary_key_index = + self.possible_modifiers.len().saturating_sub(1); + } + should_render = true; + } else if key.bare_key == BareKey::Char(' ') && key.has_no_modifiers() { + if let Some(selected_modifier) = self + .possible_modifiers + .get(self.selected_secondary_key_index) + { + if self.secondary_modifier.contains(selected_modifier) { + self.secondary_modifier.remove(selected_modifier); + } else { + self.secondary_modifier.insert(*selected_modifier); + } + should_render = true; + } + } + } else { + if key.bare_key == BareKey::Left && key.has_no_modifiers() { + self.browsing_secondary_modifier = true; + self.selected_secondary_key_index = self.selected_primary_key_index; + should_render = true; + } else if key.bare_key == BareKey::Right && key.has_no_modifiers() { + self.browsing_secondary_modifier = true; + self.selected_secondary_key_index = self.selected_primary_key_index; + should_render = true; + } else if key.bare_key == BareKey::Down && key.has_no_modifiers() { + if self.selected_primary_key_index < self.possible_modifiers.len().saturating_sub(1) + { + self.selected_primary_key_index += 1; + } else { + self.selected_primary_key_index = 0; + } + should_render = true; + } 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 { + self.selected_primary_key_index = + self.possible_modifiers.len().saturating_sub(1); + } + should_render = true; + } else if key.bare_key == BareKey::Char(' ') && key.has_no_modifiers() { + if let Some(selected_modifier) = + self.possible_modifiers.get(self.selected_primary_key_index) + { + if self.primary_modifier.contains(selected_modifier) { + self.primary_modifier.remove(selected_modifier); + } else { + self.primary_modifier.insert(*selected_modifier); + } + should_render = true; + } + } + } + if key.bare_key == BareKey::Enter { + self.remapping_leaders = false; + should_render = true; + } + should_render + } + fn handle_main_screen_key(&mut self, key: KeyWithModifier) -> bool { + let mut should_render = false; + if key.bare_key == BareKey::Down && key.has_no_modifiers() { + if self.selected_index.is_none() { + self.selected_index = Some(0); + } else if self.selected_index < Some(1) { + self.selected_index = Some(1); + } else { + self.selected_index = None; + } + should_render = true; + } else if key.bare_key == BareKey::Up && key.has_no_modifiers() { + if self.selected_index.is_none() { + self.selected_index = Some(1); + } else if self.selected_index == Some(1) { + self.selected_index = Some(0); + } else { + self.selected_index = None; + } + should_render = true; + } else if key.bare_key == BareKey::Enter && key.has_no_modifiers() { + if let Some(selected) = self.selected_index.take() { + if selected == 0 { + // TODO: these should be part of a "transaction" when they are + // implemented + reconfigure(default_keybinds( + self.primary_modifier + .iter() + .map(|m| m.to_string()) + .collect::>() + .join(" "), + self.secondary_modifier + .iter() + .map(|m| m.to_string()) + .collect::>() + .join(" "), + )); + switch_to_input_mode(&InputMode::Normal); + } else if selected == 1 { + // TODO: these should be part of a "transaction" when they are + // implemented + reconfigure(unlock_first_keybinds( + self.primary_modifier + .iter() + .map(|m| m.to_string()) + .collect::>() + .join(" "), + self.secondary_modifier + .iter() + .map(|m| m.to_string()) + .collect::>() + .join(" "), + )); + switch_to_input_mode(&InputMode::Locked); + } + should_render = true; + } + } else if key.bare_key == BareKey::Char('l') && key.has_no_modifiers() { + self.remapping_leaders = true; + should_render = true; + } else if key.bare_key == BareKey::Esc && key.has_no_modifiers() { + close_self(); + should_render = true; + } + should_render + } + fn render_selection_keymap(&self, rows: usize, cols: usize) { + let widths = self.remapping_screen_widths(); + if cols >= widths.0 { + let mut x = cols.saturating_sub(10) / 2; + let mut y = rows.saturating_sub(7) / 2; + if self.browsing_secondary_modifier { + x += 31; + y += self.selected_secondary_key_index; + } else { + y += self.selected_primary_key_index; + } + let text = "<←↓↑→> / "; + let text_len = text.chars().count(); + let text = Text::new(text) + .color_range(2, 1..5) + .color_range(2, 10..15) + .selected(); + print_text_with_coordinates(text, x.saturating_sub(text_len), y + 3, None, None); + } + } + fn render_remapping_screen_title(&self, rows: usize, cols: usize) { + let widths = self.remapping_screen_widths(); + let screen_width = if cols >= widths.0 { + widths.0 + } else if cols >= widths.1 { + widths.1 + } else { + widths.2 + }; + let leader_keys_text = if cols >= widths.0 { + "Adjust leader keys for the presets in the previous screen:" + } else { + "Adjust leader keys:" + }; + let base_x = cols.saturating_sub(screen_width) / 2; + let base_y = rows.saturating_sub(7) / 2; + print_text_with_coordinates( + Text::new(leader_keys_text).color_range(2, ..), + base_x, + base_y, + None, + None, + ); + } + fn render_primary_modifier_selector(&self, rows: usize, cols: usize) { + let widths = self.remapping_screen_widths(); + let screen_width = if cols >= widths.0 { + widths.0 + } else if cols >= widths.1 { + widths.1 + } else { + widths.2 + }; + self.render_remapping_screen_title(rows, cols); + 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 >= widths.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( + self.primary_leader_key_color_index, + primary_modifier_start_position.., + ), + base_x, + base_y + 2, + None, + None, + ); + print_nested_list_with_coordinates( + self.possible_modifiers + .iter() + .enumerate() + .map(|(i, m)| { + let item = if self.primary_modifier.contains(m) { + NestedListItem::new(m.to_string()) + .color_range(self.primary_leader_key_color_index, ..) + } else { + NestedListItem::new(m.to_string()) + }; + if !self.browsing_secondary_modifier && self.selected_primary_key_index == i { + item.selected() + } else { + item + } + }) + .collect(), + base_x, + base_y + 3, + Some(screen_width / 2), + None, + ); + } + fn render_secondary_modifier_selector(&mut self, rows: usize, cols: usize) { + let widths = self.remapping_screen_widths(); + let screen_width = if cols >= widths.0 { + widths.0 + } else if cols >= widths.1 { + widths.1 + } else { + widths.2 + }; + let base_x = cols.saturating_sub(screen_width) / 2; + let base_y = rows.saturating_sub(7) / 2; + let secondary_modifier_key_text = self.secondary_modifier_text(); + let (secondary_modifier_text, secondary_modifier_start_position) = if cols >= widths.0 { + (format!("Secondary: {}", secondary_modifier_key_text), 10) + } 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( + self.secondary_leader_key_color_index, + secondary_modifier_start_position.., + ), + secondary_modifier_menu_x_coords, + base_y + 2, + None, + None, + ); + print_nested_list_with_coordinates( + self.possible_modifiers + .iter() + .enumerate() + .map(|(i, m)| { + let item = if self.secondary_modifier.contains(m) { + NestedListItem::new(m.to_string()) + .color_range(self.secondary_leader_key_color_index, ..) + } 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 + 3, + Some(screen_width / 2), + None, + ); + } + fn render_remapping_leaders_screen(&mut self, rows: usize, cols: usize) { + self.render_remapping_screen_title(rows, cols); + self.render_primary_modifier_selector(rows, cols); + self.render_secondary_modifier_selector(rows, cols); + self.render_selection_keymap(rows, cols); + self.render_help_text_remapping(rows, cols); + } + fn render_override_title(&self, rows: usize, cols: usize, primary_modifier_key_text: &str) { + let widths = self.main_screen_widths(primary_modifier_key_text); + if cols >= widths.0 { + let title_text = "Override keybindings with one of the following presets:"; + let left_padding = cols.saturating_sub(widths.0) / 2; + print_text_with_coordinates( + Text::new(title_text).color_range(2, ..), + left_padding, + rows.saturating_sub(15) / 2, + None, + None, + ); + } else { + let title_text = "Override keybindings:"; + let left_padding = if cols >= widths.1 { + cols.saturating_sub(widths.1) / 2 + } else { + cols.saturating_sub(widths.2) / 2 + }; + print_text_with_coordinates( + Text::new(title_text).color_range(2, ..), + left_padding, + rows.saturating_sub(15) / 2, + None, + None, + ); + } + } + fn render_first_bulletin(&self, rows: usize, cols: usize, primary_modifier_key_text: &str) { + let widths = self.main_screen_widths(primary_modifier_key_text); + let primary_modifier_key_text_len = primary_modifier_key_text.chars().count(); + let default_text = "1. Default"; + let (mut list_items, max_width) = if cols >= widths.0 { + let list_items = vec![ + NestedListItem::new(default_text).color_range(self.preset_color_index, ..), + NestedListItem::new("All modes available directly from the base mode, eg.:") + .indent(1), + NestedListItem::new(format!( + "{} p - to enter PANE mode", + primary_modifier_key_text + )) + .color_range( + self.primary_leader_key_color_index, + ..primary_modifier_key_text_len + 3, + ) + .color_range( + self.mode_color_index, + primary_modifier_key_text_len + 14..primary_modifier_key_text_len + 18, + ) + .indent(1), + NestedListItem::new(format!( + "{} t - to enter TAB mode.", + primary_modifier_key_text + )) + .color_range( + self.primary_leader_key_color_index, + ..primary_modifier_key_text_len + 3, + ) + .color_range( + self.mode_color_index, + primary_modifier_key_text_len + 14..primary_modifier_key_text_len + 17, + ) + .indent(1), + ]; + let max_width = widths.0; + (list_items, max_width) + } else if cols >= widths.1 { + let list_items = vec![ + NestedListItem::new(default_text).color_range(self.preset_color_index, ..), + NestedListItem::new("Modes available directly, eg.:").indent(1), + NestedListItem::new(format!( + "{} p - to enter PANE mode", + primary_modifier_key_text + )) + .indent(1) + .color_range( + self.primary_leader_key_color_index, + ..primary_modifier_key_text_len + 3, + ) + .color_range( + self.mode_color_index, + primary_modifier_key_text_len + 14..primary_modifier_key_text_len + 18, + ), + NestedListItem::new(format!( + "{} t - to enter TAB mode.", + primary_modifier_key_text + )) + .indent(1) + .color_range( + self.primary_leader_key_color_index, + ..primary_modifier_key_text_len + 3, + ) + .color_range( + self.mode_color_index, + primary_modifier_key_text_len + 14..primary_modifier_key_text_len + 17, + ), + ]; + let max_width = widths.1; + (list_items, max_width) + } else { + let list_items = vec![ + NestedListItem::new(default_text).color_range(self.preset_color_index, ..), + NestedListItem::new("Directly, eg.:").indent(1), + NestedListItem::new(format!("{} p - PANE mode", primary_modifier_key_text)) + .color_range( + self.primary_leader_key_color_index, + ..primary_modifier_key_text_len + 3, + ) + .color_range( + self.mode_color_index, + primary_modifier_key_text_len + 5..primary_modifier_key_text_len + 10, + ) + .indent(1), + NestedListItem::new(format!("{} t - TAB mode.", primary_modifier_key_text)) + .color_range( + self.primary_leader_key_color_index, + ..primary_modifier_key_text_len + 3, + ) + .color_range( + self.mode_color_index, + primary_modifier_key_text_len + 5..primary_modifier_key_text_len + 9, + ) + .indent(1), + ]; + let max_width = widths.2; + (list_items, max_width) + }; + if self.selected_index == Some(0) { + list_items = list_items.drain(..).map(|i| i.selected()).collect(); + } + let left_padding = cols.saturating_sub(max_width) / 2; + let top_coordinates = if rows > 14 { + (rows.saturating_sub(15) / 2) + 2 + } else { + (rows.saturating_sub(15) / 2) + 1 + }; + print_nested_list_with_coordinates( + list_items, + left_padding, + top_coordinates, + Some(max_width), + None, + ); + } + fn render_second_bulletin(&self, rows: usize, cols: usize, primary_modifier_key_text: &str) { + let unlock_first_text = "2. Unlock First (non-colliding)"; + let widths = self.main_screen_widths(primary_modifier_key_text); + let primary_modifier_key_text_len = primary_modifier_key_text.chars().count(); + let (mut list_items, max_width) = if cols >= widths.0 { + let list_items = vec![ + NestedListItem::new(unlock_first_text).color_range(self.preset_color_index, ..), + NestedListItem::new(format!( + "Single key modes available after unlocking with {} g, eg.:", + primary_modifier_key_text + )) + .indent(1), + NestedListItem::new(format!( + "{} g + p to enter PANE mode", + primary_modifier_key_text + )) + .indent(1) + .color_range( + self.primary_leader_key_color_index, + ..primary_modifier_key_text_len + 3, + ) + .color_range( + self.primary_leader_key_color_index, + primary_modifier_key_text_len + 5..primary_modifier_key_text_len + 7, + ) + .color_range( + self.mode_color_index, + primary_modifier_key_text_len + 16..primary_modifier_key_text_len + 21, + ), + NestedListItem::new(format!( + "{} g + t to enter TAB mode.", + primary_modifier_key_text + )) + .indent(1) + .color_range( + self.primary_leader_key_color_index, + ..primary_modifier_key_text_len + 3, + ) + .color_range( + self.primary_leader_key_color_index, + primary_modifier_key_text_len + 5..primary_modifier_key_text_len + 7, + ) + .color_range( + self.mode_color_index, + primary_modifier_key_text_len + 16..primary_modifier_key_text_len + 20, + ), + ]; + let max_width = widths.0; + (list_items, max_width) + } else if cols >= widths.1 { + let list_items = vec![ + NestedListItem::new(unlock_first_text).color_range(self.preset_color_index, ..), + NestedListItem::new(format!( + "Single key modes after {} g, eg.:", + primary_modifier_key_text + )) + .indent(1), + NestedListItem::new(format!( + "{} g + p to enter PANE mode", + primary_modifier_key_text + )) + .color_range( + self.primary_leader_key_color_index, + ..primary_modifier_key_text_len + 3, + ) + .color_range( + self.primary_leader_key_color_index, + primary_modifier_key_text_len + 5..primary_modifier_key_text_len + 7, + ) + .color_range( + self.mode_color_index, + primary_modifier_key_text_len + 16..primary_modifier_key_text_len + 21, + ) + .indent(1), + NestedListItem::new(format!( + "{} g + t to enter TAB mode.", + primary_modifier_key_text + )) + .color_range( + self.primary_leader_key_color_index, + ..primary_modifier_key_text_len + 3, + ) + .color_range( + self.primary_leader_key_color_index, + primary_modifier_key_text_len + 5..primary_modifier_key_text_len + 7, + ) + .color_range( + self.mode_color_index, + primary_modifier_key_text_len + 16..primary_modifier_key_text_len + 20, + ) + .indent(1), + ]; + let max_width = widths.1; + (list_items, max_width) + } else { + let list_items = vec![ + NestedListItem::new("2. Unlock First").color_range(self.preset_color_index, ..), + NestedListItem::new(format!( + "{} g + single key, eg.:", + primary_modifier_key_text + )) + .indent(1), + NestedListItem::new(format!("{} g + p PANE mode", primary_modifier_key_text)) + .color_range( + self.primary_leader_key_color_index, + ..primary_modifier_key_text_len + 3, + ) + .color_range( + self.primary_leader_key_color_index, + primary_modifier_key_text_len + 5..primary_modifier_key_text_len + 7, + ) + .color_range( + self.mode_color_index, + primary_modifier_key_text_len + 7..primary_modifier_key_text_len + 11, + ) + .indent(1), + NestedListItem::new(format!("{} g + t TAB mode.", primary_modifier_key_text)) + .color_range( + self.primary_leader_key_color_index, + ..primary_modifier_key_text_len + 3, + ) + .color_range( + self.primary_leader_key_color_index, + primary_modifier_key_text_len + 5..primary_modifier_key_text_len + 7, + ) + .color_range( + self.mode_color_index, + primary_modifier_key_text_len + 7..primary_modifier_key_text_len + 10, + ) + .indent(1), + ]; + let max_width = widths.2; + (list_items, max_width) + }; + if self.selected_index == Some(1) { + list_items = list_items.drain(..).map(|i| i.selected()).collect(); + } + let left_padding = cols.saturating_sub(max_width) / 2; + let top_coordinates = if rows > 14 { + (rows.saturating_sub(15) / 2) + 7 + } else { + (rows.saturating_sub(15) / 2) + 5 + }; + print_nested_list_with_coordinates( + list_items, + left_padding, + top_coordinates, + Some(max_width), + None, + ); + } + fn render_leader_keys_indication( + &self, + rows: usize, + cols: usize, + primary_modifier_key_text: &str, + secondary_modifier_key_text: &str, + ) { + let widths = self.main_screen_widths(primary_modifier_key_text); + let primary_modifier_key_text_len = primary_modifier_key_text.chars().count(); + let secondary_modifier_key_text_len = secondary_modifier_key_text.chars().count(); + let top_coordinates = if rows > 14 { + (rows.saturating_sub(15) / 2) + 12 + } else { + (rows.saturating_sub(15) / 2) + 9 + }; + + if cols >= widths.0 { + let leader_key_text = format!( + "Leader keys: {} - modes, {} - quicknav and shortcuts.", + primary_modifier_key_text, secondary_modifier_key_text + ); + let left_padding = cols.saturating_sub(widths.0) / 2; + print_text_with_coordinates( + Text::new(leader_key_text) + .color_range(2, ..12) + .color_range( + self.primary_leader_key_color_index, + 13..primary_modifier_key_text_len + 14, + ) + .color_range( + self.secondary_leader_key_color_index, + primary_modifier_key_text_len + 23 + ..primary_modifier_key_text_len + 23 + secondary_modifier_key_text_len, + ), + left_padding, + top_coordinates, + None, + None, + ) + } else { + let leader_key_text = format!( + "Leaders: {}, {}", + primary_modifier_key_text, secondary_modifier_key_text + ); + let left_padding = if cols >= widths.1 { + cols.saturating_sub(widths.1) / 2 + } else { + cols.saturating_sub(widths.2) / 2 + }; + print_text_with_coordinates( + Text::new(leader_key_text) + .color_range(2, ..8) + .color_range( + self.primary_leader_key_color_index, + 9..primary_modifier_key_text_len + 10, + ) + .color_range( + self.secondary_leader_key_color_index, + primary_modifier_key_text_len + 11 + ..primary_modifier_key_text_len + 12 + secondary_modifier_key_text_len, + ), + left_padding, + top_coordinates, + None, + None, + ) + }; + } + fn render_warning_if_needed(&self, rows: usize, cols: usize, primary_modifier_key_text: &str) { + let widths = self.main_screen_widths(primary_modifier_key_text); + let top_coordinates = if rows > 14 { + (rows.saturating_sub(15) / 2) + 14 + } else { + (rows.saturating_sub(15) / 2) + 10 + }; + let left_padding = if cols >= widths.0 { + cols.saturating_sub(widths.0) / 2 + } else if cols >= widths.1 { + cols.saturating_sub(widths.1) / 2 + } else { + cols.saturating_sub(widths.2) / 2 + }; + if let Some(warning_text) = self.warning_text(cols) { + print_text_with_coordinates( + Text::new(warning_text).color_range(3, ..), + left_padding, + top_coordinates, + None, + None, + ); + } + } + fn render_help_text_main(&self, rows: usize, cols: usize, primary_modifier_key_text: &str) { + let widths = self.main_screen_widths(primary_modifier_key_text); + if cols >= widths.0 { + let help_text = "Help: <↓↑/ENTER> - navigate/select, - leaders, - close"; + print_text_with_coordinates( + Text::new(help_text) + .color_range(2, 6..16) + .color_range(2, 36..39) + .color_range(2, 51..56), + 0, + rows, + None, + None, + ); + } else { + let help_text = "Help: <↓↑> / / / "; + print_text_with_coordinates( + Text::new(help_text) + .color_range(2, 6..10) + .color_range(2, 13..20) + .color_range(2, 23..26) + .color_range(2, 29..34), + 0, + rows, + None, + None, + ); + } + } + fn render_help_text_remapping(&self, rows: usize, cols: usize) { + let widths = self.remapping_screen_widths(); + if cols >= widths.0 { + let help_text = "Help: - when done"; + print_text_with_coordinates( + Text::new(help_text).color_range(2, 6..13), + 0, + rows, + None, + None, + ); + } else { + let help_text = "Help: / <←↓↑→> / "; + print_text_with_coordinates( + Text::new(help_text) + .color_range(2, 6..13) + .color_range(2, 16..22) + .color_range(2, 25..32), + 0, + rows, + None, + None, + ); + } + } + fn primary_modifier_text(&self) -> String { + if self.primary_modifier.is_empty() { + "".to_owned() + } else { + self.primary_modifier + .iter() + .map(|m| m.to_string()) + .collect::>() + .join("-") + } + } + fn secondary_modifier_text(&self) -> String { + if self.secondary_modifier.is_empty() { + "".to_owned() + } else { + self.secondary_modifier + .iter() + .map(|m| m.to_string()) + .collect::>() + .join("-") + } + } + fn main_screen_widths(&self, primary_modifier_text: &str) -> (usize, usize, usize) { + let primary_modifier_key_text_len = primary_modifier_text.chars().count(); + let full_width = 61 + primary_modifier_key_text_len; + let mid_width = 36 + primary_modifier_key_text_len; + let min_width = 26 + primary_modifier_key_text_len; + (full_width, mid_width, min_width) + } + fn remapping_screen_widths(&self) -> (usize, usize, usize) { + let full_width = 62; + let mid_width = 42; + let min_width = 30; + (full_width, mid_width, min_width) + } + fn render_main_screen(&mut self, rows: usize, cols: usize) { + let primary_modifier_key_text = self.primary_modifier_text(); + let secondary_modifier_key_text = self.secondary_modifier_text(); + self.render_override_title(rows, cols, &primary_modifier_key_text); + self.render_first_bulletin(rows, cols, &primary_modifier_key_text); + self.render_second_bulletin(rows, cols, &primary_modifier_key_text); + self.render_leader_keys_indication( + rows, + cols, + &primary_modifier_key_text, + &secondary_modifier_key_text, + ); + self.render_warning_if_needed(rows, cols, &primary_modifier_key_text); + self.render_help_text_main(rows, cols, &primary_modifier_key_text); + } + fn warning_text(&self, max_width: usize) -> Option { + if self.needs_kitty_support() { + // TODO: some widget to test support by detecting pressed keys + 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 disabled.")) + } + } 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) + } +} + +fn unlock_first_keybinds(primary_modifier: String, secondary_modifier: String) -> String { + format!( + r#" +default_mode "locked" +keybinds clear-defaults=true {{ + normal {{ + }} + locked {{ + bind "{primary_modifier} g" {{ SwitchToMode "Normal"; }} + }} + resize {{ + bind "r" {{ SwitchToMode "Normal"; }} + bind "h" "Left" {{ Resize "Increase Left"; }} + bind "j" "Down" {{ Resize "Increase Down"; }} + bind "k" "Up" {{ Resize "Increase Up"; }} + bind "l" "Right" {{ Resize "Increase Right"; }} + bind "H" {{ Resize "Decrease Left"; }} + bind "J" {{ Resize "Decrease Down"; }} + bind "K" {{ Resize "Decrease Up"; }} + bind "L" {{ Resize "Decrease Right"; }} + bind "=" "+" {{ Resize "Increase"; }} + bind "-" {{ Resize "Decrease"; }} + }} + pane {{ + bind "p" {{ SwitchToMode "Normal"; }} + bind "h" "Left" {{ MoveFocus "Left"; }} + bind "l" "Right" {{ MoveFocus "Right"; }} + bind "j" "Down" {{ MoveFocus "Down"; }} + bind "k" "Up" {{ MoveFocus "Up"; }} + bind "Tab" {{ SwitchFocus; }} + bind "n" {{ NewPane; SwitchToMode "Locked"; }} + bind "d" {{ NewPane "Down"; SwitchToMode "Locked"; }} + bind "r" {{ NewPane "Right"; SwitchToMode "Locked"; }} + bind "x" {{ CloseFocus; SwitchToMode "Locked"; }} + bind "f" {{ ToggleFocusFullscreen; SwitchToMode "Locked"; }} + bind "z" {{ TogglePaneFrames; SwitchToMode "Locked"; }} + bind "w" {{ ToggleFloatingPanes; SwitchToMode "Locked"; }} + bind "e" {{ TogglePaneEmbedOrFloating; SwitchToMode "Locked"; }} + bind "c" {{ SwitchToMode "RenamePane"; PaneNameInput 0;}} + }} + move {{ + bind "m" {{ SwitchToMode "Normal"; }} + bind "n" "Tab" {{ MovePane; }} + bind "p" {{ MovePaneBackwards; }} + bind "h" "Left" {{ MovePane "Left"; }} + bind "j" "Down" {{ MovePane "Down"; }} + bind "k" "Up" {{ MovePane "Up"; }} + bind "l" "Right" {{ MovePane "Right"; }} + }} + tab {{ + bind "t" {{ SwitchToMode "Normal"; }} + bind "r" {{ SwitchToMode "RenameTab"; TabNameInput 0; }} + bind "h" "Left" "Up" "k" {{ GoToPreviousTab; }} + bind "l" "Right" "Down" "j" {{ GoToNextTab; }} + bind "n" {{ NewTab; SwitchToMode "Locked"; }} + bind "x" {{ CloseTab; SwitchToMode "Locked"; }} + bind "s" {{ ToggleActiveSyncTab; SwitchToMode "Locked"; }} + bind "b" {{ BreakPane; SwitchToMode "Locked"; }} + bind "]" {{ BreakPaneRight; SwitchToMode "Locked"; }} + bind "[" {{ BreakPaneLeft; SwitchToMode "Locked"; }} + bind "1" {{ GoToTab 1; SwitchToMode "Locked"; }} + bind "2" {{ GoToTab 2; SwitchToMode "Locked"; }} + bind "3" {{ GoToTab 3; SwitchToMode "Locked"; }} + bind "4" {{ GoToTab 4; SwitchToMode "Locked"; }} + bind "5" {{ GoToTab 5; SwitchToMode "Locked"; }} + bind "6" {{ GoToTab 6; SwitchToMode "Locked"; }} + bind "7" {{ GoToTab 7; SwitchToMode "Locked"; }} + bind "8" {{ GoToTab 8; SwitchToMode "Locked"; }} + bind "9" {{ GoToTab 9; SwitchToMode "Locked"; }} + bind "Tab" {{ ToggleTab; }} + }} + scroll {{ + bind "s" {{ SwitchToMode "Normal"; }} + bind "e" {{ EditScrollback; SwitchToMode "Locked"; }} + bind "f" {{ SwitchToMode "EnterSearch"; SearchInput 0; }} + bind "Ctrl c" {{ ScrollToBottom; SwitchToMode "Locked"; }} + bind "j" "Down" {{ ScrollDown; }} + bind "k" "Up" {{ ScrollUp; }} + bind "Ctrl f" "PageDown" "Right" "l" {{ PageScrollDown; }} + bind "Ctrl b" "PageUp" "Left" "h" {{ PageScrollUp; }} + bind "d" {{ HalfPageScrollDown; }} + bind "u" {{ HalfPageScrollUp; }} + }} + search {{ + bind "Ctrl c" {{ ScrollToBottom; SwitchToMode "Locked"; }} + bind "j" "Down" {{ ScrollDown; }} + bind "k" "Up" {{ ScrollUp; }} + bind "Ctrl f" "PageDown" "Right" "l" {{ PageScrollDown; }} + bind "Ctrl b" "PageUp" "Left" "h" {{ PageScrollUp; }} + bind "d" {{ HalfPageScrollDown; }} + bind "u" {{ HalfPageScrollUp; }} + bind "n" {{ Search "down"; }} + bind "p" {{ Search "up"; }} + bind "c" {{ SearchToggleOption "CaseSensitivity"; }} + bind "w" {{ SearchToggleOption "Wrap"; }} + bind "o" {{ SearchToggleOption "WholeWord"; }} + }} + entersearch {{ + bind "Ctrl c" "Esc" {{ SwitchToMode "Scroll"; }} + bind "Enter" {{ SwitchToMode "Search"; }} + }} + renametab {{ + bind "Ctrl c" "Enter" {{ SwitchToMode "Locked"; }} + bind "Esc" {{ UndoRenameTab; SwitchToMode "Tab"; }} + }} + renamepane {{ + bind "Ctrl c" "Enter" {{ SwitchToMode "Locked"; }} + bind "Esc" {{ UndoRenamePane; SwitchToMode "Pane"; }} + }} + session {{ + bind "o" {{ SwitchToMode "Normal"; }} + bind "{primary_modifier} s" {{ SwitchToMode "Scroll"; }} + bind "d" {{ Detach; }} + bind "w" {{ + LaunchOrFocusPlugin "session-manager" {{ + floating true + move_to_focused_tab true + }}; + SwitchToMode "Locked" + }} + bind "c" {{ + LaunchOrFocusPlugin "configuration" {{ + floating true + move_to_focused_tab true + }}; + SwitchToMode "Locked" + }} + }} + shared_except "locked" "renametab" "renamepane" {{ + bind "{primary_modifier} g" {{ SwitchToMode "Locked"; }} + bind "{primary_modifier} q" {{ Quit; }} + }} + shared_among "normal" "locked" {{ + bind "{secondary_modifier} n" {{ NewPane; }} + bind "{secondary_modifier} f" {{ ToggleFloatingPanes; }} + bind "{secondary_modifier} i" {{ MoveTab "Left"; }} + bind "{secondary_modifier} o" {{ MoveTab "Right"; }} + bind "{secondary_modifier} h" "{secondary_modifier} Left" {{ MoveFocusOrTab "Left"; }} + bind "{secondary_modifier} l" "{secondary_modifier} Right" {{ MoveFocusOrTab "Right"; }} + bind "{secondary_modifier} j" "{secondary_modifier} Down" {{ MoveFocus "Down"; }} + bind "{secondary_modifier} k" "{secondary_modifier} Up" {{ MoveFocus "Up"; }} + bind "{secondary_modifier} =" "{secondary_modifier} +" {{ Resize "Increase"; }} + bind "{secondary_modifier} -" {{ Resize "Decrease"; }} + bind "{secondary_modifier} [" {{ PreviousSwapLayout; }} + bind "{secondary_modifier} ]" {{ NextSwapLayout; }} + }} + shared_except "locked" "renametab" "renamepane" {{ + bind "Enter" {{ SwitchToMode "Locked"; }} + }} + shared_except "pane" "locked" "renametab" "renamepane" "entersearch" {{ + bind "p" {{ SwitchToMode "Pane"; }} + }} + shared_except "resize" "locked" "renametab" "renamepane" "entersearch" {{ + bind "r" {{ SwitchToMode "Resize"; }} + }} + shared_except "scroll" "locked" "renametab" "renamepane" "entersearch" {{ + bind "s" {{ SwitchToMode "Scroll"; }} + }} + shared_except "session" "locked" "renametab" "renamepane" "entersearch" {{ + bind "o" {{ SwitchToMode "Session"; }} + }} + shared_except "tab" "locked" "renametab" "renamepane" "entersearch" {{ + bind "t" {{ SwitchToMode "Tab"; }} + }} + shared_except "move" "locked" "renametab" "renamepane" "entersearch" {{ + bind "m" {{ SwitchToMode "Move"; }} + }} +}}"# + ) +} + +fn default_keybinds(primary_modifier: String, secondary_modifier: String) -> String { + if primary_modifier.is_empty() && secondary_modifier.is_empty() { + return default_keybinds_no_modifiers(); + } else if primary_modifier == secondary_modifier { + return non_colliding_default_keybinds(primary_modifier, secondary_modifier); + } else if primary_modifier.is_empty() { + return default_keybinds_no_primary_modifier(secondary_modifier); + } else if secondary_modifier.is_empty() { + return default_keybinds_no_secondary_modifier(primary_modifier); + } + format!( + r#" +default_mode "normal" +keybinds clear-defaults=true {{ + normal {{}} + locked {{ + bind "{primary_modifier} g" {{ SwitchToMode "Normal"; }} + }} + resize {{ + bind "{primary_modifier} n" {{ SwitchToMode "Normal"; }} + bind "h" "Left" {{ Resize "Increase Left"; }} + bind "j" "Down" {{ Resize "Increase Down"; }} + bind "k" "Up" {{ Resize "Increase Up"; }} + bind "l" "Right" {{ Resize "Increase Right"; }} + bind "H" {{ Resize "Decrease Left"; }} + bind "J" {{ Resize "Decrease Down"; }} + bind "K" {{ Resize "Decrease Up"; }} + bind "L" {{ Resize "Decrease Right"; }} + bind "=" "+" {{ Resize "Increase"; }} + bind "-" {{ Resize "Decrease"; }} + }} + pane {{ + bind "{primary_modifier} p" {{ SwitchToMode "Normal"; }} + bind "h" "Left" {{ MoveFocus "Left"; }} + bind "l" "Right" {{ MoveFocus "Right"; }} + bind "j" "Down" {{ MoveFocus "Down"; }} + bind "k" "Up" {{ MoveFocus "Up"; }} + bind "p" {{ SwitchFocus; }} + bind "n" {{ NewPane; SwitchToMode "Normal"; }} + bind "d" {{ NewPane "Down"; SwitchToMode "Normal"; }} + bind "r" {{ NewPane "Right"; SwitchToMode "Normal"; }} + bind "x" {{ CloseFocus; SwitchToMode "Normal"; }} + bind "f" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }} + bind "z" {{ TogglePaneFrames; SwitchToMode "Normal"; }} + bind "w" {{ ToggleFloatingPanes; SwitchToMode "Normal"; }} + bind "e" {{ TogglePaneEmbedOrFloating; SwitchToMode "Normal"; }} + bind "c" {{ SwitchToMode "RenamePane"; PaneNameInput 0;}} + }} + move {{ + bind "{primary_modifier} h" {{ SwitchToMode "Normal"; }} + bind "n" "Tab" {{ MovePane; }} + bind "p" {{ MovePaneBackwards; }} + bind "h" "Left" {{ MovePane "Left"; }} + bind "j" "Down" {{ MovePane "Down"; }} + bind "k" "Up" {{ MovePane "Up"; }} + bind "l" "Right" {{ MovePane "Right"; }} + }} + tab {{ + bind "{primary_modifier} t" {{ SwitchToMode "Normal"; }} + bind "r" {{ SwitchToMode "RenameTab"; TabNameInput 0; }} + bind "h" "Left" "Up" "k" {{ GoToPreviousTab; }} + bind "l" "Right" "Down" "j" {{ GoToNextTab; }} + bind "n" {{ NewTab; SwitchToMode "Normal"; }} + bind "x" {{ CloseTab; SwitchToMode "Normal"; }} + bind "s" {{ ToggleActiveSyncTab; SwitchToMode "Normal"; }} + bind "b" {{ BreakPane; SwitchToMode "Normal"; }} + bind "]" {{ BreakPaneRight; SwitchToMode "Normal"; }} + bind "[" {{ BreakPaneLeft; SwitchToMode "Normal"; }} + bind "1" {{ GoToTab 1; SwitchToMode "Normal"; }} + bind "2" {{ GoToTab 2; SwitchToMode "Normal"; }} + bind "3" {{ GoToTab 3; SwitchToMode "Normal"; }} + bind "4" {{ GoToTab 4; SwitchToMode "Normal"; }} + bind "5" {{ GoToTab 5; SwitchToMode "Normal"; }} + bind "6" {{ GoToTab 6; SwitchToMode "Normal"; }} + bind "7" {{ GoToTab 7; SwitchToMode "Normal"; }} + bind "8" {{ GoToTab 8; SwitchToMode "Normal"; }} + bind "9" {{ GoToTab 9; SwitchToMode "Normal"; }} + bind "Tab" {{ ToggleTab; }} + }} + scroll {{ + bind "{primary_modifier} s" {{ SwitchToMode "Normal"; }} + bind "e" {{ EditScrollback; SwitchToMode "Normal"; }} + bind "s" {{ SwitchToMode "EnterSearch"; SearchInput 0; }} + bind "Ctrl c" {{ ScrollToBottom; SwitchToMode "Normal"; }} + bind "j" "Down" {{ ScrollDown; }} + bind "k" "Up" {{ ScrollUp; }} + bind "Ctrl f" "PageDown" "Right" "l" {{ PageScrollDown; }} + bind "Ctrl b" "PageUp" "Left" "h" {{ PageScrollUp; }} + bind "d" {{ HalfPageScrollDown; }} + bind "u" {{ HalfPageScrollUp; }} + }} + search {{ + bind "{primary_modifier} s" {{ SwitchToMode "Normal"; }} + bind "Ctrl c" {{ ScrollToBottom; SwitchToMode "Normal"; }} + bind "j" "Down" {{ ScrollDown; }} + bind "k" "Up" {{ ScrollUp; }} + bind "Ctrl f" "PageDown" "Right" "l" {{ PageScrollDown; }} + bind "Ctrl b" "PageUp" "Left" "h" {{ PageScrollUp; }} + bind "d" {{ HalfPageScrollDown; }} + bind "u" {{ HalfPageScrollUp; }} + bind "n" {{ Search "down"; }} + bind "p" {{ Search "up"; }} + bind "c" {{ SearchToggleOption "CaseSensitivity"; }} + bind "w" {{ SearchToggleOption "Wrap"; }} + bind "o" {{ SearchToggleOption "WholeWord"; }} + }} + entersearch {{ + bind "Ctrl c" "Esc" {{ SwitchToMode "Scroll"; }} + bind "Enter" {{ SwitchToMode "Search"; }} + }} + renametab {{ + bind "Ctrl c" {{ SwitchToMode "Normal"; }} + bind "Esc" {{ UndoRenameTab; SwitchToMode "Tab"; }} + }} + renamepane {{ + bind "Ctrl c" {{ SwitchToMode "Normal"; }} + bind "Esc" {{ UndoRenamePane; SwitchToMode "Pane"; }} + }} + session {{ + bind "{primary_modifier} o" {{ SwitchToMode "Normal"; }} + bind "{primary_modifier} s" {{ SwitchToMode "Scroll"; }} + bind "d" {{ Detach; }} + bind "w" {{ + LaunchOrFocusPlugin "session-manager" {{ + floating true + move_to_focused_tab true + }}; + SwitchToMode "Normal" + }} + bind "c" {{ + LaunchOrFocusPlugin "configuration" {{ + floating true + move_to_focused_tab true + }}; + SwitchToMode "Normal" + }} + }} + tmux {{ + bind "[" {{ SwitchToMode "Scroll"; }} + bind "{primary_modifier} b" {{ Write 2; SwitchToMode "Normal"; }} + bind "\"" {{ NewPane "Down"; SwitchToMode "Normal"; }} + bind "%" {{ NewPane "Right"; SwitchToMode "Normal"; }} + bind "z" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }} + bind "c" {{ NewTab; SwitchToMode "Normal"; }} + bind "," {{ SwitchToMode "RenameTab"; }} + bind "p" {{ GoToPreviousTab; SwitchToMode "Normal"; }} + bind "n" {{ GoToNextTab; SwitchToMode "Normal"; }} + bind "Left" {{ MoveFocus "Left"; SwitchToMode "Normal"; }} + bind "Right" {{ MoveFocus "Right"; SwitchToMode "Normal"; }} + bind "Down" {{ MoveFocus "Down"; SwitchToMode "Normal"; }} + bind "Up" {{ MoveFocus "Up"; SwitchToMode "Normal"; }} + bind "h" {{ MoveFocus "Left"; SwitchToMode "Normal"; }} + bind "l" {{ MoveFocus "Right"; SwitchToMode "Normal"; }} + bind "j" {{ MoveFocus "Down"; SwitchToMode "Normal"; }} + bind "k" {{ MoveFocus "Up"; SwitchToMode "Normal"; }} + bind "o" {{ FocusNextPane; }} + bind "d" {{ Detach; }} + bind "Space" {{ NextSwapLayout; }} + bind "x" {{ CloseFocus; SwitchToMode "Normal"; }} + }} + shared_except "locked" {{ + bind "{primary_modifier} g" {{ SwitchToMode "Locked"; }} + bind "{primary_modifier} q" {{ Quit; }} + bind "{secondary_modifier} f" {{ ToggleFloatingPanes; }} + bind "{secondary_modifier} n" {{ NewPane; }} + bind "{secondary_modifier} i" {{ MoveTab "Left"; }} + bind "{secondary_modifier} o" {{ MoveTab "Right"; }} + bind "{secondary_modifier} h" "{secondary_modifier} Left" {{ MoveFocusOrTab "Left"; }} + bind "{secondary_modifier} l" "{secondary_modifier} Right" {{ MoveFocusOrTab "Right"; }} + bind "{secondary_modifier} j" "{secondary_modifier} Down" {{ MoveFocus "Down"; }} + bind "{secondary_modifier} k" "{secondary_modifier} Up" {{ MoveFocus "Up"; }} + bind "{secondary_modifier} =" "{secondary_modifier} +" {{ Resize "Increase"; }} + bind "{secondary_modifier} -" {{ Resize "Decrease"; }} + bind "{secondary_modifier} [" {{ PreviousSwapLayout; }} + bind "{secondary_modifier} ]" {{ NextSwapLayout; }} + }} + shared_except "normal" "locked" {{ + bind "Enter" "Esc" {{ SwitchToMode "Normal"; }} + }} + shared_except "pane" "locked" {{ + bind "{primary_modifier} p" {{ SwitchToMode "Pane"; }} + }} + shared_except "resize" "locked" {{ + bind "{primary_modifier} n" {{ SwitchToMode "Resize"; }} + }} + shared_except "scroll" "locked" {{ + bind "{primary_modifier} s" {{ SwitchToMode "Scroll"; }} + }} + shared_except "session" "locked" {{ + bind "{primary_modifier} o" {{ SwitchToMode "Session"; }} + }} + shared_except "tab" "locked" {{ + bind "{primary_modifier} t" {{ SwitchToMode "Tab"; }} + }} + shared_except "move" "locked" {{ + bind "{primary_modifier} h" {{ SwitchToMode "Move"; }} + }} + shared_except "tmux" "locked" {{ + bind "{primary_modifier} b" {{ SwitchToMode "Tmux"; }} + }} +}} +"# + ) +} + +fn default_keybinds_no_primary_modifier(secondary_modifier: String) -> String { + format!( + r#" +default_mode "normal" +keybinds clear-defaults=true {{ + normal {{}} + locked {{}} + resize {{ + bind "h" "Left" {{ Resize "Increase Left"; }} + bind "j" "Down" {{ Resize "Increase Down"; }} + bind "k" "Up" {{ Resize "Increase Up"; }} + bind "l" "Right" {{ Resize "Increase Right"; }} + bind "H" {{ Resize "Decrease Left"; }} + bind "J" {{ Resize "Decrease Down"; }} + bind "K" {{ Resize "Decrease Up"; }} + bind "L" {{ Resize "Decrease Right"; }} + bind "=" "+" {{ Resize "Increase"; }} + bind "-" {{ Resize "Decrease"; }} + }} + pane {{ + bind "h" "Left" {{ MoveFocus "Left"; }} + bind "l" "Right" {{ MoveFocus "Right"; }} + bind "j" "Down" {{ MoveFocus "Down"; }} + bind "k" "Up" {{ MoveFocus "Up"; }} + bind "p" {{ SwitchFocus; }} + bind "n" {{ NewPane; SwitchToMode "Normal"; }} + bind "d" {{ NewPane "Down"; SwitchToMode "Normal"; }} + bind "r" {{ NewPane "Right"; SwitchToMode "Normal"; }} + bind "x" {{ CloseFocus; SwitchToMode "Normal"; }} + bind "f" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }} + bind "z" {{ TogglePaneFrames; SwitchToMode "Normal"; }} + bind "w" {{ ToggleFloatingPanes; SwitchToMode "Normal"; }} + bind "e" {{ TogglePaneEmbedOrFloating; SwitchToMode "Normal"; }} + bind "c" {{ SwitchToMode "RenamePane"; PaneNameInput 0;}} + }} + move {{ + bind "n" "Tab" {{ MovePane; }} + bind "p" {{ MovePaneBackwards; }} + bind "h" "Left" {{ MovePane "Left"; }} + bind "j" "Down" {{ MovePane "Down"; }} + bind "k" "Up" {{ MovePane "Up"; }} + bind "l" "Right" {{ MovePane "Right"; }} + }} + tab {{ + bind "r" {{ SwitchToMode "RenameTab"; TabNameInput 0; }} + bind "h" "Left" "Up" "k" {{ GoToPreviousTab; }} + bind "l" "Right" "Down" "j" {{ GoToNextTab; }} + bind "n" {{ NewTab; SwitchToMode "Normal"; }} + bind "x" {{ CloseTab; SwitchToMode "Normal"; }} + bind "s" {{ ToggleActiveSyncTab; SwitchToMode "Normal"; }} + bind "b" {{ BreakPane; SwitchToMode "Normal"; }} + bind "]" {{ BreakPaneRight; SwitchToMode "Normal"; }} + bind "[" {{ BreakPaneLeft; SwitchToMode "Normal"; }} + bind "1" {{ GoToTab 1; SwitchToMode "Normal"; }} + bind "2" {{ GoToTab 2; SwitchToMode "Normal"; }} + bind "3" {{ GoToTab 3; SwitchToMode "Normal"; }} + bind "4" {{ GoToTab 4; SwitchToMode "Normal"; }} + bind "5" {{ GoToTab 5; SwitchToMode "Normal"; }} + bind "6" {{ GoToTab 6; SwitchToMode "Normal"; }} + bind "7" {{ GoToTab 7; SwitchToMode "Normal"; }} + bind "8" {{ GoToTab 8; SwitchToMode "Normal"; }} + bind "9" {{ GoToTab 9; SwitchToMode "Normal"; }} + bind "Tab" {{ ToggleTab; }} + }} + scroll {{ + bind "e" {{ EditScrollback; SwitchToMode "Normal"; }} + bind "s" {{ SwitchToMode "EnterSearch"; SearchInput 0; }} + bind "j" "Down" {{ ScrollDown; }} + bind "k" "Up" {{ ScrollUp; }} + bind "d" {{ HalfPageScrollDown; }} + bind "u" {{ HalfPageScrollUp; }} + }} + search {{ + bind "Ctrl c" {{ ScrollToBottom; SwitchToMode "Normal"; }} + bind "j" "Down" {{ ScrollDown; }} + bind "k" "Up" {{ ScrollUp; }} + bind "Ctrl f" "PageDown" "Right" "l" {{ PageScrollDown; }} + bind "Ctrl b" "PageUp" "Left" "h" {{ PageScrollUp; }} + bind "d" {{ HalfPageScrollDown; }} + bind "u" {{ HalfPageScrollUp; }} + bind "n" {{ Search "down"; }} + bind "p" {{ Search "up"; }} + bind "c" {{ SearchToggleOption "CaseSensitivity"; }} + bind "w" {{ SearchToggleOption "Wrap"; }} + bind "o" {{ SearchToggleOption "WholeWord"; }} + }} + entersearch {{ + bind "Ctrl c" "Esc" {{ SwitchToMode "Scroll"; }} + bind "Enter" {{ SwitchToMode "Search"; }} + }} + renametab {{ + bind "Ctrl c" {{ SwitchToMode "Normal"; }} + bind "Esc" {{ UndoRenameTab; SwitchToMode "Tab"; }} + }} + renamepane {{ + bind "Ctrl c" {{ SwitchToMode "Normal"; }} + bind "Esc" {{ UndoRenamePane; SwitchToMode "Pane"; }} + }} + session {{ + bind "d" {{ Detach; }} + bind "w" {{ + LaunchOrFocusPlugin "session-manager" {{ + floating true + move_to_focused_tab true + }}; + SwitchToMode "Normal" + }} + bind "c" {{ + LaunchOrFocusPlugin "configuration" {{ + floating true + move_to_focused_tab true + }}; + SwitchToMode "Normal" + }} + }} + tmux {{ + bind "[" {{ SwitchToMode "Scroll"; }} + bind "\"" {{ NewPane "Down"; SwitchToMode "Normal"; }} + bind "%" {{ NewPane "Right"; SwitchToMode "Normal"; }} + bind "z" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }} + bind "c" {{ NewTab; SwitchToMode "Normal"; }} + bind "," {{ SwitchToMode "RenameTab"; }} + bind "p" {{ GoToPreviousTab; SwitchToMode "Normal"; }} + bind "n" {{ GoToNextTab; SwitchToMode "Normal"; }} + bind "Left" {{ MoveFocus "Left"; SwitchToMode "Normal"; }} + bind "Right" {{ MoveFocus "Right"; SwitchToMode "Normal"; }} + bind "Down" {{ MoveFocus "Down"; SwitchToMode "Normal"; }} + bind "Up" {{ MoveFocus "Up"; SwitchToMode "Normal"; }} + bind "h" {{ MoveFocus "Left"; SwitchToMode "Normal"; }} + bind "l" {{ MoveFocus "Right"; SwitchToMode "Normal"; }} + bind "j" {{ MoveFocus "Down"; SwitchToMode "Normal"; }} + bind "k" {{ MoveFocus "Up"; SwitchToMode "Normal"; }} + bind "o" {{ FocusNextPane; }} + bind "d" {{ Detach; }} + bind "Space" {{ NextSwapLayout; }} + bind "x" {{ CloseFocus; SwitchToMode "Normal"; }} + }} + shared_except "locked" {{ + bind "{secondary_modifier} n" {{ NewPane; }} + bind "{secondary_modifier} f" {{ ToggleFloatingPanes; }} + bind "{secondary_modifier} i" {{ MoveTab "Left"; }} + bind "{secondary_modifier} o" {{ MoveTab "Right"; }} + bind "{secondary_modifier} h" "{secondary_modifier} Left" {{ MoveFocusOrTab "Left"; }} + bind "{secondary_modifier} l" "{secondary_modifier} Right" {{ MoveFocusOrTab "Right"; }} + bind "{secondary_modifier} j" "{secondary_modifier} Down" {{ MoveFocus "Down"; }} + bind "{secondary_modifier} k" "{secondary_modifier} Up" {{ MoveFocus "Up"; }} + bind "{secondary_modifier} =" "{secondary_modifier} +" {{ Resize "Increase"; }} + bind "{secondary_modifier} -" {{ Resize "Decrease"; }} + bind "{secondary_modifier} [" {{ PreviousSwapLayout; }} + bind "{secondary_modifier} ]" {{ NextSwapLayout; }} + }} + shared_except "normal" "locked" {{ + bind "Enter" "Esc" {{ SwitchToMode "Normal"; }} + }} +}} +"# + ) +} + +fn default_keybinds_no_secondary_modifier(primary_modifier: String) -> String { + format!( + r#" +default_mode "normal" +keybinds clear-defaults=true {{ + normal {{}} + locked {{ + bind "{primary_modifier} g" {{ SwitchToMode "Normal"; }} + }} + resize {{ + bind "{primary_modifier} n" {{ SwitchToMode "Normal"; }} + bind "h" "Left" {{ Resize "Increase Left"; }} + bind "j" "Down" {{ Resize "Increase Down"; }} + bind "k" "Up" {{ Resize "Increase Up"; }} + bind "l" "Right" {{ Resize "Increase Right"; }} + bind "H" {{ Resize "Decrease Left"; }} + bind "J" {{ Resize "Decrease Down"; }} + bind "K" {{ Resize "Decrease Up"; }} + bind "L" {{ Resize "Decrease Right"; }} + bind "=" "+" {{ Resize "Increase"; }} + bind "-" {{ Resize "Decrease"; }} + }} + pane {{ + bind "{primary_modifier} p" {{ SwitchToMode "Normal"; }} + bind "h" "Left" {{ MoveFocus "Left"; }} + bind "l" "Right" {{ MoveFocus "Right"; }} + bind "j" "Down" {{ MoveFocus "Down"; }} + bind "k" "Up" {{ MoveFocus "Up"; }} + bind "p" {{ SwitchFocus; }} + bind "n" {{ NewPane; SwitchToMode "Normal"; }} + bind "d" {{ NewPane "Down"; SwitchToMode "Normal"; }} + bind "r" {{ NewPane "Right"; SwitchToMode "Normal"; }} + bind "x" {{ CloseFocus; SwitchToMode "Normal"; }} + bind "f" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }} + bind "z" {{ TogglePaneFrames; SwitchToMode "Normal"; }} + bind "w" {{ ToggleFloatingPanes; SwitchToMode "Normal"; }} + bind "e" {{ TogglePaneEmbedOrFloating; SwitchToMode "Normal"; }} + bind "c" {{ SwitchToMode "RenamePane"; PaneNameInput 0;}} + }} + move {{ + bind "{primary_modifier} h" {{ SwitchToMode "Normal"; }} + bind "n" "Tab" {{ MovePane; }} + bind "p" {{ MovePaneBackwards; }} + bind "h" "Left" {{ MovePane "Left"; }} + bind "j" "Down" {{ MovePane "Down"; }} + bind "k" "Up" {{ MovePane "Up"; }} + bind "l" "Right" {{ MovePane "Right"; }} + }} + tab {{ + bind "{primary_modifier} t" {{ SwitchToMode "Normal"; }} + bind "r" {{ SwitchToMode "RenameTab"; TabNameInput 0; }} + bind "h" "Left" "Up" "k" {{ GoToPreviousTab; }} + bind "l" "Right" "Down" "j" {{ GoToNextTab; }} + bind "n" {{ NewTab; SwitchToMode "Normal"; }} + bind "x" {{ CloseTab; SwitchToMode "Normal"; }} + bind "s" {{ ToggleActiveSyncTab; SwitchToMode "Normal"; }} + bind "b" {{ BreakPane; SwitchToMode "Normal"; }} + bind "]" {{ BreakPaneRight; SwitchToMode "Normal"; }} + bind "[" {{ BreakPaneLeft; SwitchToMode "Normal"; }} + bind "1" {{ GoToTab 1; SwitchToMode "Normal"; }} + bind "2" {{ GoToTab 2; SwitchToMode "Normal"; }} + bind "3" {{ GoToTab 3; SwitchToMode "Normal"; }} + bind "4" {{ GoToTab 4; SwitchToMode "Normal"; }} + bind "5" {{ GoToTab 5; SwitchToMode "Normal"; }} + bind "6" {{ GoToTab 6; SwitchToMode "Normal"; }} + bind "7" {{ GoToTab 7; SwitchToMode "Normal"; }} + bind "8" {{ GoToTab 8; SwitchToMode "Normal"; }} + bind "9" {{ GoToTab 9; SwitchToMode "Normal"; }} + bind "Tab" {{ ToggleTab; }} + }} + scroll {{ + bind "{primary_modifier} s" {{ SwitchToMode "Normal"; }} + bind "e" {{ EditScrollback; SwitchToMode "Normal"; }} + bind "s" {{ SwitchToMode "EnterSearch"; SearchInput 0; }} + bind "Ctrl c" {{ ScrollToBottom; SwitchToMode "Normal"; }} + bind "j" "Down" {{ ScrollDown; }} + bind "k" "Up" {{ ScrollUp; }} + bind "Ctrl f" "PageDown" "Right" "l" {{ PageScrollDown; }} + bind "Ctrl b" "PageUp" "Left" "h" {{ PageScrollUp; }} + bind "d" {{ HalfPageScrollDown; }} + bind "u" {{ HalfPageScrollUp; }} + }} + search {{ + bind "{primary_modifier} s" {{ SwitchToMode "Normal"; }} + bind "Ctrl c" {{ ScrollToBottom; SwitchToMode "Normal"; }} + bind "j" "Down" {{ ScrollDown; }} + bind "k" "Up" {{ ScrollUp; }} + bind "Ctrl f" "PageDown" "Right" "l" {{ PageScrollDown; }} + bind "Ctrl b" "PageUp" "Left" "h" {{ PageScrollUp; }} + bind "d" {{ HalfPageScrollDown; }} + bind "u" {{ HalfPageScrollUp; }} + bind "n" {{ Search "down"; }} + bind "p" {{ Search "up"; }} + bind "c" {{ SearchToggleOption "CaseSensitivity"; }} + bind "w" {{ SearchToggleOption "Wrap"; }} + bind "o" {{ SearchToggleOption "WholeWord"; }} + }} + entersearch {{ + bind "Ctrl c" "Esc" {{ SwitchToMode "Scroll"; }} + bind "Enter" {{ SwitchToMode "Search"; }} + }} + renametab {{ + bind "Ctrl c" {{ SwitchToMode "Normal"; }} + bind "Esc" {{ UndoRenameTab; SwitchToMode "Tab"; }} + }} + renamepane {{ + bind "Ctrl c" {{ SwitchToMode "Normal"; }} + bind "Esc" {{ UndoRenamePane; SwitchToMode "Pane"; }} + }} + session {{ + bind "{primary_modifier} o" {{ SwitchToMode "Normal"; }} + bind "{primary_modifier} s" {{ SwitchToMode "Scroll"; }} + bind "d" {{ Detach; }} + bind "w" {{ + LaunchOrFocusPlugin "session-manager" {{ + floating true + move_to_focused_tab true + }}; + SwitchToMode "Normal" + }} + bind "c" {{ + LaunchOrFocusPlugin "configuration" {{ + floating true + move_to_focused_tab true + }}; + SwitchToMode "Normal" + }} + }} + tmux {{ + bind "[" {{ SwitchToMode "Scroll"; }} + bind "{primary_modifier} b" {{ Write 2; SwitchToMode "Normal"; }} + bind "\"" {{ NewPane "Down"; SwitchToMode "Normal"; }} + bind "%" {{ NewPane "Right"; SwitchToMode "Normal"; }} + bind "z" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }} + bind "c" {{ NewTab; SwitchToMode "Normal"; }} + bind "," {{ SwitchToMode "RenameTab"; }} + bind "p" {{ GoToPreviousTab; SwitchToMode "Normal"; }} + bind "n" {{ GoToNextTab; SwitchToMode "Normal"; }} + bind "Left" {{ MoveFocus "Left"; SwitchToMode "Normal"; }} + bind "Right" {{ MoveFocus "Right"; SwitchToMode "Normal"; }} + bind "Down" {{ MoveFocus "Down"; SwitchToMode "Normal"; }} + bind "Up" {{ MoveFocus "Up"; SwitchToMode "Normal"; }} + bind "h" {{ MoveFocus "Left"; SwitchToMode "Normal"; }} + bind "l" {{ MoveFocus "Right"; SwitchToMode "Normal"; }} + bind "j" {{ MoveFocus "Down"; SwitchToMode "Normal"; }} + bind "k" {{ MoveFocus "Up"; SwitchToMode "Normal"; }} + bind "o" {{ FocusNextPane; }} + bind "d" {{ Detach; }} + bind "Space" {{ NextSwapLayout; }} + bind "x" {{ CloseFocus; SwitchToMode "Normal"; }} + }} + shared_except "locked" {{ + bind "{primary_modifier} g" {{ SwitchToMode "Locked"; }} + bind "{primary_modifier} q" {{ Quit; }} + }} + shared_except "normal" "locked" {{ + bind "Enter" "Esc" {{ SwitchToMode "Normal"; }} + }} + shared_except "pane" "locked" {{ + bind "{primary_modifier} p" {{ SwitchToMode "Pane"; }} + }} + shared_except "resize" "locked" {{ + bind "{primary_modifier} n" {{ SwitchToMode "Resize"; }} + }} + shared_except "scroll" "locked" {{ + bind "{primary_modifier} s" {{ SwitchToMode "Scroll"; }} + }} + shared_except "session" "locked" {{ + bind "{primary_modifier} o" {{ SwitchToMode "Session"; }} + }} + shared_except "tab" "locked" {{ + bind "{primary_modifier} t" {{ SwitchToMode "Tab"; }} + }} + shared_except "move" "locked" {{ + bind "{primary_modifier} h" {{ SwitchToMode "Move"; }} + }} + shared_except "tmux" "locked" {{ + bind "{primary_modifier} b" {{ SwitchToMode "Tmux"; }} + }} +}} +"# + ) +} + +fn default_keybinds_no_modifiers() -> String { + format!( + r#" +default_mode "normal" +keybinds clear-defaults=true {{ + normal {{}} + locked {{}} + resize {{ + bind "h" "Left" {{ Resize "Increase Left"; }} + bind "j" "Down" {{ Resize "Increase Down"; }} + bind "k" "Up" {{ Resize "Increase Up"; }} + bind "l" "Right" {{ Resize "Increase Right"; }} + bind "H" {{ Resize "Decrease Left"; }} + bind "J" {{ Resize "Decrease Down"; }} + bind "K" {{ Resize "Decrease Up"; }} + bind "L" {{ Resize "Decrease Right"; }} + bind "=" "+" {{ Resize "Increase"; }} + bind "-" {{ Resize "Decrease"; }} + }} + pane {{ + bind "h" "Left" {{ MoveFocus "Left"; }} + bind "l" "Right" {{ MoveFocus "Right"; }} + bind "j" "Down" {{ MoveFocus "Down"; }} + bind "k" "Up" {{ MoveFocus "Up"; }} + bind "p" {{ SwitchFocus; }} + bind "n" {{ NewPane; SwitchToMode "Normal"; }} + bind "d" {{ NewPane "Down"; SwitchToMode "Normal"; }} + bind "r" {{ NewPane "Right"; SwitchToMode "Normal"; }} + bind "x" {{ CloseFocus; SwitchToMode "Normal"; }} + bind "f" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }} + bind "z" {{ TogglePaneFrames; SwitchToMode "Normal"; }} + bind "w" {{ ToggleFloatingPanes; SwitchToMode "Normal"; }} + bind "e" {{ TogglePaneEmbedOrFloating; SwitchToMode "Normal"; }} + bind "c" {{ SwitchToMode "RenamePane"; PaneNameInput 0;}} + }} + move {{ + bind "n" "Tab" {{ MovePane; }} + bind "p" {{ MovePaneBackwards; }} + bind "h" "Left" {{ MovePane "Left"; }} + bind "j" "Down" {{ MovePane "Down"; }} + bind "k" "Up" {{ MovePane "Up"; }} + bind "l" "Right" {{ MovePane "Right"; }} + }} + tab {{ + bind "r" {{ SwitchToMode "RenameTab"; TabNameInput 0; }} + bind "h" "Left" "Up" "k" {{ GoToPreviousTab; }} + bind "l" "Right" "Down" "j" {{ GoToNextTab; }} + bind "n" {{ NewTab; SwitchToMode "Normal"; }} + bind "x" {{ CloseTab; SwitchToMode "Normal"; }} + bind "s" {{ ToggleActiveSyncTab; SwitchToMode "Normal"; }} + bind "b" {{ BreakPane; SwitchToMode "Normal"; }} + bind "]" {{ BreakPaneRight; SwitchToMode "Normal"; }} + bind "[" {{ BreakPaneLeft; SwitchToMode "Normal"; }} + bind "1" {{ GoToTab 1; SwitchToMode "Normal"; }} + bind "2" {{ GoToTab 2; SwitchToMode "Normal"; }} + bind "3" {{ GoToTab 3; SwitchToMode "Normal"; }} + bind "4" {{ GoToTab 4; SwitchToMode "Normal"; }} + bind "5" {{ GoToTab 5; SwitchToMode "Normal"; }} + bind "6" {{ GoToTab 6; SwitchToMode "Normal"; }} + bind "7" {{ GoToTab 7; SwitchToMode "Normal"; }} + bind "8" {{ GoToTab 8; SwitchToMode "Normal"; }} + bind "9" {{ GoToTab 9; SwitchToMode "Normal"; }} + bind "Tab" {{ ToggleTab; }} + }} + scroll {{ + bind "e" {{ EditScrollback; SwitchToMode "Normal"; }} + bind "s" {{ SwitchToMode "EnterSearch"; SearchInput 0; }} + bind "Ctrl c" {{ ScrollToBottom; SwitchToMode "Normal"; }} + bind "j" "Down" {{ ScrollDown; }} + bind "k" "Up" {{ ScrollUp; }} + bind "Ctrl f" "PageDown" "Right" "l" {{ PageScrollDown; }} + bind "Ctrl b" "PageUp" "Left" "h" {{ PageScrollUp; }} + bind "d" {{ HalfPageScrollDown; }} + bind "u" {{ HalfPageScrollUp; }} + }} + search {{ + bind "Ctrl c" {{ ScrollToBottom; SwitchToMode "Normal"; }} + bind "j" "Down" {{ ScrollDown; }} + bind "k" "Up" {{ ScrollUp; }} + bind "Ctrl f" "PageDown" "Right" "l" {{ PageScrollDown; }} + bind "Ctrl b" "PageUp" "Left" "h" {{ PageScrollUp; }} + bind "d" {{ HalfPageScrollDown; }} + bind "u" {{ HalfPageScrollUp; }} + bind "n" {{ Search "down"; }} + bind "p" {{ Search "up"; }} + bind "c" {{ SearchToggleOption "CaseSensitivity"; }} + bind "w" {{ SearchToggleOption "Wrap"; }} + bind "o" {{ SearchToggleOption "WholeWord"; }} + }} + entersearch {{ + bind "Ctrl c" "Esc" {{ SwitchToMode "Scroll"; }} + bind "Enter" {{ SwitchToMode "Search"; }} + }} + renametab {{ + bind "Ctrl c" {{ SwitchToMode "Normal"; }} + bind "Esc" {{ UndoRenameTab; SwitchToMode "Tab"; }} + }} + renamepane {{ + bind "Ctrl c" {{ SwitchToMode "Normal"; }} + bind "Esc" {{ UndoRenamePane; SwitchToMode "Pane"; }} + }} + session {{ + bind "d" {{ Detach; }} + bind "w" {{ + LaunchOrFocusPlugin "session-manager" {{ + floating true + move_to_focused_tab true + }}; + SwitchToMode "Normal" + }} + bind "c" {{ + LaunchOrFocusPlugin "configuration" {{ + floating true + move_to_focused_tab true + }}; + SwitchToMode "Normal" + }} + }} + tmux {{ + bind "[" {{ SwitchToMode "Scroll"; }} + bind "\"" {{ NewPane "Down"; SwitchToMode "Normal"; }} + bind "%" {{ NewPane "Right"; SwitchToMode "Normal"; }} + bind "z" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }} + bind "c" {{ NewTab; SwitchToMode "Normal"; }} + bind "," {{ SwitchToMode "RenameTab"; }} + bind "p" {{ GoToPreviousTab; SwitchToMode "Normal"; }} + bind "n" {{ GoToNextTab; SwitchToMode "Normal"; }} + bind "Left" {{ MoveFocus "Left"; SwitchToMode "Normal"; }} + bind "Right" {{ MoveFocus "Right"; SwitchToMode "Normal"; }} + bind "Down" {{ MoveFocus "Down"; SwitchToMode "Normal"; }} + bind "Up" {{ MoveFocus "Up"; SwitchToMode "Normal"; }} + bind "h" {{ MoveFocus "Left"; SwitchToMode "Normal"; }} + bind "l" {{ MoveFocus "Right"; SwitchToMode "Normal"; }} + bind "j" {{ MoveFocus "Down"; SwitchToMode "Normal"; }} + bind "k" {{ MoveFocus "Up"; SwitchToMode "Normal"; }} + bind "o" {{ FocusNextPane; }} + bind "d" {{ Detach; }} + bind "Space" {{ NextSwapLayout; }} + bind "x" {{ CloseFocus; SwitchToMode "Normal"; }} + }} + shared_except "normal" "locked" {{ + bind "Enter" "Esc" {{ SwitchToMode "Normal"; }} + }} +}} +"# + ) +} + +fn non_colliding_default_keybinds(primary_modifier: String, secondary_modifier: String) -> String { + format!( + r#" +default_mode "normal" +keybinds clear-defaults=true {{ + normal {{}} + locked {{ + bind "{primary_modifier} g" {{ SwitchToMode "Normal"; }} + }} + resize {{ + bind "{primary_modifier} r" {{ SwitchToMode "Normal"; }} + bind "h" "Left" {{ Resize "Increase Left"; }} + bind "j" "Down" {{ Resize "Increase Down"; }} + bind "k" "Up" {{ Resize "Increase Up"; }} + bind "l" "Right" {{ Resize "Increase Right"; }} + bind "H" {{ Resize "Decrease Left"; }} + bind "J" {{ Resize "Decrease Down"; }} + bind "K" {{ Resize "Decrease Up"; }} + bind "L" {{ Resize "Decrease Right"; }} + bind "=" "+" {{ Resize "Increase"; }} + bind "-" {{ Resize "Decrease"; }} + }} + pane {{ + bind "{primary_modifier} p" {{ SwitchToMode "Normal"; }} + bind "h" "Left" {{ MoveFocus "Left"; }} + bind "l" "Right" {{ MoveFocus "Right"; }} + bind "j" "Down" {{ MoveFocus "Down"; }} + bind "k" "Up" {{ MoveFocus "Up"; }} + bind "p" {{ SwitchFocus; }} + bind "n" {{ NewPane; SwitchToMode "Normal"; }} + bind "d" {{ NewPane "Down"; SwitchToMode "Normal"; }} + bind "r" {{ NewPane "Right"; SwitchToMode "Normal"; }} + bind "x" {{ CloseFocus; SwitchToMode "Normal"; }} + bind "f" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }} + bind "z" {{ TogglePaneFrames; SwitchToMode "Normal"; }} + bind "w" {{ ToggleFloatingPanes; SwitchToMode "Normal"; }} + bind "e" {{ TogglePaneEmbedOrFloating; SwitchToMode "Normal"; }} + bind "c" {{ SwitchToMode "RenamePane"; PaneNameInput 0;}} + }} + move {{ + bind "{primary_modifier} m" {{ SwitchToMode "Normal"; }} + bind "n" "Tab" {{ MovePane; }} + bind "p" {{ MovePaneBackwards; }} + bind "h" "Left" {{ MovePane "Left"; }} + bind "j" "Down" {{ MovePane "Down"; }} + bind "k" "Up" {{ MovePane "Up"; }} + bind "l" "Right" {{ MovePane "Right"; }} + }} + tab {{ + bind "{primary_modifier} t" {{ SwitchToMode "Normal"; }} + bind "r" {{ SwitchToMode "RenameTab"; TabNameInput 0; }} + bind "h" "Left" "Up" "k" {{ GoToPreviousTab; }} + bind "l" "Right" "Down" "j" {{ GoToNextTab; }} + bind "n" {{ NewTab; SwitchToMode "Normal"; }} + bind "x" {{ CloseTab; SwitchToMode "Normal"; }} + bind "s" {{ ToggleActiveSyncTab; SwitchToMode "Normal"; }} + bind "b" {{ BreakPane; SwitchToMode "Normal"; }} + bind "]" {{ BreakPaneRight; SwitchToMode "Normal"; }} + bind "[" {{ BreakPaneLeft; SwitchToMode "Normal"; }} + bind "1" {{ GoToTab 1; SwitchToMode "Normal"; }} + bind "2" {{ GoToTab 2; SwitchToMode "Normal"; }} + bind "3" {{ GoToTab 3; SwitchToMode "Normal"; }} + bind "4" {{ GoToTab 4; SwitchToMode "Normal"; }} + bind "5" {{ GoToTab 5; SwitchToMode "Normal"; }} + bind "6" {{ GoToTab 6; SwitchToMode "Normal"; }} + bind "7" {{ GoToTab 7; SwitchToMode "Normal"; }} + bind "8" {{ GoToTab 8; SwitchToMode "Normal"; }} + bind "9" {{ GoToTab 9; SwitchToMode "Normal"; }} + bind "Tab" {{ ToggleTab; }} + }} + scroll {{ + bind "{primary_modifier} s" {{ SwitchToMode "Normal"; }} + bind "e" {{ EditScrollback; SwitchToMode "Normal"; }} + bind "s" {{ SwitchToMode "EnterSearch"; SearchInput 0; }} + bind "Ctrl c" {{ ScrollToBottom; SwitchToMode "Normal"; }} + bind "j" "Down" {{ ScrollDown; }} + bind "k" "Up" {{ ScrollUp; }} + bind "Ctrl f" "PageDown" "Right" "l" {{ PageScrollDown; }} + bind "Ctrl b" "PageUp" "Left" "h" {{ PageScrollUp; }} + bind "d" {{ HalfPageScrollDown; }} + bind "u" {{ HalfPageScrollUp; }} + }} + search {{ + bind "{primary_modifier} s" {{ SwitchToMode "Normal"; }} + bind "Ctrl c" {{ ScrollToBottom; SwitchToMode "Normal"; }} + bind "j" "Down" {{ ScrollDown; }} + bind "k" "Up" {{ ScrollUp; }} + bind "Ctrl f" "PageDown" "Right" "l" {{ PageScrollDown; }} + bind "Ctrl b" "PageUp" "Left" "h" {{ PageScrollUp; }} + bind "d" {{ HalfPageScrollDown; }} + bind "u" {{ HalfPageScrollUp; }} + bind "n" {{ Search "down"; }} + bind "p" {{ Search "up"; }} + bind "c" {{ SearchToggleOption "CaseSensitivity"; }} + bind "w" {{ SearchToggleOption "Wrap"; }} + bind "o" {{ SearchToggleOption "WholeWord"; }} + }} + entersearch {{ + bind "Ctrl c" "Esc" {{ SwitchToMode "Scroll"; }} + bind "Enter" {{ SwitchToMode "Search"; }} + }} + renametab {{ + bind "Ctrl c" {{ SwitchToMode "Normal"; }} + bind "Esc" {{ UndoRenameTab; SwitchToMode "Tab"; }} + }} + renamepane {{ + bind "Ctrl c" {{ SwitchToMode "Normal"; }} + bind "Esc" {{ UndoRenamePane; SwitchToMode "Pane"; }} + }} + session {{ + bind "{primary_modifier} o" {{ SwitchToMode "Normal"; }} + bind "{primary_modifier} s" {{ SwitchToMode "Scroll"; }} + bind "d" {{ Detach; }} + bind "w" {{ + LaunchOrFocusPlugin "session-manager" {{ + floating true + move_to_focused_tab true + }}; + SwitchToMode "Normal" + }} + bind "c" {{ + LaunchOrFocusPlugin "configuration" {{ + floating true + move_to_focused_tab true + }}; + SwitchToMode "Normal" + }} + }} + tmux {{ + bind "[" {{ SwitchToMode "Scroll"; }} + bind "{primary_modifier} b" {{ Write 2; SwitchToMode "Normal"; }} + bind "\"" {{ NewPane "Down"; SwitchToMode "Normal"; }} + bind "%" {{ NewPane "Right"; SwitchToMode "Normal"; }} + bind "z" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }} + bind "c" {{ NewTab; SwitchToMode "Normal"; }} + bind "," {{ SwitchToMode "RenameTab"; }} + bind "p" {{ GoToPreviousTab; SwitchToMode "Normal"; }} + bind "n" {{ GoToNextTab; SwitchToMode "Normal"; }} + bind "Left" {{ MoveFocus "Left"; SwitchToMode "Normal"; }} + bind "Right" {{ MoveFocus "Right"; SwitchToMode "Normal"; }} + bind "Down" {{ MoveFocus "Down"; SwitchToMode "Normal"; }} + bind "Up" {{ MoveFocus "Up"; SwitchToMode "Normal"; }} + bind "h" {{ MoveFocus "Left"; SwitchToMode "Normal"; }} + bind "l" {{ MoveFocus "Right"; SwitchToMode "Normal"; }} + bind "j" {{ MoveFocus "Down"; SwitchToMode "Normal"; }} + bind "k" {{ MoveFocus "Up"; SwitchToMode "Normal"; }} + bind "o" {{ FocusNextPane; }} + bind "d" {{ Detach; }} + bind "Space" {{ NextSwapLayout; }} + bind "x" {{ CloseFocus; SwitchToMode "Normal"; }} + }} + shared_except "locked" {{ + bind "{primary_modifier} g" {{ SwitchToMode "Locked"; }} + bind "{primary_modifier} q" {{ Quit; }} + bind "{secondary_modifier} f" {{ ToggleFloatingPanes; }} + bind "{secondary_modifier} n" {{ NewPane; }} + bind "{secondary_modifier} i" {{ MoveTab "Left"; }} + bind "{secondary_modifier} o" {{ MoveTab "Right"; }} + bind "{secondary_modifier} h" "{secondary_modifier} Left" {{ MoveFocusOrTab "Left"; }} + bind "{secondary_modifier} l" "{secondary_modifier} Right" {{ MoveFocusOrTab "Right"; }} + bind "{secondary_modifier} j" "{secondary_modifier} Down" {{ MoveFocus "Down"; }} + bind "{secondary_modifier} k" "{secondary_modifier} Up" {{ MoveFocus "Up"; }} + bind "{secondary_modifier} =" "{secondary_modifier} +" {{ Resize "Increase"; }} + bind "{secondary_modifier} -" {{ Resize "Decrease"; }} + bind "{secondary_modifier} [" {{ PreviousSwapLayout; }} + bind "{secondary_modifier} ]" {{ NextSwapLayout; }} + }} + shared_except "normal" "locked" {{ + bind "Enter" "Esc" {{ SwitchToMode "Normal"; }} + }} + shared_except "pane" "locked" {{ + bind "{primary_modifier} p" {{ SwitchToMode "Pane"; }} + }} + shared_except "resize" "locked" {{ + bind "{primary_modifier} r" {{ SwitchToMode "Resize"; }} + }} + shared_except "scroll" "locked" {{ + bind "{primary_modifier} s" {{ SwitchToMode "Scroll"; }} + }} + shared_except "session" "locked" "tab" {{ + bind "{primary_modifier} o" {{ SwitchToMode "Session"; }} + }} + shared_except "tab" "locked" {{ + bind "{primary_modifier} t" {{ SwitchToMode "Tab"; }} + }} + shared_except "move" "locked" {{ + bind "{primary_modifier} m" {{ SwitchToMode "Move"; }} + }} + shared_except "tmux" "locked" {{ + bind "{primary_modifier} b" {{ SwitchToMode "Tmux"; }} + }} +}} +"# + ) +} diff --git a/default-plugins/fixture-plugin-for-tests/src/main.rs b/default-plugins/fixture-plugin-for-tests/src/main.rs index b6cac35b..918aba72 100644 --- a/default-plugins/fixture-plugin-for-tests/src/main.rs +++ b/default-plugins/fixture-plugin-for-tests/src/main.rs @@ -56,7 +56,7 @@ impl ZellijPlugin for State { PermissionType::WebAccess, PermissionType::ReadCliPipes, PermissionType::MessageAndLaunchOtherPlugins, - PermissionType::RebindKeys, + PermissionType::Reconfigure, ]); self.configuration = configuration; subscribe(&[ @@ -320,7 +320,7 @@ impl ZellijPlugin for State { ); }, BareKey::Char('0') if key.has_modifiers(&[KeyModifier::Ctrl]) => { - rebind_keys( + reconfigure( " keybinds { locked { diff --git a/default-plugins/status-bar/src/one_line_ui.rs b/default-plugins/status-bar/src/one_line_ui.rs index 87b1e2fd..7e3b8a4d 100644 --- a/default-plugins/status-bar/src/one_line_ui.rs +++ b/default-plugins/status-bar/src/one_line_ui.rs @@ -1265,6 +1265,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)]) -> Vec)]) -> Vec { + let mut matching = keymap.iter().find_map(|(key, acvec)| { + let has_match = acvec + .iter() + .find(|a| a.launches_plugin("configuration")) + .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) -> LinePart { if keyvec.is_empty() { return LinePart::default(); diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap index da8650ae..b2401f43 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_terminal_window.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1050 +assertion_line: 1064 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -26,4 +26,4 @@ expression: last_snapshot │ ││ │ │ ││ │ └────────────────────────────────────────────────┘└────────────────────────────────────────────────┘ - Ctrl + g  p  t  n  h  s  o  q  Alt +  New Pane  <←↓↑→> Change Focus  + Ctrl + g  p  t  n  h  s  o  q  Alt +  New  <←↓↑→> Focus  Floating  diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__send_command_through_the_cli.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__send_command_through_the_cli.snap index 197fd571..623bfb73 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__send_command_through_the_cli.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__send_command_through_the_cli.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 2349 +assertion_line: 2372 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  Alt <[]>  VERTICAL  @@ -26,4 +26,4 @@ expression: last_snapshot │ ││ │ │ ││ │ └─────────────────────────────────────────────────────────────────────────┘└ [ EXIT CODE: 0 ] re-run, drop to shell, exit ────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Alt +  New  <←↓↑→> Focus  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 1f76fb4e..28bd7b3c 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -36,6 +36,7 @@ lazy_static::lazy_static! { WorkspaceMember{crate_name: "default-plugins/tab-bar", build: true}, WorkspaceMember{crate_name: "default-plugins/fixture-plugin-for-tests", build: true}, WorkspaceMember{crate_name: "default-plugins/session-manager", build: true}, + WorkspaceMember{crate_name: "default-plugins/configuration", build: true}, WorkspaceMember{crate_name: "zellij-utils", build: false}, WorkspaceMember{crate_name: "zellij-tile-utils", build: false}, WorkspaceMember{crate_name: "zellij-tile", build: false}, diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 9a3231b3..fbde2f86 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -97,7 +97,7 @@ pub enum ServerInstruction { DisconnectAllClientsExcept(ClientId), ChangeMode(ClientId, InputMode), ChangeModeForAllClients(InputMode), - RebindKeys(ClientId, String), // String -> stringified keybindings + Reconfigure(ClientId, String), // String -> stringified configuration } impl From<&ServerInstruction> for ServerContext { @@ -129,7 +129,7 @@ impl From<&ServerInstruction> for ServerContext { ServerInstruction::ChangeModeForAllClients(..) => { ServerContext::ChangeModeForAllClients }, - ServerInstruction::RebindKeys(..) => ServerContext::RebindKeys, + ServerInstruction::Reconfigure(..) => ServerContext::Reconfigure, } } } @@ -149,7 +149,7 @@ pub(crate) struct SessionMetaData { pub config_options: Box, pub client_keybinds: HashMap, pub client_input_modes: HashMap, - pub default_mode: InputMode, + pub default_mode: HashMap, screen_thread: Option>, pty_thread: Option>, plugin_thread: Option>, @@ -570,6 +570,10 @@ pub fn start_server(mut os_input: Box, socket_path: PathBuf) { default_mode, &attrs, session_data.capabilities, + session_data + .client_keybinds + .get(&client_id) + .unwrap_or(&session_data.client_attributes.keybinds), Some(default_mode), ); session_data @@ -911,24 +915,57 @@ pub fn start_server(mut os_input: Box, socket_path: PathBuf) { .unwrap() .change_mode_for_all_clients(input_mode); }, - ServerInstruction::RebindKeys(client_id, new_keybinds) => { + ServerInstruction::Reconfigure(client_id, new_config) => { + let mut new_default_mode = None; + match Options::from_string(&new_config) { + Ok(mut new_config_options) => { + if let Some(default_mode) = new_config_options.default_mode.take() { + new_default_mode = Some(default_mode); + session_data + .write() + .unwrap() + .as_mut() + .unwrap() + .default_mode + .insert(client_id, default_mode); + } + }, + Err(e) => { + log::error!("Failed to parse config: {}", e); + }, + } + let new_keybinds = session_data .write() .unwrap() .as_mut() .unwrap() - .rebind_keys(client_id, new_keybinds) + .rebind_keys(client_id, new_config) .clone(); - if let Some(new_keybinds) = new_keybinds { - session_data - .write() - .unwrap() - .as_ref() - .unwrap() - .senders - .send_to_screen(ScreenInstruction::RebindKeys(new_keybinds, client_id)) - .unwrap(); - } + session_data + .write() + .unwrap() + .as_ref() + .unwrap() + .senders + .send_to_screen(ScreenInstruction::Reconfigure { + client_id, + keybinds: new_keybinds.clone(), + default_mode: new_default_mode, + }) + .unwrap(); + session_data + .write() + .unwrap() + .as_ref() + .unwrap() + .senders + .send_to_plugin(PluginInstruction::Reconfigure { + client_id, + keybinds: new_keybinds, + default_mode: new_default_mode, + }) + .unwrap(); }, } } @@ -1167,7 +1204,7 @@ fn init_session( plugin_thread: Some(plugin_thread), pty_writer_thread: Some(pty_writer_thread), background_jobs_thread: Some(background_jobs_thread), - default_mode, + default_mode: HashMap::new(), } } diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs index 639a389e..223ecb55 100644 --- a/zellij-server/src/plugins/mod.rs +++ b/zellij-server/src/plugins/mod.rs @@ -31,6 +31,7 @@ use zellij_utils::{ errors::{prelude::*, ContextType, PluginContext}, input::{ command::TerminalAction, + keybinds::Keybinds, layout::{FloatingPaneLayout, Layout, Run, RunPlugin, RunPluginOrAlias, TiledPaneLayout}, plugins::PluginAliases, }, @@ -142,6 +143,11 @@ pub enum PluginInstruction { message: MessageToPlugin, }, UnblockCliPipes(Vec), + Reconfigure { + client_id: ClientId, + keybinds: Option, + default_mode: Option, + }, WatchFilesystem, Exit, } @@ -182,6 +188,7 @@ impl From<&PluginInstruction> for PluginContext { PluginInstruction::WatchFilesystem => PluginContext::WatchFilesystem, PluginInstruction::KeybindPipe { .. } => PluginContext::KeybindPipe, PluginInstruction::DumpLayoutToPlugin(..) => PluginContext::DumpLayoutToPlugin, + PluginInstruction::Reconfigure { .. } => PluginContext::Reconfigure, } } } @@ -734,6 +741,15 @@ pub(crate) fn plugin_thread_main( .context("failed to unblock input pipe"); } }, + PluginInstruction::Reconfigure { + client_id, + keybinds, + default_mode, + } => { + wasm_bridge + .reconfigure(client_id, keybinds, default_mode) + .non_fatal(); + }, PluginInstruction::WatchFilesystem => { wasm_bridge.start_fs_watcher_if_not_started(); }, diff --git a/zellij-server/src/plugins/plugin_loader.rs b/zellij-server/src/plugins/plugin_loader.rs index 6ed36c05..51ca3e16 100644 --- a/zellij-server/src/plugins/plugin_loader.rs +++ b/zellij-server/src/plugins/plugin_loader.rs @@ -29,6 +29,7 @@ use zellij_utils::{ data::{InputMode, PluginCapabilities}, errors::prelude::*, input::command::TerminalAction, + input::keybinds::Keybinds, input::layout::Layout, input::plugins::PluginConfig, ipc::ClientAttributes, @@ -69,6 +70,7 @@ pub struct PluginLoader<'a> { default_layout: Box, layout_dir: Option, default_mode: InputMode, + keybinds: Option, } impl<'a> PluginLoader<'a> { @@ -89,6 +91,7 @@ impl<'a> PluginLoader<'a> { default_layout: Box, layout_dir: Option, default_mode: InputMode, + keybinds: &HashMap, ) -> Result<()> { let err_context = || format!("failed to reload plugin {plugin_id} from memory"); let mut connected_clients: Vec = @@ -97,6 +100,7 @@ impl<'a> PluginLoader<'a> { return Err(anyhow!("No connected clients, cannot reload plugin")); } let first_client_id = connected_clients.remove(0); + let keybinds = keybinds.get(&first_client_id).cloned(); let mut plugin_loader = PluginLoader::new_from_existing_plugin_attributes( &plugin_cache, @@ -115,6 +119,7 @@ impl<'a> PluginLoader<'a> { default_layout, layout_dir, default_mode, + keybinds, )?; plugin_loader .load_module_from_memory() @@ -152,6 +157,7 @@ impl<'a> PluginLoader<'a> { skip_cache: bool, layout_dir: Option, default_mode: InputMode, + keybinds: Option, ) -> Result<()> { let err_context = || format!("failed to start plugin {plugin_id} for client {client_id}"); let mut plugin_loader = PluginLoader::new( @@ -173,6 +179,7 @@ impl<'a> PluginLoader<'a> { default_layout, layout_dir, default_mode, + keybinds, )?; if skip_cache { plugin_loader @@ -226,6 +233,7 @@ impl<'a> PluginLoader<'a> { default_layout: Box, layout_dir: Option, default_mode: InputMode, + keybinds: Option, ) -> Result<()> { let mut new_plugins = HashSet::new(); for plugin_id in plugin_map.lock().unwrap().plugin_ids() { @@ -249,6 +257,7 @@ impl<'a> PluginLoader<'a> { default_layout.clone(), layout_dir.clone(), default_mode, + keybinds.clone(), )?; plugin_loader .load_module_from_memory() @@ -278,6 +287,7 @@ impl<'a> PluginLoader<'a> { default_layout: Box, layout_dir: Option, default_mode: InputMode, + keybinds: &HashMap, ) -> Result<()> { let err_context = || format!("failed to reload plugin id {plugin_id}"); @@ -287,6 +297,7 @@ impl<'a> PluginLoader<'a> { return Err(anyhow!("No connected clients, cannot reload plugin")); } let first_client_id = connected_clients.remove(0); + let keybinds = keybinds.get(&first_client_id).cloned(); let mut plugin_loader = PluginLoader::new_from_existing_plugin_attributes( &plugin_cache, @@ -305,6 +316,7 @@ impl<'a> PluginLoader<'a> { default_layout, layout_dir, default_mode, + keybinds, )?; plugin_loader .compile_module() @@ -338,6 +350,7 @@ impl<'a> PluginLoader<'a> { default_layout: Box, layout_dir: Option, default_mode: InputMode, + keybinds: Option, ) -> Result { let plugin_own_data_dir = ZELLIJ_SESSION_CACHE_DIR .join(Url::from(&plugin.location).to_string()) @@ -366,6 +379,7 @@ impl<'a> PluginLoader<'a> { default_layout, layout_dir, default_mode, + keybinds, }) } pub fn new_from_existing_plugin_attributes( @@ -385,6 +399,7 @@ impl<'a> PluginLoader<'a> { default_layout: Box, layout_dir: Option, default_mode: InputMode, + keybinds: Option, ) -> Result { let err_context = || "Failed to find existing plugin"; let (running_plugin, _subscriptions, _workers) = { @@ -420,6 +435,7 @@ impl<'a> PluginLoader<'a> { default_layout, layout_dir, default_mode, + keybinds, ) } pub fn new_from_different_client_id( @@ -439,6 +455,7 @@ impl<'a> PluginLoader<'a> { default_layout: Box, layout_dir: Option, default_mode: InputMode, + keybinds: Option, ) -> Result { let err_context = || "Failed to find existing plugin"; let running_plugin = { @@ -475,6 +492,7 @@ impl<'a> PluginLoader<'a> { default_layout, layout_dir, default_mode, + keybinds, ) } pub fn load_module_from_memory(&mut self) -> Result { @@ -731,6 +749,7 @@ impl<'a> PluginLoader<'a> { self.default_layout.clone(), self.layout_dir.clone(), self.default_mode, + self.keybinds.clone(), )?; plugin_loader_for_client .load_module_from_memory() @@ -832,6 +851,11 @@ impl<'a> PluginLoader<'a> { layout_dir: self.layout_dir.clone(), default_mode: self.default_mode.clone(), subscriptions: Arc::new(Mutex::new(HashSet::new())), + keybinds: self + .keybinds + .as_ref() + .unwrap_or_else(|| &self.client_attributes.keybinds) + .clone(), stdin_pipe, stdout_pipe, }; diff --git a/zellij-server/src/plugins/plugin_map.rs b/zellij-server/src/plugins/plugin_map.rs index c5ac6d5f..6390841a 100644 --- a/zellij-server/src/plugins/plugin_map.rs +++ b/zellij-server/src/plugins/plugin_map.rs @@ -22,6 +22,7 @@ use zellij_utils::{ data::InputMode, data::PluginCapabilities, input::command::TerminalAction, + input::keybinds::Keybinds, input::layout::{Layout, PluginUserConfiguration, RunPlugin, RunPluginLocation}, input::plugins::PluginConfig, ipc::ClientAttributes, @@ -289,6 +290,7 @@ pub struct PluginEnv { pub subscriptions: Arc>, pub stdin_pipe: Arc>>, pub stdout_pipe: Arc>>, + pub keybinds: Keybinds, } #[derive(Clone)] @@ -424,4 +426,10 @@ impl RunningPlugin { false } } + pub fn update_keybinds(&mut self, keybinds: Keybinds) { + self.store.data_mut().keybinds = keybinds; + } + pub fn update_default_mode(&mut self, default_mode: InputMode) { + self.store.data_mut().default_mode = default_mode; + } } diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs index c1f4fcd5..5ef220db 100644 --- a/zellij-server/src/plugins/unit/plugin_tests.rs +++ b/zellij-server/src/plugins/unit/plugin_tests.rs @@ -6492,7 +6492,7 @@ pub fn disconnect_other_clients_plugins_command() { #[test] #[ignore] -pub fn rebind_keys_plugin_command() { +pub fn reconfigure_plugin_command() { let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its // destructor removes the directory let plugin_host_folder = PathBuf::from(temp_folder.path()); @@ -6527,7 +6527,7 @@ pub fn rebind_keys_plugin_command() { let received_server_instruction = Arc::new(Mutex::new(vec![])); let server_thread = log_actions_in_thread!( received_server_instruction, - ServerInstruction::RebindKeys, + ServerInstruction::Reconfigure, server_receiver, 1 ); @@ -6555,20 +6555,20 @@ pub fn rebind_keys_plugin_command() { std::thread::sleep(std::time::Duration::from_millis(500)); teardown(); server_thread.join().unwrap(); // this might take a while if the cache is cold - let rebind_keys_event = received_server_instruction + let reconfigure_event = received_server_instruction .lock() .unwrap() .iter() .rev() .find_map(|i| { - if let ServerInstruction::RebindKeys(..) = i { + if let ServerInstruction::Reconfigure(..) = i { Some(i.clone()) } else { None } }) .clone(); - assert_snapshot!(format!("{:#?}", rebind_keys_event)); + assert_snapshot!(format!("{:#?}", reconfigure_event)); } #[test] diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap index 465f1088..28c7dedc 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/plugins/./unit/plugin_tests.rs -assertion_line: 5500 +assertion_line: 5505 expression: "format!(\"{:#?}\", permissions)" --- Some( @@ -14,6 +14,6 @@ Some( WebAccess, ReadCliPipes, MessageAndLaunchOtherPlugins, - RebindKeys, + Reconfigure, ], ) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__reconfigure_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__reconfigure_plugin_command.snap new file mode 100644 index 00000000..3f788d7d --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__reconfigure_plugin_command.snap @@ -0,0 +1,11 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 6571 +expression: "format!(\"{:#?}\", reconfigure_event)" +--- +Some( + Reconfigure( + 1, + "\n keybinds {\n locked {\n bind \"a\" { NewTab; }\n }\n }\n ", + ), +) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap index a0fec410..75f47da7 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/plugins/./unit/plugin_tests.rs -assertion_line: 5409 +assertion_line: 5414 expression: "format!(\"{:#?}\", new_tab_event)" --- Some( @@ -16,6 +16,6 @@ Some( WebAccess, ReadCliPipes, MessageAndLaunchOtherPlugins, - RebindKeys, + Reconfigure, ], ) diff --git a/zellij-server/src/plugins/wasm_bridge.rs b/zellij-server/src/plugins/wasm_bridge.rs index 2abc22d3..00a7795b 100644 --- a/zellij-server/src/plugins/wasm_bridge.rs +++ b/zellij-server/src/plugins/wasm_bridge.rs @@ -21,6 +21,7 @@ use zellij_utils::async_std::task::{self, JoinHandle}; use zellij_utils::consts::ZELLIJ_CACHE_DIR; use zellij_utils::data::{InputMode, PermissionStatus, PermissionType, PipeMessage, PipeSource}; use zellij_utils::downloader::Downloader; +use zellij_utils::input::keybinds::Keybinds; use zellij_utils::input::permission::PermissionCache; use zellij_utils::notify_debouncer_full::{notify::RecommendedWatcher, Debouncer, FileIdMap}; use zellij_utils::plugin_api::event::ProtobufEvent; @@ -103,6 +104,7 @@ pub struct WasmBridge { pending_pipes: PendingPipes, layout_dir: Option, default_mode: InputMode, + keybinds: HashMap, } impl WasmBridge { @@ -149,6 +151,7 @@ impl WasmBridge { pending_pipes: Default::default(), layout_dir, default_mode, + keybinds: HashMap::new(), } } pub fn load_plugin( @@ -206,6 +209,7 @@ impl WasmBridge { let default_layout = self.default_layout.clone(); let layout_dir = self.layout_dir.clone(); let default_mode = self.default_mode; + let keybinds = self.keybinds.get(&client_id).cloned(); async move { let _ = senders.send_to_background_jobs( BackgroundJob::AnimatePluginLoading(plugin_id), @@ -254,6 +258,7 @@ impl WasmBridge { skip_cache, layout_dir, default_mode, + keybinds, ) { Ok(_) => handle_plugin_successful_loading(&senders, plugin_id), Err(e) => handle_plugin_loading_failure( @@ -346,6 +351,7 @@ impl WasmBridge { let default_layout = self.default_layout.clone(); let layout_dir = self.layout_dir.clone(); let default_mode = self.default_mode; + let keybinds = self.keybinds.clone(); async move { match PluginLoader::reload_plugin( first_plugin_id, @@ -364,6 +370,7 @@ impl WasmBridge { default_layout.clone(), layout_dir.clone(), default_mode, + &keybinds, ) { Ok(_) => { handle_plugin_successful_loading(&senders, first_plugin_id); @@ -390,6 +397,7 @@ impl WasmBridge { default_layout.clone(), layout_dir.clone(), default_mode, + &keybinds, ) { Ok(_) => handle_plugin_successful_loading(&senders, *plugin_id), Err(e) => handle_plugin_loading_failure( @@ -443,6 +451,7 @@ impl WasmBridge { self.default_layout.clone(), self.layout_dir.clone(), self.default_mode, + self.keybinds.get(&client_id).cloned(), ) { Ok(_) => { let _ = self @@ -795,6 +804,51 @@ impl WasmBridge { .unwrap() .run_plugin_of_plugin_id(plugin_id) } + + pub fn reconfigure( + &mut self, + client_id: ClientId, + keybinds: Option, + default_mode: Option, + ) -> Result<()> { + let plugins_to_reconfigure: Vec>> = self + .plugin_map + .lock() + .unwrap() + .running_plugins() + .iter() + .cloned() + .filter_map(|(_plugin_id, c_id, running_plugin)| { + if c_id == client_id { + Some(running_plugin.clone()) + } else { + None + } + }) + .collect(); + if let Some(default_mode) = default_mode.as_ref() { + self.default_mode = *default_mode; + } + if let Some(keybinds) = keybinds.as_ref() { + self.keybinds.insert(client_id, keybinds.clone()); + } + for running_plugin in plugins_to_reconfigure { + task::spawn({ + let running_plugin = running_plugin.clone(); + let keybinds = keybinds.clone(); + async move { + let mut running_plugin = running_plugin.lock().unwrap(); + if let Some(keybinds) = keybinds { + running_plugin.update_keybinds(keybinds); + } + if let Some(default_mode) = default_mode { + running_plugin.update_default_mode(default_mode); + } + } + }); + } + Ok(()) + } fn apply_cached_events_and_resizes_for_plugin( &mut self, plugin_id: PluginId, @@ -1288,6 +1342,18 @@ pub fn apply_event_to_plugin( let err_context = || format!("Failed to apply event to plugin {plugin_id}"); match check_event_permission(running_plugin.store.data(), event) { (PermissionStatus::Granted, _) => { + let mut event = event.clone(); + if let Event::ModeUpdate(mode_info) = &mut event { + // we do this because there can be some cases where this event arrives here with + // the wrong keybindings or default mode (for example: when triggered from the CLI, + // where we do not know the target client_id and thus don't know if their keybindings are the + // default or if they have changed at runtime), the keybindings in running_plugin + // should always be up-to-date. Ideally, we would have changed the keybindings in + // ModeInfo to an Option, but alas - this is already part of our contract and that + // would be a breaking change. + mode_info.keybinds = running_plugin.store.data().keybinds.to_keybinds_vec(); + mode_info.base_mode = Some(running_plugin.store.data().default_mode); + } let protobuf_event: ProtobufEvent = event .clone() .try_into() diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs index a517df06..21b8b01d 100644 --- a/zellij-server/src/plugins/zellij_exports.rs +++ b/zellij-server/src/plugins/zellij_exports.rs @@ -61,6 +61,7 @@ macro_rules! apply_action { $env.default_shell.clone(), $env.default_layout.clone(), None, + $env.keybinds.clone(), $env.default_mode.clone(), ) { log::error!("{}: {:?}", $error_message(), e); @@ -244,7 +245,7 @@ fn host_run_plugin_command(caller: Caller<'_, PluginEnv>) { PluginCommand::WatchFilesystem => watch_filesystem(env), PluginCommand::DumpSessionLayout => dump_session_layout(env), PluginCommand::CloseSelf => close_self(env), - PluginCommand::RebindKeys(new_keybinds) => rebind_keys(env, new_keybinds)?, + PluginCommand::Reconfigure(new_config) => reconfigure(env, new_config)?, }, (PermissionStatus::Denied, permission) => { log::error!( @@ -781,11 +782,11 @@ fn close_self(env: &PluginEnv) { .non_fatal(); } -fn rebind_keys(env: &PluginEnv, new_keybinds: String) -> Result<()> { - let err_context = || "Failed to rebind keys"; +fn reconfigure(env: &PluginEnv, new_config: String) -> Result<()> { + let err_context = || "Failed to reconfigure"; let client_id = env.client_id; env.senders - .send_to_server(ServerInstruction::RebindKeys(client_id, new_keybinds)) + .send_to_server(ServerInstruction::Reconfigure(client_id, new_config)) .with_context(err_context)?; Ok(()) } @@ -1446,7 +1447,7 @@ fn check_command_permission( | PluginCommand::CliPipeOutput(..) => PermissionType::ReadCliPipes, PluginCommand::MessageToPlugin(..) => PermissionType::MessageAndLaunchOtherPlugins, PluginCommand::DumpSessionLayout => PermissionType::ReadApplicationState, - PluginCommand::RebindKeys(..) => PermissionType::RebindKeys, + PluginCommand::Reconfigure(..) => PermissionType::Reconfigure, _ => return (PermissionStatus::Granted, None), }; diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 3092310a..ab5db049 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -19,6 +19,7 @@ use zellij_utils::{ actions::{Action, SearchDirection, SearchOption}, command::TerminalAction, get_mode_info, + keybinds::Keybinds, layout::Layout, }, ipc::{ @@ -38,6 +39,7 @@ pub(crate) fn route_action( default_shell: Option, default_layout: Box, mut seen_cli_pipes: Option<&mut HashSet>, + client_keybinds: Keybinds, default_mode: InputMode, ) -> Result { let mut should_break = false; @@ -99,7 +101,13 @@ pub(crate) fn route_action( .send_to_plugin(PluginInstruction::Update(vec![( None, Some(client_id), - Event::ModeUpdate(get_mode_info(mode, attrs, capabilities, Some(default_mode))), + Event::ModeUpdate(get_mode_info( + mode, + attrs, + capabilities, + &client_keybinds, + Some(default_mode), + )), )])) .with_context(err_context)?; senders @@ -107,7 +115,13 @@ pub(crate) fn route_action( .with_context(err_context)?; senders .send_to_screen(ScreenInstruction::ChangeMode( - get_mode_info(mode, attrs, capabilities, Some(default_mode)), + get_mode_info( + mode, + attrs, + capabilities, + &client_keybinds, + Some(default_mode), + ), client_id, )) .with_context(err_context)?; @@ -349,6 +363,7 @@ pub(crate) fn route_action( input_mode, attrs, capabilities, + &client_keybinds, Some(default_mode), )), )])) @@ -363,6 +378,7 @@ pub(crate) fn route_action( input_mode, attrs, capabilities, + &client_keybinds, Some(default_mode), ))) .with_context(err_context)?; @@ -1025,7 +1041,20 @@ pub(crate) fn route_thread_main( rlocked_sessions.default_shell.clone(), rlocked_sessions.layout.clone(), Some(&mut seen_cli_pipes), - rlocked_sessions.default_mode, + rlocked_sessions + .client_keybinds + .get(&client_id) + .unwrap_or( + &rlocked_sessions + .client_attributes + .keybinds, + ) + .clone(), + rlocked_sessions + .default_mode + .get(&client_id) + .unwrap_or(&InputMode::Normal) + .clone(), )? { should_break = true; } @@ -1050,7 +1079,16 @@ pub(crate) fn route_thread_main( rlocked_sessions.default_shell.clone(), rlocked_sessions.layout.clone(), Some(&mut seen_cli_pipes), - rlocked_sessions.default_mode, + rlocked_sessions + .client_keybinds + .get(&client_id) + .unwrap_or(&rlocked_sessions.client_attributes.keybinds) + .clone(), + rlocked_sessions + .default_mode + .get(&client_id) + .unwrap_or(&InputMode::Normal) + .clone(), )? { should_break = true; } diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index e61b5d19..189c2571 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -361,7 +361,11 @@ pub enum ScreenInstruction { DumpLayoutToHd, RenameSession(String, ClientId), // String -> new name ListClientsMetadata(Option, ClientId), // Option - default shell - RebindKeys(Keybinds, ClientId), + Reconfigure { + client_id: ClientId, + keybinds: Option, + default_mode: Option, + }, } impl From<&ScreenInstruction> for ScreenContext { @@ -546,7 +550,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::DumpLayoutToHd => ScreenContext::DumpLayoutToHd, ScreenInstruction::RenameSession(..) => ScreenContext::RenameSession, ScreenInstruction::ListClientsMetadata(..) => ScreenContext::ListClientsMetadata, - ScreenInstruction::RebindKeys(..) => ScreenContext::RebindKeys, + ScreenInstruction::Reconfigure { .. } => ScreenContext::Reconfigure, } } } @@ -1214,7 +1218,7 @@ impl Screen { let tab_name = tab_name.unwrap_or_else(|| String::new()); let position = self.tabs.len(); - let tab = Tab::new( + let mut tab = Tab::new( tab_index, position, tab_name, @@ -1245,6 +1249,9 @@ impl Screen { self.styled_underlines, self.explicitly_disable_kitty_keyboard_protocol, ); + for (client_id, mode_info) in &self.mode_info { + tab.change_mode_info(mode_info.clone(), *client_id); + } self.tabs.insert(tab_index, tab); Ok(()) } @@ -2151,17 +2158,30 @@ impl Screen { } Ok(()) } - pub fn rebind_keys(&mut self, new_keybinds: Keybinds, client_id: ClientId) -> Result<()> { + pub fn reconfigure_mode_info( + &mut self, + new_keybinds: Option, + new_default_mode: Option, + client_id: ClientId, + ) -> Result<()> { if self.connected_clients_contains(&client_id) { + let should_update_mode_info = new_keybinds.is_some() || new_default_mode.is_some(); let mode_info = self .mode_info .entry(client_id) .or_insert_with(|| self.default_mode_info.clone()); - mode_info.update_keybinds(new_keybinds); - for tab in self.tabs.values_mut() { - tab.change_mode_info(mode_info.clone(), client_id); - tab.mark_active_pane_for_rerender(client_id); - tab.update_input_modes()?; + if let Some(new_keybinds) = new_keybinds { + mode_info.update_keybinds(new_keybinds); + } + if let Some(new_default_mode) = new_default_mode { + mode_info.update_default_mode(new_default_mode); + } + if should_update_mode_info { + for tab in self.tabs.values_mut() { + tab.change_mode_info(mode_info.clone(), client_id); + tab.mark_active_pane_for_rerender(client_id); + tab.update_input_modes()?; + } } } else { log::error!("Could not find client_id {client_id} to rebind keys"); @@ -2363,6 +2383,7 @@ pub(crate) fn screen_thread_main( // ¯\_(ツ)_/¯ arrow_fonts: !arrow_fonts, }, + &client_attributes.keybinds, config_options.default_mode, ), draw_pane_frames, @@ -4028,8 +4049,14 @@ pub(crate) fn screen_thread_main( } screen.unblock_input()?; }, - ScreenInstruction::RebindKeys(new_keybinds, client_id) => { - screen.rebind_keys(new_keybinds, client_id).non_fatal(); + ScreenInstruction::Reconfigure { + client_id, + keybinds, + default_mode, + } => { + screen + .reconfigure_mode_info(keybinds, default_mode, client_id) + .non_fatal(); }, } } diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 5f7f0a66..09d2d794 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -114,6 +114,7 @@ fn send_cli_action_to_server( let get_current_dir = || PathBuf::from("."); let actions = Action::actions_from_cli(cli_action, Box::new(get_current_dir), None).unwrap(); let senders = session_metadata.senders.clone(); + let client_keybinds = session_metadata.client_keybinds.clone(); let default_mode = session_metadata.default_mode.clone(); let capabilities = PluginCapabilities::default(); let client_attributes = ClientAttributes::default(); @@ -130,7 +131,14 @@ fn send_cli_action_to_server( default_shell.clone(), default_layout.clone(), None, - default_mode, + client_keybinds + .get(&client_id) + .unwrap_or(&session_metadata.client_attributes.keybinds) + .clone(), + default_mode + .get(&client_id) + .unwrap_or(&InputMode::Normal) + .clone(), ) .unwrap(); } @@ -578,7 +586,7 @@ impl MockScreen { layout, client_input_modes: HashMap::new(), client_keybinds: HashMap::new(), - default_mode: InputMode::Normal, + default_mode: HashMap::new(), }; let os_input = FakeInputOutput::default(); diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs index 01049b37..d46b904a 100644 --- a/zellij-tile/src/shim.rs +++ b/zellij-tile/src/shim.rs @@ -809,8 +809,8 @@ pub fn dump_session_layout() { } /// Rebind keys for the current user -pub fn rebind_keys(keys: String) { - let plugin_command = PluginCommand::RebindKeys(keys); +pub fn reconfigure(new_config: String) { + let plugin_command = PluginCommand::Reconfigure(new_config); let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); object_to_stdout(&protobuf_plugin_command.encode_to_vec()); unsafe { host_run_plugin_command() }; diff --git a/zellij-utils/assets/config/default.kdl b/zellij-utils/assets/config/default.kdl index e830cd09..95f42fe4 100644 --- a/zellij-utils/assets/config/default.kdl +++ b/zellij-utils/assets/config/default.kdl @@ -120,6 +120,13 @@ keybinds { }; SwitchToMode "Normal" } + bind "c" { + LaunchOrFocusPlugin "configuration" { + floating true + move_to_focused_tab true + }; + SwitchToMode "Normal" + } } tmux { bind "[" { SwitchToMode "Scroll"; } @@ -147,6 +154,7 @@ keybinds { shared_except "locked" { bind "Ctrl g" { SwitchToMode "Locked"; } bind "Ctrl q" { Quit; } + bind "Alt f" { ToggleFloatingPanes; } bind "Alt n" { NewPane; } bind "Alt i" { MoveTab "Left"; } bind "Alt o" { MoveTab "Right"; } @@ -197,6 +205,7 @@ plugins { filepicker location="zellij:strider" { cwd "/" } + configuration location="zellij:configuration" } // Choose what to do when zellij receives SIGTERM, SIGINT, SIGQUIT or SIGHUP diff --git a/zellij-utils/assets/plugins/configuration.wasm b/zellij-utils/assets/plugins/configuration.wasm new file mode 100755 index 00000000..581dbfb1 Binary files /dev/null and b/zellij-utils/assets/plugins/configuration.wasm differ diff --git a/zellij-utils/assets/prost/api.plugin_command.rs b/zellij-utils/assets/prost/api.plugin_command.rs index 393f2e0c..1e57602e 100644 --- a/zellij-utils/assets/prost/api.plugin_command.rs +++ b/zellij-utils/assets/prost/api.plugin_command.rs @@ -119,7 +119,7 @@ pub mod plugin_command { #[prost(message, tag = "62")] NewTabsWithLayoutInfoPayload(super::NewTabsWithLayoutInfoPayload), #[prost(string, tag = "63")] - RebindKeysPayload(::prost::alloc::string::String), + ReconfigurePayload(::prost::alloc::string::String), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -433,7 +433,7 @@ pub enum CommandName { DumpSessionLayout = 84, CloseSelf = 85, NewTabsWithLayoutInfo = 86, - RebindKeys = 87, + Reconfigure = 87, } impl CommandName { /// String value of the enum field names used in the ProtoBuf definition. @@ -529,7 +529,7 @@ impl CommandName { CommandName::DumpSessionLayout => "DumpSessionLayout", CommandName::CloseSelf => "CloseSelf", CommandName::NewTabsWithLayoutInfo => "NewTabsWithLayoutInfo", - CommandName::RebindKeys => "RebindKeys", + CommandName::Reconfigure => "Reconfigure", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -622,7 +622,7 @@ impl CommandName { "DumpSessionLayout" => Some(Self::DumpSessionLayout), "CloseSelf" => Some(Self::CloseSelf), "NewTabsWithLayoutInfo" => Some(Self::NewTabsWithLayoutInfo), - "RebindKeys" => Some(Self::RebindKeys), + "Reconfigure" => Some(Self::Reconfigure), _ => None, } } diff --git a/zellij-utils/assets/prost/api.plugin_permission.rs b/zellij-utils/assets/prost/api.plugin_permission.rs index 50bc853f..013732d9 100644 --- a/zellij-utils/assets/prost/api.plugin_permission.rs +++ b/zellij-utils/assets/prost/api.plugin_permission.rs @@ -10,7 +10,7 @@ pub enum PermissionType { WebAccess = 6, ReadCliPipes = 7, MessageAndLaunchOtherPlugins = 8, - RebindKeys = 9, + Reconfigure = 9, } impl PermissionType { /// String value of the enum field names used in the ProtoBuf definition. @@ -30,7 +30,7 @@ impl PermissionType { PermissionType::MessageAndLaunchOtherPlugins => { "MessageAndLaunchOtherPlugins" } - PermissionType::RebindKeys => "RebindKeys", + PermissionType::Reconfigure => "Reconfigure", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -45,7 +45,7 @@ impl PermissionType { "WebAccess" => Some(Self::WebAccess), "ReadCliPipes" => Some(Self::ReadCliPipes), "MessageAndLaunchOtherPlugins" => Some(Self::MessageAndLaunchOtherPlugins), - "RebindKeys" => Some(Self::RebindKeys), + "Reconfigure" => Some(Self::Reconfigure), _ => None, } } diff --git a/zellij-utils/src/consts.rs b/zellij-utils/src/consts.rs index 05794495..1c11815c 100644 --- a/zellij-utils/src/consts.rs +++ b/zellij-utils/src/consts.rs @@ -110,6 +110,7 @@ mod not_wasm { add_plugin!(assets, "tab-bar.wasm"); add_plugin!(assets, "strider.wasm"); add_plugin!(assets, "session-manager.wasm"); + add_plugin!(assets, "configuration.wasm"); assets }; } diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 54c056e3..1a3c9881 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -942,7 +942,7 @@ pub enum Permission { WebAccess, ReadCliPipes, MessageAndLaunchOtherPlugins, - RebindKeys, + Reconfigure, } impl PermissionType { @@ -963,7 +963,7 @@ impl PermissionType { PermissionType::MessageAndLaunchOtherPlugins => { "Send messages to and launch other plugins".to_owned() }, - PermissionType::RebindKeys => "Rebind keys".to_owned(), + PermissionType::Reconfigure => "Change Zellij runtime configuration".to_owned(), } } } @@ -1164,6 +1164,9 @@ impl ModeInfo { pub fn update_keybinds(&mut self, keybinds: Keybinds) { self.keybinds = keybinds.to_keybinds_vec(); } + pub fn update_default_mode(&mut self, new_default_mode: InputMode) { + self.base_mode = Some(new_default_mode); + } } #[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)] @@ -1767,5 +1770,5 @@ pub enum PluginCommand { DumpSessionLayout, CloseSelf, NewTabsWithLayoutInfo(LayoutInfo), - RebindKeys(String), // String -> stringified keybindings + Reconfigure(String), // String -> stringified configuration } diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index 3656d8ad..8700dd15 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -353,7 +353,7 @@ pub enum ScreenContext { RenameSession, DumpLayoutToPlugin, ListClientsMetadata, - RebindKeys, + Reconfigure, } /// Stack call representations corresponding to the different types of [`PtyInstruction`]s. @@ -409,6 +409,7 @@ pub enum PluginContext { KeybindPipe, DumpLayoutToPlugin, ListClientsMetadata, + Reconfigure, } /// Stack call representations corresponding to the different types of [`ClientInstruction`]s. @@ -457,7 +458,7 @@ pub enum ServerContext { DisconnectAllClientsExcept, ChangeMode, ChangeModeForAllClients, - RebindKeys, + Reconfigure, } #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index 5d6d99de..32a8a828 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -712,16 +712,6 @@ impl Action { pub fn launches_plugin(&self, plugin_url: &str) -> bool { match self { Action::LaunchPlugin(run_plugin_or_alias, ..) => { - log::info!( - "1: {:?} == {:?}", - run_plugin_or_alias.location_string(), - plugin_url - ); - eprintln!( - "1: {:?} == {:?}", - run_plugin_or_alias.location_string(), - plugin_url - ); &run_plugin_or_alias.location_string() == plugin_url }, Action::LaunchOrFocusPlugin(run_plugin_or_alias, ..) => { diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs index dd17ac8c..296b5313 100644 --- a/zellij-utils/src/input/mod.rs +++ b/zellij-utils/src/input/mod.rs @@ -33,9 +33,10 @@ mod not_wasm { mode: InputMode, attributes: &ClientAttributes, capabilities: PluginCapabilities, + keybinds: &Keybinds, base_mode: Option, ) -> ModeInfo { - let keybinds = attributes.keybinds.to_keybinds_vec(); + let keybinds = keybinds.to_keybinds_vec(); let session_name = envs::get_session_name().ok(); ModeInfo { diff --git a/zellij-utils/src/input/plugins.rs b/zellij-utils/src/input/plugins.rs index ec68bd59..5a2ac645 100644 --- a/zellij-utils/src/input/plugins.rs +++ b/zellij-utils/src/input/plugins.rs @@ -65,6 +65,7 @@ impl PluginConfig { || tag == "compact-bar" || tag == "strider" || tag == "session-manager" + || tag == "configuration" { Some(PluginConfig { path: PathBuf::from(&tag), diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs index a69b6b02..02ef0ab8 100644 --- a/zellij-utils/src/kdl/mod.rs +++ b/zellij-utils/src/kdl/mod.rs @@ -1649,6 +1649,10 @@ impl Options { support_kitty_keyboard_protocol, }) } + pub fn from_string(stringified_keybindings: &String) -> Result { + let document: KdlDocument = stringified_keybindings.parse()?; + Options::from_kdl(&document) + } } impl Layout { diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto index 14ab3045..5442909e 100644 --- a/zellij-utils/src/plugin_api/plugin_command.proto +++ b/zellij-utils/src/plugin_api/plugin_command.proto @@ -98,7 +98,7 @@ enum CommandName { DumpSessionLayout = 84; CloseSelf = 85; NewTabsWithLayoutInfo = 86; - RebindKeys = 87; + Reconfigure = 87; } message PluginCommand { @@ -156,7 +156,7 @@ message PluginCommand { KillSessionsPayload kill_sessions_payload = 60; string scan_host_folder_payload = 61; NewTabsWithLayoutInfoPayload new_tabs_with_layout_info_payload = 62; - string rebind_keys_payload = 63; + string reconfigure_payload = 63; } } diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs index aeafc1d4..a6514b1b 100644 --- a/zellij-utils/src/plugin_api/plugin_command.rs +++ b/zellij-utils/src/plugin_api/plugin_command.rs @@ -888,11 +888,11 @@ impl TryFrom for PluginCommand { }, _ => Err("Mismatched payload for NewTabsWithLayoutInfo"), }, - Some(CommandName::RebindKeys) => match protobuf_plugin_command.payload { - Some(Payload::RebindKeysPayload(rebind_keys_payload)) => { - Ok(PluginCommand::RebindKeys(rebind_keys_payload)) + Some(CommandName::Reconfigure) => match protobuf_plugin_command.payload { + Some(Payload::ReconfigurePayload(reconfigure_payload)) => { + Ok(PluginCommand::Reconfigure(reconfigure_payload)) }, - _ => Err("Mismatched payload for RebindKeys"), + _ => Err("Mismatched payload for Reconfigure"), }, None => Err("Unrecognized plugin command"), } @@ -1426,9 +1426,9 @@ impl TryFrom for ProtobufPluginCommand { )), }) }, - PluginCommand::RebindKeys(rebind_keys_payload) => Ok(ProtobufPluginCommand { - name: CommandName::RebindKeys as i32, - payload: Some(Payload::RebindKeysPayload(rebind_keys_payload)), + PluginCommand::Reconfigure(reconfigure_payload) => Ok(ProtobufPluginCommand { + name: CommandName::Reconfigure as i32, + payload: Some(Payload::ReconfigurePayload(reconfigure_payload)), }), } } diff --git a/zellij-utils/src/plugin_api/plugin_permission.proto b/zellij-utils/src/plugin_api/plugin_permission.proto index cc1fda6a..399ebba5 100644 --- a/zellij-utils/src/plugin_api/plugin_permission.proto +++ b/zellij-utils/src/plugin_api/plugin_permission.proto @@ -12,5 +12,5 @@ enum PermissionType { WebAccess = 6; ReadCliPipes = 7; MessageAndLaunchOtherPlugins = 8; - RebindKeys = 9; + Reconfigure = 9; } diff --git a/zellij-utils/src/plugin_api/plugin_permission.rs b/zellij-utils/src/plugin_api/plugin_permission.rs index 968de917..5ea70157 100644 --- a/zellij-utils/src/plugin_api/plugin_permission.rs +++ b/zellij-utils/src/plugin_api/plugin_permission.rs @@ -24,7 +24,7 @@ impl TryFrom for PermissionType { ProtobufPermissionType::MessageAndLaunchOtherPlugins => { Ok(PermissionType::MessageAndLaunchOtherPlugins) }, - ProtobufPermissionType::RebindKeys => Ok(PermissionType::RebindKeys), + ProtobufPermissionType::Reconfigure => Ok(PermissionType::Reconfigure), } } } @@ -50,7 +50,7 @@ impl TryFrom for ProtobufPermissionType { PermissionType::MessageAndLaunchOtherPlugins => { Ok(ProtobufPermissionType::MessageAndLaunchOtherPlugins) }, - PermissionType::RebindKeys => Ok(ProtobufPermissionType::RebindKeys), + PermissionType::Reconfigure => Ok(ProtobufPermissionType::Reconfigure), } } } diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap index 09e02f71..0f919210 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 725 +assertion_line: 753 expression: "format!(\"{:#?}\", config)" --- Config { @@ -117,6 +117,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -552,6 +562,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -997,6 +1017,16 @@ Config { Normal, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -1590,6 +1620,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -2066,6 +2106,16 @@ Config { }: [ PageScrollDown, ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -2441,6 +2491,16 @@ Config { Scroll, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -2824,6 +2884,16 @@ Config { }: [ PageScrollDown, ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -3224,6 +3294,16 @@ Config { Normal, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -3545,6 +3625,16 @@ Config { Normal, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -3854,6 +3944,34 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'c', + ), + key_modifiers: {}, + }: [ + LaunchOrFocusPlugin( + Alias( + PluginAlias { + name: "configuration", + configuration: Some( + PluginUserConfiguration( + {}, + ), + ), + initial_cwd: None, + run_plugin: None, + }, + ), + true, + true, + false, + false, + ), + SwitchToMode( + Normal, + ), + ], KeyWithModifier { bare_key: Char( 'd', @@ -3862,6 +3980,16 @@ Config { }: [ Detach, ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -4238,6 +4366,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -4620,6 +4758,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -5064,6 +5212,16 @@ Config { }: [ Detach, ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -5409,6 +5567,18 @@ Config { ), initial_cwd: None, }, + "configuration": RunPlugin { + _allow_exec_host_cmd: false, + location: Zellij( + PluginTag( + "configuration", + ), + ), + configuration: PluginUserConfiguration( + {}, + ), + initial_cwd: None, + }, "filepicker": RunPlugin { _allow_exec_host_cmd: false, location: Zellij( diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap index 6a4cb7f9..04174c5f 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 783 +assertion_line: 811 expression: "format!(\"{:#?}\", config)" --- Config { @@ -117,6 +117,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -552,6 +562,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -997,6 +1017,16 @@ Config { Normal, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -1590,6 +1620,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -2066,6 +2106,16 @@ Config { }: [ PageScrollDown, ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -2441,6 +2491,16 @@ Config { Scroll, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -2824,6 +2884,16 @@ Config { }: [ PageScrollDown, ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -3224,6 +3294,16 @@ Config { Normal, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -3545,6 +3625,16 @@ Config { Normal, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -3854,6 +3944,34 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'c', + ), + key_modifiers: {}, + }: [ + LaunchOrFocusPlugin( + Alias( + PluginAlias { + name: "configuration", + configuration: Some( + PluginUserConfiguration( + {}, + ), + ), + initial_cwd: None, + run_plugin: None, + }, + ), + true, + true, + false, + false, + ), + SwitchToMode( + Normal, + ), + ], KeyWithModifier { bare_key: Char( 'd', @@ -3862,6 +3980,16 @@ Config { }: [ Detach, ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -4238,6 +4366,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -4620,6 +4758,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -5064,6 +5212,16 @@ Config { }: [ Detach, ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -5409,6 +5567,18 @@ Config { ), initial_cwd: None, }, + "configuration": RunPlugin { + _allow_exec_host_cmd: false, + location: Zellij( + PluginTag( + "configuration", + ), + ), + configuration: PluginUserConfiguration( + {}, + ), + initial_cwd: None, + }, "filepicker": RunPlugin { _allow_exec_host_cmd: false, location: Zellij( diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap index d533e426..028feefe 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 825 +assertion_line: 853 expression: "format!(\"{:#?}\", config)" --- Config { @@ -134,6 +134,18 @@ Config { ), initial_cwd: None, }, + "configuration": RunPlugin { + _allow_exec_host_cmd: false, + location: Zellij( + PluginTag( + "configuration", + ), + ), + configuration: PluginUserConfiguration( + {}, + ), + initial_cwd: None, + }, "filepicker": RunPlugin { _allow_exec_host_cmd: false, location: Zellij( diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap index 2cb867e7..ef40f8c5 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 811 +assertion_line: 839 expression: "format!(\"{:#?}\", config)" --- Config { @@ -117,6 +117,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -552,6 +562,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -997,6 +1017,16 @@ Config { Normal, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -1590,6 +1620,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -2066,6 +2106,16 @@ Config { }: [ PageScrollDown, ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -2441,6 +2491,16 @@ Config { Scroll, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -2824,6 +2884,16 @@ Config { }: [ PageScrollDown, ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -3224,6 +3294,16 @@ Config { Normal, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -3545,6 +3625,16 @@ Config { Normal, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -3854,6 +3944,34 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'c', + ), + key_modifiers: {}, + }: [ + LaunchOrFocusPlugin( + Alias( + PluginAlias { + name: "configuration", + configuration: Some( + PluginUserConfiguration( + {}, + ), + ), + initial_cwd: None, + run_plugin: None, + }, + ), + true, + true, + false, + false, + ), + SwitchToMode( + Normal, + ), + ], KeyWithModifier { bare_key: Char( 'd', @@ -3862,6 +3980,16 @@ Config { }: [ Detach, ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -4238,6 +4366,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -4620,6 +4758,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -5064,6 +5212,16 @@ Config { }: [ Detach, ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -5713,6 +5871,18 @@ Config { ), initial_cwd: None, }, + "configuration": RunPlugin { + _allow_exec_host_cmd: false, + location: Zellij( + PluginTag( + "configuration", + ), + ), + configuration: PluginUserConfiguration( + {}, + ), + initial_cwd: None, + }, "filepicker": RunPlugin { _allow_exec_host_cmd: false, location: Zellij( diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap index 808e9cfb..795d0cf9 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap @@ -1,6 +1,6 @@ --- source: zellij-utils/src/setup.rs -assertion_line: 797 +assertion_line: 825 expression: "format!(\"{:#?}\", config)" --- Config { @@ -117,6 +117,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -552,6 +562,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -997,6 +1017,16 @@ Config { Normal, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -1590,6 +1620,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -2066,6 +2106,16 @@ Config { }: [ PageScrollDown, ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -2441,6 +2491,16 @@ Config { Scroll, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -2824,6 +2884,16 @@ Config { }: [ PageScrollDown, ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -3224,6 +3294,16 @@ Config { Normal, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -3545,6 +3625,16 @@ Config { Normal, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -3854,6 +3944,34 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'c', + ), + key_modifiers: {}, + }: [ + LaunchOrFocusPlugin( + Alias( + PluginAlias { + name: "configuration", + configuration: Some( + PluginUserConfiguration( + {}, + ), + ), + initial_cwd: None, + run_plugin: None, + }, + ), + true, + true, + false, + false, + ), + SwitchToMode( + Normal, + ), + ], KeyWithModifier { bare_key: Char( 'd', @@ -3862,6 +3980,16 @@ Config { }: [ Detach, ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -4238,6 +4366,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -4620,6 +4758,16 @@ Config { Tmux, ), ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -5064,6 +5212,16 @@ Config { }: [ Detach, ], + KeyWithModifier { + bare_key: Char( + 'f', + ), + key_modifiers: { + Alt, + }, + }: [ + ToggleFloatingPanes, + ], KeyWithModifier { bare_key: Char( 'g', @@ -5409,6 +5567,18 @@ Config { ), initial_cwd: None, }, + "configuration": RunPlugin { + _allow_exec_host_cmd: false, + location: Zellij( + PluginTag( + "configuration", + ), + ), + configuration: PluginUserConfiguration( + {}, + ), + initial_cwd: None, + }, "filepicker": RunPlugin { _allow_exec_host_cmd: false, location: Zellij(