aboutsummaryrefslogtreecommitdiffstats
path: root/default-plugins
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2024-07-17 16:08:31 +0200
committerGitHub <noreply@github.com>2024-07-17 16:08:31 +0200
commit84ff29dd024c73e94f3e65d56d907ae903fe3752 (patch)
treeb3f01370b3b1af71f018eeb27a1437b75e959721 /default-plugins
parentdocs(changelog): scroll region fix (diff)
downloadsl0ppij-84ff29dd024c73e94f3e65d56d907ae903fe3752.tar.gz
sl0ppij-84ff29dd024c73e94f3e65d56d907ae903fe3752.tar.xz
sl0ppij-84ff29dd024c73e94f3e65d56d907ae903fe3752.zip
feat(ui): configuration screen + configure non-colliding keys at runtime (#3502)
* rebind => reconfigure * persist keybinds and mode info to new tabs * add configuration plugin * make tests pass * remove warnings * style(fmt): rustfmt
Diffstat (limited to 'default-plugins')
-rw-r--r--default-plugins/configuration/.cargo/config.toml2
-rw-r--r--default-plugins/configuration/.gitignore1
-rw-r--r--default-plugins/configuration/Cargo.toml11
l---------default-plugins/configuration/LICENSE.md1
-rw-r--r--default-plugins/configuration/src/main.rs1987
-rw-r--r--default-plugins/fixture-plugin-for-tests/src/main.rs4
-rw-r--r--default-plugins/status-bar/src/one_line_ui.rs20
7 files changed, 2024 insertions, 2 deletions
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 <aram@poor.dev>"]
+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<String, String>,
+ selected_index: Option<usize>,
+ selected_primary_key_index: usize,
+ selected_secondary_key_index: usize,
+ remapping_leaders: bool,
+ primary_modifier: BTreeSet<KeyModifier>,
+ secondary_modifier: BTreeSet<KeyModifier>,
+ possible_modifiers: Vec<KeyModifier>,
+ 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<String, String>) {
+ 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::<Vec<_>>()
+ .join(" "),
+ self.secondary_modifier
+ .iter()
+ .map(|m| m.to_string())
+ .collect::<Vec<_>>()
+ .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::<Vec<_>>()
+ .join(" "),
+ self.secondary_modifier
+ .iter()
+ .map(|m| m.to_string())
+ .collect::<Vec<_>>()
+ .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 = "<←↓↑→> / <SPACE> ";
+ 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, <l> - leaders, <ESC> - 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: <↓↑> / <ENTER> / <l> / <ESC>";
+ 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: <ENTER> - when done";
+ print_text_with_coordinates(
+ Text::new(help_text).color_range(2, 6..13),
+ 0,
+ rows,
+ None,
+ None,
+ );
+ } else {
+ let help_text = "Help: <ENTER> / <←↓↑→> / <SPACE>";
+ 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() {
+ "<UNBOUND>".to_owned()
+ } else {
+ self.primary_modifier
+ .iter()
+ .map(|m| m.to_string())
+ .collect::<Vec<_>>()
+ .join("-")
+ }
+ }
+ fn secondary_modifier_text(&self) -> String {
+ if self.secondary_modifier.is_empty() {
+ "<UNBOUND>".to_owned()
+ } else {
+ self.secondary_modifier
+ .iter()
+ .map(|m| m.to_string())
+ .collect::<Vec<_>>()
+ .join("-")
+ }
+ }
+ fn 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<String> {
+ 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<KeyWithModifier
]} else if mi.mode == IM::Session { vec![
(s("Detach"), s("Detach"), action_key(&km, &[Action::Detach])),
(s("Session Manager"), s("Manager"), session_manager_key(&km)),
+ (s("Configure"), s("Config"), configuration_key(&km)),
(s("Select pane"), s("Select"), to_basemode_key),
]} else if mi.mode == IM::Tmux { vec![
(s("Move focus"), s("Move"), action_key_group(&km, &[
@@ -1355,6 +1356,25 @@ fn session_manager_key(keymap: &[(KeyWithModifier, Vec<Action>)]) -> Vec<KeyWith
}
}
+fn configuration_key(keymap: &[(KeyWithModifier, Vec<Action>)]) -> Vec<KeyWithModifier> {
+ let mut matching = keymap.iter().find_map(|(key, acvec)| {
+ let has_match = acvec
+ .iter()
+ .find(|a| a.launches_plugin("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<usize>) -> LinePart {
if keyvec.is_empty() {
return LinePart::default();