aboutsummaryrefslogtreecommitdiffstats
path: root/default-plugins/multiple-select/src
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2025-06-03 17:15:32 +0200
committerGitHub <noreply@github.com>2025-06-03 17:15:32 +0200
commita9f8bbcd197c8d14832fc1ab1fbe4d4111a2014d (patch)
tree6d354fdd36cf534d73e0cb81a7cacd3fedc1ebb9 /default-plugins/multiple-select/src
parentdocs(changelog): theme fixes (diff)
downloadsl0ppij-a9f8bbcd197c8d14832fc1ab1fbe4d4111a2014d.tar.gz
sl0ppij-a9f8bbcd197c8d14832fc1ab1fbe4d4111a2014d.tar.xz
sl0ppij-a9f8bbcd197c8d14832fc1ab1fbe4d4111a2014d.zip
feat(ux): improve multiple select (#4221)
* intercept/clear-intercept key APIs * allow opening a pinned unfocused floating pane * rework plugin * improve some apis * fix tests * tests for pane groups * more exact placement and tests * plugin command permission and cleanup * improve some multiselect ux * improve plugin ui * remove old status indicator * allow moving plugin out of the way * style(fmt): rustfmt * update plugins * remove old keybinding * cleanups * fix: only rename pane if needed * changelog and some cleanups * style(fmt): rustfmt
Diffstat (limited to 'default-plugins/multiple-select/src')
-rw-r--r--default-plugins/multiple-select/src/main.rs770
-rw-r--r--default-plugins/multiple-select/src/state.rs634
-rw-r--r--default-plugins/multiple-select/src/ui.rs873
3 files changed, 591 insertions, 1686 deletions
diff --git a/default-plugins/multiple-select/src/main.rs b/default-plugins/multiple-select/src/main.rs
index 773fd6fb..6bcd9b9b 100644
--- a/default-plugins/multiple-select/src/main.rs
+++ b/default-plugins/multiple-select/src/main.rs
@@ -1,9 +1,6 @@
-pub mod state;
-pub mod ui;
-
-use state::{MarkedIndex, VisibilityAndFocus};
use std::collections::BTreeMap;
-use ui::PaneItem;
+use std::time::Instant;
+use zellij_tile::prelude::actions::Action;
use zellij_tile::prelude::*;
#[derive(Debug, Default)]
@@ -12,13 +9,17 @@ pub struct App {
own_client_id: Option<ClientId>,
own_tab_index: Option<usize>,
total_tabs_in_session: Option<usize>,
- search_string: String,
- previous_search_string: String, // used eg. for the new tab title when breaking panes
- left_side_panes: Vec<PaneItem>,
- right_side_panes: Vec<PaneItem>,
- search_results: Option<Vec<PaneItem>>,
- visibility_and_focus: VisibilityAndFocus,
- marked_index: Option<MarkedIndex>,
+ grouped_panes: Vec<PaneId>,
+ grouped_panes_count: usize,
+ mode_info: ModeInfo,
+ closing: bool,
+ highlighted_at: Option<Instant>,
+ baseline_ui_width: usize,
+ current_rows: usize,
+ current_cols: usize,
+ display_area_rows: usize,
+ display_area_cols: usize,
+ alternate_coordinates: bool,
}
register_plugin!(App);
@@ -27,186 +28,597 @@ impl ZellijPlugin for App {
fn load(&mut self, _configuration: BTreeMap<String, String>) {
subscribe(&[
EventType::Key,
- EventType::Mouse,
+ EventType::InterceptedKeyPress,
EventType::ModeUpdate,
- EventType::RunCommandResult,
- EventType::TabUpdate,
EventType::PaneUpdate,
- EventType::FailedToWriteConfigToDisk,
- EventType::ConfigWasWrittenToDisk,
- EventType::BeforeClose,
+ EventType::TabUpdate,
+ EventType::Timer,
]);
+
let plugin_ids = get_plugin_ids();
self.own_plugin_id = Some(plugin_ids.plugin_id);
self.own_client_id = Some(plugin_ids.client_id);
- rename_plugin_pane(plugin_ids.plugin_id, "Multiple Select");
+
+ intercept_key_presses();
+ set_selectable(false);
}
+
fn update(&mut self, event: Event) -> bool {
- let mut should_render = false;
+ if self.closing {
+ return false;
+ }
match event {
- Event::PaneUpdate(pane_manifest) => {
- self.react_to_zellij_state_update(pane_manifest);
- should_render = true;
- },
- Event::Key(key) => {
- match key.bare_key {
- BareKey::Tab if key.has_no_modifiers() => {
- self.visibility_and_focus.toggle_focus();
- self.marked_index = None;
- self.update_highlighted_panes();
- should_render = true;
- },
- BareKey::Char(character)
- if key.has_no_modifiers()
- && self.visibility_and_focus.left_side_is_focused()
- && self.marked_index.is_none() =>
- {
- self.search_string.push(character);
- self.update_search_results();
- should_render = true;
- },
- BareKey::Backspace
- if key.has_no_modifiers()
- && self.visibility_and_focus.left_side_is_focused()
- && self.marked_index.is_none() =>
- {
- self.search_string.pop();
- self.update_search_results();
- should_render = true;
- },
- BareKey::Enter if key.has_no_modifiers() => {
- if self.visibility_and_focus.left_side_is_focused() {
- if let Some(marked_index) = self.marked_index.take() {
- let keep_left_side_focused = false;
- self.group_panes(marked_index, keep_left_side_focused);
- } else {
- match self.search_results.take() {
- Some(search_results) => {
- self.group_search_results(search_results)
- },
- None => self.group_all_panes(),
- }
- self.handle_left_side_emptied();
- }
- }
- should_render = true;
- },
- BareKey::Right
- if key.has_no_modifiers()
- && self.visibility_and_focus.left_side_is_focused() =>
- {
- if let Some(marked_index) = self.marked_index.take() {
- let keep_left_side_focused = true;
- self.group_panes(marked_index, keep_left_side_focused);
- should_render = true;
- }
- },
- BareKey::Left
- if key.has_no_modifiers()
- && self.visibility_and_focus.right_side_is_focused() =>
- {
- if self.visibility_and_focus.right_side_is_focused() {
- if let Some(marked_index) = self.marked_index.take() {
- self.ungroup_panes(marked_index);
- should_render = true;
- }
- }
- },
- BareKey::Char('c') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
- if self.visibility_and_focus.right_side_is_focused() {
- // this means we're in the selection panes part and we want to clear
- // them
- self.ungroup_all_panes();
- } else if self.visibility_and_focus.left_side_is_focused() {
- if self.marked_index.is_some() {
- self.marked_index = None;
- self.update_highlighted_panes();
- } else {
- self.ungroup_all_panes_and_close_self();
- }
- }
- should_render = true;
- },
- BareKey::Down if key.has_no_modifiers() => {
- self.move_marked_index_down();
- should_render = true;
- },
- BareKey::Up if key.has_no_modifiers() => {
- self.move_marked_index_up();
- should_render = true;
- },
- BareKey::Char(' ') if key.has_no_modifiers() && self.marked_index.is_some() => {
- self.mark_entry();
- should_render = true;
- },
- BareKey::Char('b')
- if key.has_no_modifiers()
- && self.visibility_and_focus.right_side_is_focused() =>
- {
- self.break_grouped_panes_to_new_tab();
- },
- BareKey::Char('s')
- if key.has_no_modifiers()
- && self.visibility_and_focus.right_side_is_focused() =>
- {
- self.stack_grouped_panes();
- },
- BareKey::Char('f')
- if key.has_no_modifiers()
- && self.visibility_and_focus.right_side_is_focused() =>
- {
- self.float_grouped_panes();
- },
- BareKey::Char('e')
- if key.has_no_modifiers()
- && self.visibility_and_focus.right_side_is_focused() =>
- {
- self.embed_grouped_panes();
- },
- BareKey::Char('r')
- if key.has_no_modifiers()
- && self.visibility_and_focus.right_side_is_focused() =>
- {
- self.break_grouped_panes_right();
- },
- BareKey::Char('l')
- if key.has_no_modifiers()
- && self.visibility_and_focus.right_side_is_focused() =>
- {
- self.break_grouped_panes_left();
- },
- BareKey::Char('c')
- if key.has_no_modifiers()
- && self.visibility_and_focus.right_side_is_focused() =>
- {
- self.close_grouped_panes();
- },
- _ => {},
- }
- },
- Event::BeforeClose => {
- self.unhighlight_all_panes();
- },
- _ => {},
+ Event::ModeUpdate(mode_info) => self.handle_mode_update(mode_info),
+ Event::PaneUpdate(pane_manifest) => self.handle_pane_update(pane_manifest),
+ Event::TabUpdate(tab_infos) => self.handle_tab_update(tab_infos),
+ Event::InterceptedKeyPress(key) => self.handle_key_press(key),
+ Event::Timer(_) => self.handle_timer(),
+ _ => false,
}
- should_render
}
+
fn render(&mut self, rows: usize, cols: usize) {
- self.render_close_shortcut(cols);
- self.render_tab_shortcut(cols, rows);
- match self.visibility_and_focus {
- VisibilityAndFocus::OnlyLeftSideVisible => self.render_left_side(rows, cols, true),
- VisibilityAndFocus::OnlyRightSideVisible => self.render_right_side(rows, cols, true),
- VisibilityAndFocus::BothSidesVisibleLeftSideFocused => {
- self.render_left_side(rows, cols, true);
- self.render_right_side(rows, cols, false);
- },
- VisibilityAndFocus::BothSidesVisibleRightSideFocused => {
- self.render_left_side(rows, cols, false);
- self.render_right_side(rows, cols, true);
+ self.update_current_size(rows, cols);
+ let ui_width = self.calculate_ui_width();
+ self.update_baseline_ui_width(ui_width);
+ let base_x = cols.saturating_sub(self.baseline_ui_width) / 2;
+ let base_y = rows.saturating_sub(8) / 2;
+ self.render_header(base_x, base_y);
+ self.render_shortcuts(base_x, base_y + 2);
+ self.render_controls(base_x, base_y + 7);
+ }
+}
+
+impl App {
+ fn update_current_size(&mut self, new_rows: usize, new_cols: usize) {
+ let size_changed = new_rows != self.current_rows || new_cols != self.current_cols;
+ self.current_rows = new_rows;
+ self.current_cols = new_cols;
+ if size_changed {
+ self.baseline_ui_width = 0;
+ }
+ }
+ fn update_baseline_ui_width(&mut self, current_ui_width: usize) {
+ if current_ui_width > self.baseline_ui_width {
+ self.baseline_ui_width = current_ui_width;
+ }
+ }
+
+ fn calculate_ui_width(&self) -> usize {
+ let controls_width = group_controls_length(&self.mode_info);
+
+ let header_width = Self::header_text().0.len();
+ let shortcuts_max_width = Self::shortcuts_max_width();
+
+ std::cmp::max(
+ controls_width,
+ std::cmp::max(header_width, shortcuts_max_width),
+ )
+ }
+
+ fn header_text() -> (&'static str, Text) {
+ let header_text = "<ESC> - cancel, <TAB> - move";
+ let header_text_component = Text::new(header_text)
+ .color_substring(3, "<ESC>")
+ .color_substring(3, "<TAB>");
+ (header_text, header_text_component)
+ }
+
+ fn shortcuts_max_width() -> usize {
+ std::cmp::max(
+ std::cmp::max(
+ Self::group_actions_text().0.len(),
+ Self::shortcuts_line1_text().0.len(),
+ ),
+ std::cmp::max(
+ Self::shortcuts_line2_text().0.len(),
+ Self::shortcuts_line3_text().0.len(),
+ ),
+ )
+ }
+
+ fn group_actions_text() -> (&'static str, Text) {
+ let text = "GROUP ACTIONS";
+ let component = Text::new(text).color_all(2);
+ (text, component)
+ }
+
+ fn shortcuts_line1_text() -> (&'static str, Text) {
+ let text = "<b> - break out, <s> - stack, <c> - close";
+ let component = Text::new(text)
+ .color_substring(3, "<b>")
+ .color_substring(3, "<s>")
+ .color_substring(3, "<c>");
+ (text, component)
+ }
+
+ fn shortcuts_line2_text() -> (&'static str, Text) {
+ let text = "<r> - break right, <l> - break left";
+ let component = Text::new(text)
+ .color_substring(3, "<r>")
+ .color_substring(3, "<l>");
+ (text, component)
+ }
+
+ fn shortcuts_line3_text() -> (&'static str, Text) {
+ let text = "<e> - embed, <f> - float";
+ let component = Text::new(text)
+ .color_substring(3, "<e>")
+ .color_substring(3, "<f>");
+ (text, component)
+ }
+
+ fn handle_mode_update(&mut self, mode_info: ModeInfo) -> bool {
+ if self.mode_info != mode_info {
+ self.mode_info = mode_info;
+ let ui_width = self.calculate_ui_width();
+ self.update_baseline_ui_width(ui_width);
+ true
+ } else {
+ false
+ }
+ }
+
+ fn handle_pane_update(&mut self, pane_manifest: PaneManifest) -> bool {
+ let Some(own_client_id) = self.own_client_id else {
+ return false;
+ };
+
+ self.update_grouped_panes(&pane_manifest, own_client_id);
+ self.update_tab_info(&pane_manifest);
+ self.total_tabs_in_session = Some(pane_manifest.panes.keys().count());
+
+ true
+ }
+
+ fn handle_tab_update(&mut self, tab_infos: Vec<TabInfo>) -> bool {
+ for tab in tab_infos {
+ if tab.active {
+ self.display_area_rows = tab.display_area_rows;
+ self.display_area_cols = tab.display_area_columns;
+ break;
+ }
+ }
+
+ false
+ }
+
+ fn update_grouped_panes(&mut self, pane_manifest: &PaneManifest, own_client_id: ClientId) {
+ self.grouped_panes.clear();
+ let mut count = 0;
+
+ for (_tab_index, pane_infos) in &pane_manifest.panes {
+ for pane_info in pane_infos {
+ if pane_info.index_in_pane_group.get(&own_client_id).is_some() {
+ let pane_id = if pane_info.is_plugin {
+ PaneId::Plugin(pane_info.id)
+ } else {
+ PaneId::Terminal(pane_info.id)
+ };
+ self.grouped_panes.push(pane_id);
+ count += 1;
+ }
+ }
+ }
+ if count == 0 {
+ self.close_self();
+ }
+
+ let previous_count = self.grouped_panes_count;
+ self.grouped_panes_count = count;
+ if let Some(own_plugin_id) = self.own_plugin_id {
+ let title = if count == 1 {
+ "SELECTED PANE"
+ } else {
+ "SELECTED PANES"
+ };
+ if previous_count != count {
+ rename_plugin_pane(own_plugin_id, format!("{} {}", count, title));
+ }
+ if previous_count != 0 && count != 0 && previous_count != count {
+ if self.doherty_threshold_elapsed_since_highlight() {
+ self.highlighted_at = Some(Instant::now());
+ highlight_and_unhighlight_panes(vec![PaneId::Plugin(own_plugin_id)], vec![]);
+ set_timeout(0.4);
+ }
+ }
+ }
+ }
+
+ fn doherty_threshold_elapsed_since_highlight(&self) -> bool {
+ self.highlighted_at
+ .map(|h| h.elapsed() >= std::time::Duration::from_millis(400))
+ .unwrap_or(true)
+ }
+
+ fn update_tab_info(&mut self, pane_manifest: &PaneManifest) {
+ for (tab_index, pane_infos) in &pane_manifest.panes {
+ for pane_info in pane_infos {
+ if pane_info.is_plugin && Some(pane_info.id) == self.own_plugin_id {
+ self.own_tab_index = Some(*tab_index);
+ return;
+ }
+ }
+ }
+ }
+
+ fn handle_key_press(&mut self, key: KeyWithModifier) -> bool {
+ if !key.has_no_modifiers() {
+ return false;
+ }
+
+ match key.bare_key {
+ BareKey::Char('b') => self.break_grouped_panes_to_new_tab(),
+ BareKey::Char('s') => self.stack_grouped_panes(),
+ BareKey::Char('f') => self.float_grouped_panes(),
+ BareKey::Char('e') => self.embed_grouped_panes(),
+ BareKey::Char('r') => self.break_grouped_panes_right(),
+ BareKey::Char('l') => self.break_grouped_panes_left(),
+ BareKey::Char('c') => self.close_grouped_panes(),
+ BareKey::Tab => self.next_coordinates(),
+ BareKey::Esc => {
+ self.ungroup_panes_in_zellij(&self.grouped_panes.clone());
+ self.close_self();
},
+ _ => return false,
+ }
+ false
+ }
+ fn handle_timer(&mut self) -> bool {
+ if let Some(own_plugin_id) = self.own_plugin_id {
+ if self.doherty_threshold_elapsed_since_highlight() {
+ highlight_and_unhighlight_panes(vec![], vec![PaneId::Plugin(own_plugin_id)]);
+ }
+ }
+ false
+ }
+
+ fn render_header(&self, base_x: usize, base_y: usize) {
+ let header_text = Self::header_text();
+
+ print_text_with_coordinates(header_text.1, base_x, base_y, None, None);
+ }
+
+ fn render_shortcuts(&self, base_x: usize, base_y: usize) {
+ let mut running_y = base_y;
+ print_text_with_coordinates(Self::group_actions_text().1, base_x, running_y, None, None);
+ running_y += 1;
+
+ print_text_with_coordinates(
+ Self::shortcuts_line1_text().1,
+ base_x,
+ running_y,
+ None,
+ None,
+ );
+ running_y += 1;
+
+ print_text_with_coordinates(
+ Self::shortcuts_line2_text().1,
+ base_x,
+ running_y,
+ None,
+ None,
+ );
+ running_y += 1;
+
+ print_text_with_coordinates(
+ Self::shortcuts_line3_text().1,
+ base_x,
+ running_y,
+ None,
+ None,
+ );
+ }
+
+ fn render_controls(&self, base_x: usize, base_y: usize) {
+ render_group_controls(&self.mode_info, base_x, base_y);
+ }
+
+ fn execute_action_and_close<F>(&mut self, action: F)
+ where
+ F: FnOnce(&[PaneId]),
+ {
+ let pane_ids = self.grouped_panes.clone();
+ action(&pane_ids);
+ self.close_self();
+ }
+
+ pub fn break_grouped_panes_to_new_tab(&mut self) {
+ self.execute_action_and_close(|pane_ids| {
+ break_panes_to_new_tab(pane_ids, None, true);
+ });
+ self.ungroup_panes_in_zellij(&self.grouped_panes.clone());
+ }
+
+ pub fn stack_grouped_panes(&mut self) {
+ self.execute_action_and_close(|pane_ids| {
+ stack_panes(pane_ids.to_vec());
+ });
+ self.ungroup_panes_in_zellij(&self.grouped_panes.clone());
+ }
+
+ pub fn float_grouped_panes(&mut self) {
+ self.execute_action_and_close(|pane_ids| {
+ float_multiple_panes(pane_ids.to_vec());
+ });
+ self.ungroup_panes_in_zellij(&self.grouped_panes.clone());
+ }
+
+ pub fn embed_grouped_panes(&mut self) {
+ self.execute_action_and_close(|pane_ids| {
+ embed_multiple_panes(pane_ids.to_vec());
+ });
+ self.ungroup_panes_in_zellij(&self.grouped_panes.clone());
+ }
+
+ pub fn break_grouped_panes_right(&mut self) {
+ let Some(own_tab_index) = self.own_tab_index else {
+ return;
+ };
+
+ let pane_ids = self.grouped_panes.clone();
+
+ if Some(own_tab_index + 1) < self.total_tabs_in_session {
+ break_panes_to_tab_with_index(&pane_ids, own_tab_index + 1, true);
+ } else {
+ break_panes_to_new_tab(&pane_ids, None, true);
+ }
+
+ self.close_self();
+ }
+
+ pub fn break_grouped_panes_left(&mut self) {
+ let Some(own_tab_index) = self.own_tab_index else {
+ return;
+ };
+
+ let pane_ids = self.grouped_panes.clone();
+
+ if own_tab_index > 0 {
+ break_panes_to_tab_with_index(&pane_ids, own_tab_index - 1, true);
+ } else {
+ break_panes_to_new_tab(&pane_ids, None, true);
+ }
+
+ self.close_self();
+ }
+
+ pub fn close_grouped_panes(&mut self) {
+ self.execute_action_and_close(|pane_ids| {
+ close_multiple_panes(pane_ids.to_vec());
+ });
+ }
+
+ pub fn ungroup_panes_in_zellij(&mut self, pane_ids: &[PaneId]) {
+ group_and_ungroup_panes(vec![], pane_ids.to_vec());
+ }
+ pub fn close_self(&mut self) {
+ self.closing = true;
+ close_self();
+ }
+ pub fn next_coordinates(&mut self) {
+ let width_30_percent = (self.display_area_cols as f64 * 0.3) as usize;
+ let height_30_percent = (self.display_area_rows as f64 * 0.3) as usize;
+ let width = std::cmp::max(width_30_percent, 48);
+ let height = std::cmp::max(height_30_percent, 10);
+ let y_position = self.display_area_rows.saturating_sub(height + 2);
+ if let Some(own_plugin_id) = self.own_plugin_id {
+ if self.alternate_coordinates {
+ let x_position = 2;
+ let Some(next_coordinates) = FloatingPaneCoordinates::new(
+ Some(format!("{}", x_position)),
+ Some(format!("{}", y_position)),
+ Some(format!("{}", width)),
+ Some(format!("{}", height)),
+ Some(true),
+ ) else {
+ return;
+ };
+ change_floating_panes_coordinates(vec![(
+ PaneId::Plugin(own_plugin_id),
+ next_coordinates,
+ )]);
+ self.alternate_coordinates = false;
+ } else {
+ let x_position = self
+ .display_area_cols
+ .saturating_sub(width)
+ .saturating_sub(2);
+ let Some(next_coordinates) = FloatingPaneCoordinates::new(
+ Some(format!("{}", x_position)),
+ Some(format!("{}", y_position)),
+ Some(format!("{}", width)),
+ Some(format!("{}", height)),
+ Some(true),
+ ) else {
+ return;
+ };
+ change_floating_panes_coordinates(vec![(
+ PaneId::Plugin(own_plugin_id),
+ next_coordinates,
+ )]);
+ self.alternate_coordinates = true;
+ }
}
- self.render_focus_boundary(rows, cols);
- self.render_help_line(rows, cols);
}
}
+
+fn render_group_controls(mode_info: &ModeInfo, base_x: usize, base_y: usize) {
+ let keymap = mode_info.get_mode_keybinds();
+ let (common_modifiers, pane_group_key, group_mark_key) = extract_key_bindings(&keymap);
+
+ let pane_group_bound = pane_group_key != "UNBOUND";
+ let group_mark_bound = group_mark_key != "UNBOUND";
+
+ if !pane_group_bound && !group_mark_bound {
+ return;
+ }
+
+ render_common_modifiers(&common_modifiers, base_x, base_y);
+
+ let mut next_x = base_x + render_common_modifiers(&common_modifiers, base_x, base_y);
+
+ if pane_group_bound {
+ next_x = render_toggle_group_ribbon(&pane_group_key, next_x, base_y);
+ }
+
+ if group_mark_bound {
+ render_follow_focus_ribbon(&group_mark_key, next_x, base_y, mode_info);
+ }
+}
+
+fn group_controls_length(mode_info: &ModeInfo) -> usize {
+ let keymap = mode_info.get_mode_keybinds();
+ let (common_modifiers, pane_group_key, group_mark_key) = extract_key_bindings(&keymap);
+
+ let pane_group_bound = pane_group_key != "UNBOUND";
+ let group_mark_bound = group_mark_key != "UNBOUND";
+
+ let mut length = 0;
+
+ if !common_modifiers.is_empty() {
+ let modifiers_text = format!(
+ "{} + ",
+ common_modifiers
+ .iter()
+ .map(|m| m.to_string())
+ .collect::<Vec<_>>()
+ .join(" ")
+ );
+ length += modifiers_text.chars().count();
+ }
+
+ if pane_group_bound {
+ let toggle_text = format!("<{}> Toggle", pane_group_key);
+ length += toggle_text.chars().count() + 4;
+ }
+
+ if group_mark_bound {
+ let follow_text = format!("<{}> Follow Focus", group_mark_key);
+ length += follow_text.chars().count() + 4;
+ }
+
+ length
+}
+
+fn extract_key_bindings(
+ keymap: &[(KeyWithModifier, Vec<Action>)],
+) -> (Vec<KeyModifier>, String, String) {
+ let pane_group_keys = get_key_for_action(keymap, &[Action::TogglePaneInGroup]);
+ let group_mark_keys = get_key_for_action(keymap, &[Action::ToggleGroupMarking]);
+
+ let key_refs: Vec<&KeyWithModifier> = [pane_group_keys.first(), group_mark_keys.first()]
+ .into_iter()
+ .flatten()
+ .collect();
+
+ let common_modifiers = get_common_modifiers(key_refs);
+
+ let pane_group_key = format_key_without_modifiers(&pane_group_keys, &common_modifiers);
+ let group_mark_key = format_key_without_modifiers(&group_mark_keys, &common_modifiers);
+
+ (common_modifiers, pane_group_key, group_mark_key)
+}
+
+fn format_key_without_modifiers(
+ keys: &[KeyWithModifier],
+ common_modifiers: &[KeyModifier],
+) -> String {
+ keys.first()
+ .map(|key| format!("{}", key.strip_common_modifiers(&common_modifiers.to_vec())))
+ .unwrap_or_else(|| "UNBOUND".to_string())
+}
+
+fn render_common_modifiers(
+ common_modifiers: &[KeyModifier],
+ base_x: usize,
+ base_y: usize,
+) -> usize {
+ if !common_modifiers.is_empty() {
+ let modifiers_text = format!(
+ "{} + ",
+ common_modifiers
+ .iter()
+ .map(|m| m.to_string())
+ .collect::<Vec<_>>()
+ .join(" ")
+ );
+
+ print_text_with_coordinates(
+ Text::new(&modifiers_text).color_all(0),
+ base_x,
+ base_y,
+ None,
+ None,
+ );
+
+ modifiers_text.chars().count()
+ } else {
+ 0
+ }
+}
+
+fn get_key_for_action(
+ keymap: &[(KeyWithModifier, Vec<Action>)],
+ target_action: &[Action],
+) -> Vec<KeyWithModifier> {
+ keymap
+ .iter()
+ .find_map(|(key, actions)| {
+ if actions.first() == target_action.first() {
+ Some(key.clone())
+ } else {
+ None
+ }
+ })
+ .map(|key| vec![key])
+ .unwrap_or_default()
+}
+
+fn get_common_modifiers(keys: Vec<&KeyWithModifier>) -> Vec<KeyModifier> {
+ if keys.is_empty() {
+ return vec![];
+ }
+
+ let mut common = keys[0].key_modifiers.clone();
+
+ for key in keys.iter().skip(1) {
+ common = common.intersection(&key.key_modifiers).cloned().collect();
+ }
+
+ common.into_iter().collect()
+}
+
+fn render_follow_focus_ribbon(
+ group_mark_key: &str,
+ x_position: usize,
+ base_y: usize,
+ mode_info: &ModeInfo,
+) {
+ let follow_text = format!("<{}> Follow Focus", group_mark_key);
+ let key_highlight = format!("{}", group_mark_key);
+
+ let mut ribbon = Text::new(&follow_text).color_substring(0, &key_highlight);
+
+ if mode_info.currently_marking_pane_group.unwrap_or(false) {
+ ribbon = ribbon.selected();
+ }
+
+ print_ribbon_with_coordinates(ribbon, x_position, base_y, None, None);
+}
+
+fn render_toggle_group_ribbon(pane_group_key: &str, base_x: usize, base_y: usize) -> usize {
+ let toggle_text = format!("<{}> Toggle", pane_group_key);
+ let key_highlight = format!("{}", pane_group_key);
+
+ print_ribbon_with_coordinates(
+ Text::new(&toggle_text).color_substring(0, &key_highlight),
+ base_x,
+ base_y,
+ None,
+ None,
+ );
+
+ base_x + toggle_text.len() + 4
+}
diff --git a/default-plugins/multiple-select/src/state.rs b/default-plugins/multiple-select/src/state.rs
deleted file mode 100644
index 3bf863f8..00000000
--- a/default-plugins/multiple-select/src/state.rs
+++ /dev/null
@@ -1,634 +0,0 @@
-use crate::{ui::PaneItem, App};
-use std::collections::{BTreeMap, BTreeSet, HashSet};
-
-use fuzzy_matcher::skim::SkimMatcherV2;
-use fuzzy_matcher::FuzzyMatcher;
-
-use zellij_tile::prelude::*;
-
-#[derive(Debug, Default)]
-pub struct MarkedIndex {
- pub main_index: usize,
- pub additional_indices: HashSet<usize>,
-}
-
-impl MarkedIndex {
- pub fn new(main_index: usize) -> Self {
- MarkedIndex {
- main_index,
- additional_indices: HashSet::new(),
- }
- }
-}
-
-impl MarkedIndex {
- pub fn toggle_additional_mark(&mut self) {
- if self.additional_indices.contains(&self.main_index) {
- self.additional_indices.retain(|a| a != &self.main_index);
- } else {
- self.additional_indices.insert(self.main_index);
- }
- }
-}
-
-#[derive(Debug)]
-pub enum VisibilityAndFocus {
- OnlyLeftSideVisible,
- OnlyRightSideVisible,
- BothSidesVisibleLeftSideFocused,
- BothSidesVisibleRightSideFocused,
-}
-
-impl Default for VisibilityAndFocus {
- fn default() -> Self {
- VisibilityAndFocus::OnlyLeftSideVisible
- }
-}
-
-impl VisibilityAndFocus {
- pub fn only_left_side_is_focused(&self) -> bool {
- match self {
- VisibilityAndFocus::OnlyLeftSideVisible => true,
- _ => false,
- }
- }
- pub fn left_side_is_focused(&self) -> bool {
- match self {
- VisibilityAndFocus::OnlyLeftSideVisible
- | VisibilityAndFocus::BothSidesVisibleLeftSideFocused => true,
- _ => false,
- }
- }
- pub fn right_side_is_focused(&self) -> bool {
- match self {
- VisibilityAndFocus::OnlyRightSideVisible
- | VisibilityAndFocus::BothSidesVisibleRightSideFocused => true,
- _ => false,
- }
- }
- pub fn hide_left_side(&mut self) {
- *self = VisibilityAndFocus::OnlyRightSideVisible
- }
- pub fn hide_right_side(&mut self) {
- *self = VisibilityAndFocus::OnlyLeftSideVisible
- }
- pub fn focus_right_side(&mut self) {
- *self = VisibilityAndFocus::BothSidesVisibleRightSideFocused
- }
- pub fn toggle_focus(&mut self) {
- match self {
- VisibilityAndFocus::BothSidesVisibleLeftSideFocused => {
- *self = VisibilityAndFocus::BothSidesVisibleRightSideFocused
- },
- VisibilityAndFocus::BothSidesVisibleRightSideFocused => {
- *self = VisibilityAndFocus::BothSidesVisibleLeftSideFocused
- },
- VisibilityAndFocus::OnlyLeftSideVisible => {
- *self = VisibilityAndFocus::BothSidesVisibleRightSideFocused
- },
- VisibilityAndFocus::OnlyRightSideVisible => {
- *self = VisibilityAndFocus::BothSidesVisibleLeftSideFocused
- },
- }
- }
- pub fn show_both_sides(&mut self) {
- match self {
- VisibilityAndFocus::OnlyLeftSideVisible => {
- *self = VisibilityAndFocus::BothSidesVisibleLeftSideFocused
- },
- VisibilityAndFocus::OnlyRightSideVisible => {
- *self = VisibilityAndFocus::BothSidesVisibleRightSideFocused
- },
- VisibilityAndFocus::BothSidesVisibleLeftSideFocused
- | VisibilityAndFocus::BothSidesVisibleRightSideFocused => {
- // no-op
- },
- }
- }
-}
-
-impl App {
- pub fn react_to_zellij_state_update(&mut self, pane_manifest: PaneManifest) {
- let is_first_update = self.right_side_panes.is_empty() && self.left_side_panes.is_empty();
- let panes_on_the_left_before = self.left_side_panes.len();
- let panes_on_the_right_before = self.right_side_panes.len();
- self.update_tab_info(&pane_manifest);
- self.update_panes(pane_manifest);
- if is_first_update && !self.right_side_panes.is_empty() {
- // in this case, the plugin was started with an existing group
- // most likely, the user wants to perform operations just on this group, so we
- // only show the group, giving the option to add more panes
- self.visibility_and_focus.hide_left_side();
- }
- let pane_count_changed = (panes_on_the_left_before != self.left_side_panes.len())
- || (panes_on_the_right_before != self.right_side_panes.len());
- if !is_first_update && pane_count_changed {
- let has_panes_on_the_right = !self.right_side_panes.is_empty();
- let has_panes_on_the_left = !self.left_side_panes.is_empty();
- if has_panes_on_the_right && has_panes_on_the_left {
- self.visibility_and_focus.show_both_sides();
- } else if has_panes_on_the_right {
- self.visibility_and_focus.hide_left_side();
- } else if has_panes_on_the_left {
- self.visibility_and_focus.hide_right_side();
- }
- }
- }
- pub fn update_panes(&mut self, pane_manifest: PaneManifest) {
- let mut all_panes = BTreeMap::new();
- for (_tab_index, pane_infos) in pane_manifest.panes {
- for pane_info in pane_infos {
- if pane_info.is_selectable {
- if pane_info.is_plugin {
- all_panes.insert(PaneId::Plugin(pane_info.id), pane_info);
- } else {
- all_panes.insert(PaneId::Terminal(pane_info.id), pane_info);
- }
- }
- }
- }
- self.left_side_panes
- .retain(|p| all_panes.contains_key(&p.id));
- self.right_side_panes
- .retain(|p| all_panes.contains_key(&p.id));
- let mut new_selected_panes: BTreeMap<usize, PaneItem> = BTreeMap::new(); // usize -> index_in_pane_group
- for (pane_id, pane) in all_panes.into_iter() {
- let is_known = self
- .left_side_panes
- .iter()
- .find(|p| p.id == pane_id)
- .is_some()
- || self
- .right_side_panes
- .iter()
- .find(|p| p.id == pane_id)
- .is_some();
- let index_in_pane_group = self
- .own_client_id
- .and_then(|own_client_id| pane.index_in_pane_group.get(&own_client_id));
- let is_grouped_for_own_client_id = index_in_pane_group.is_some();
- if !is_known {
- if is_grouped_for_own_client_id {
- if let Some(index_in_pane_group) = index_in_pane_group {
- // we do this rather than adding them directly to right_side_panes so that
- // we can make sure they're in the same order as the group is so that
- // things like stacking order will do the right thing
- new_selected_panes.insert(
- *index_in_pane_group,
- PaneItem {
- text: pane.title,
- id: pane_id,
- color_indices: vec![],
- },
- );
- }
- } else {
- self.left_side_panes.push(PaneItem {
- text: pane.title,
- id: pane_id,
- color_indices: vec![],
- });
- }
- } else {
- if is_grouped_for_own_client_id {
- if let Some(position) =
- self.left_side_panes.iter().position(|p| p.id == pane_id)
- {
- // pane was added to a pane group outside the plugin (eg. with mouse selection)
- let mut pane = self.left_side_panes.remove(position);
- pane.clear();
- self.right_side_panes.push(pane);
- }
- } else {
- if let Some(position) =
- self.right_side_panes.iter().position(|p| p.id == pane_id)
- {
- // pane was removed from a pane group outside the plugin (eg. with mouse selection)
- let mut pane = self.right_side_panes.remove(position);
- pane.clear();
- self.left_side_panes.push(pane);
- }
- }
- }
- }
- for (_index_in_pane_group, pane_item) in new_selected_panes.into_iter() {
- self.right_side_panes.push(pane_item);
- }
- }
- pub fn update_tab_info(&mut self, pane_manifest: &PaneManifest) {
- for (tab_index, pane_infos) in &pane_manifest.panes {
- for pane_info in pane_infos {
- if pane_info.is_plugin && Some(pane_info.id) == self.own_plugin_id {
- self.own_tab_index = Some(*tab_index);
- }
- }
- }
- self.total_tabs_in_session = Some(pane_manifest.panes.keys().count());
- }
- pub fn update_search_results(&mut self) {
- let mut matches = vec![];
- let matcher = SkimMatcherV2::default().use_cache(true);
- for pane_item in &self.left_side_panes {
- if let Some((score, indices)) =
- matcher.fuzzy_indices(&pane_item.text, &self.search_string)
- {
- let mut pane_item = pane_item.clone();
- pane_item.color_indices = indices;
- matches.push((score, pane_item));
- }
- }
- matches.sort_by(|(a_score, _a), (b_score, _b)| b_score.cmp(&a_score));
- if self.search_string.is_empty() {
- self.search_results = None;
- } else {
- self.search_results = Some(
- matches
- .into_iter()
- .map(|(_s, pane_item)| pane_item)
- .collect(),
- );
- }
- }
- pub fn group_panes_in_zellij(&mut self, pane_ids: Vec<PaneId>) {
- group_and_ungroup_panes(pane_ids, vec![]);
- }
- pub fn ungroup_panes_in_zellij(&mut self, pane_ids: Vec<PaneId>) {
- group_and_ungroup_panes(vec![], pane_ids);
- }
- pub fn update_highlighted_panes(&self) {
- let mut pane_ids_to_highlight = vec![];
- let mut pane_ids_to_unhighlight = vec![];
- if let Some(marked_index) = &self.marked_index {
- if self.visibility_and_focus.left_side_is_focused() {
- if let Some(main_index_pane_id) = self
- .search_results
- .as_ref()
- .and_then(|s| s.get(marked_index.main_index))
- .or_else(|| self.left_side_panes.get(marked_index.main_index))
- .map(|p| p.id)
- {
- pane_ids_to_highlight.push(main_index_pane_id);
- }
- for index in &marked_index.additional_indices {
- if let Some(pane_id) = self
- .search_results
- .as_ref()
- .and_then(|s| s.get(*index))
- .or_else(|| self.left_side_panes.get(*index))
- .map(|p| p.id)
- {
- pane_ids_to_highlight.push(pane_id);
- }
- }
- } else {
- if let Some(main_index_pane_id) = self
- .right_side_panes
- .get(marked_index.main_index)
- .map(|p| p.id)
- {
- pane_ids_to_highlight.push(main_index_pane_id);
- }
- for index in &marked_index.additional_indices {
- if let Some(pane_id) = self.right_side_panes.get(*index).map(|p| p.id) {
- pane_ids_to_highlight.push(pane_id);
- }
- }
- }
- }
- for pane in &self.left_side_panes {
- if !pane_ids_to_highlight.contains(&pane.id) {
- pane_ids_to_unhighlight.push(pane.id);
- }
- }
- for pane in &self.right_side_panes {
- if !pane_ids_to_highlight.contains(&pane.id) {
- pane_ids_to_unhighlight.push(pane.id);
- }
- }
- highlight_and_unhighlight_panes(pane_ids_to_highlight, pane_ids_to_unhighlight);
- }
- pub fn unhighlight_all_panes(&mut self) {
- let mut pane_ids_to_unhighlight = HashSet::new();
- for pane_item in &self.left_side_panes {
- pane_ids_to_unhighlight.insert(pane_item.id);
- }
- for pane_item in &self.right_side_panes {
- pane_ids_to_unhighlight.insert(pane_item.id);
- }
- highlight_and_unhighlight_panes(vec![], pane_ids_to_unhighlight.into_iter().collect());
- }
- pub fn ungroup_all_panes(&mut self) {
- let mut unselected_panes = vec![];
- for pane_item in self.right_side_panes.iter_mut() {
- pane_item.clear();
- unselected_panes.push(pane_item.id);
- }
- self.left_side_panes.append(&mut self.right_side_panes);
- self.ungroup_panes_in_zellij(unselected_panes);
- self.visibility_and_focus.hide_right_side();
- self.marked_index = None;
- }
- pub fn ungroup_all_panes_and_close_self(&mut self) {
- let mut pane_ids_to_ungroup = HashSet::new();
- for pane_item in &self.left_side_panes {
- pane_ids_to_ungroup.insert(pane_item.id);
- }
- for pane_item in &self.right_side_panes {
- pane_ids_to_ungroup.insert(pane_item.id);
- }
- group_and_ungroup_panes(vec![], pane_ids_to_ungroup.into_iter().collect());
- close_self();
- }
- pub fn group_panes(&mut self, mut marked_index: MarkedIndex, keep_left_side_focused: bool) {
- let mut all_selected_indices: BTreeSet<usize> =
- marked_index.additional_indices.drain().collect();
- all_selected_indices.insert(marked_index.main_index);
-
- // reverse so that the indices will remain consistent while
- // removing
- let mut selected_panes = vec![];
- for index in all_selected_indices.iter().rev() {
- let index = self
- .search_results
- .as_mut()
- .and_then(|search_results| {
- if search_results.len() > *index {
- Some(search_results.remove(*index))
- } else {
- None
- }
- })
- .and_then(|selected_search_result| {
- self.left_side_panes
- .iter()
- .position(|p| p.id == selected_search_result.id)
- })
- .unwrap_or(*index);
- if self.left_side_panes.len() > index {
- let selected_pane = self.left_side_panes.remove(index);
- selected_panes.push(selected_pane);
- }
- }
- let pane_ids_to_make_selected: Vec<PaneId> = selected_panes.iter().map(|p| p.id).collect();
- self.right_side_panes
- .append(&mut selected_panes.into_iter().rev().collect());
-
- let displayed_list_len = match self.search_results.as_ref() {
- Some(search_results) => search_results.len(),
- None => self.left_side_panes.len(),
- };
-
- if displayed_list_len == 0 {
- self.handle_left_side_emptied();
- } else if keep_left_side_focused {
- if marked_index.main_index > displayed_list_len.saturating_sub(1) {
- self.marked_index = Some(MarkedIndex::new(displayed_list_len.saturating_sub(1)));
- } else {
- self.marked_index = Some(marked_index);
- }
- self.visibility_and_focus.show_both_sides();
- } else {
- self.visibility_and_focus.focus_right_side();
- }
-
- self.group_panes_in_zellij(pane_ids_to_make_selected);
- self.update_highlighted_panes();
- }
- pub fn ungroup_panes(&mut self, mut marked_index: MarkedIndex) {
- let mut all_selected_indices: BTreeSet<usize> =
- marked_index.additional_indices.drain().collect();
- all_selected_indices.insert(marked_index.main_index);
-
- // reverse so that the indices will remain consistent while
- // removing
- let mut selected_panes = vec![];
- for index in all_selected_indices.iter().rev() {
- if self.right_side_panes.len() > *index {
- let mut selected_pane = self.right_side_panes.remove(*index);
- selected_pane.clear();
- selected_panes.push(selected_pane);
- }
- }
- self.ungroup_panes_in_zellij(selected_panes.iter().map(|p| p.id).collect());
- self.left_side_panes
- .append(&mut selected_panes.into_iter().rev().collect());
-
- if self.right_side_panes.is_empty() {
- self.marked_index = None;
- self.visibility_and_focus.hide_right_side();
- } else if marked_index.main_index > self.right_side_panes.len().saturating_sub(1) {
- self.marked_index = Some(MarkedIndex::new(
- self.right_side_panes.len().saturating_sub(1),
- ));
- self.visibility_and_focus.show_both_sides();
- } else {
- self.marked_index = Some(marked_index);
- self.visibility_and_focus.show_both_sides();
- }
- self.update_highlighted_panes();
- }
- pub fn group_search_results(&mut self, search_results: Vec<PaneItem>) {
- let mut pane_ids_to_make_selected = vec![];
- for search_result in search_results {
- let pane_id = search_result.id;
- pane_ids_to_make_selected.push(pane_id);
- self.left_side_panes.retain(|p| p.id != pane_id);
- self.right_side_panes.push(search_result);
- }
- self.group_panes_in_zellij(pane_ids_to_make_selected);
- }
- pub fn group_all_panes(&mut self) {
- let pane_ids_to_make_selected: Vec<PaneId> =
- self.left_side_panes.iter().map(|p| p.id).collect();
- self.right_side_panes.append(&mut self.left_side_panes);
- self.group_panes_in_zellij(pane_ids_to_make_selected);
- }
- pub fn handle_left_side_emptied(&mut self) {
- self.visibility_and_focus.hide_left_side();
- self.previous_search_string = self.search_string.drain(..).collect();
- self.marked_index = None;
- self.search_results = None;
- self.update_highlighted_panes();
- }
- pub fn move_marked_index_down(&mut self) {
- match self.marked_index.as_mut() {
- Some(marked_index) => {
- let is_searching = self.search_results.is_some();
- let search_result_count =
- self.search_results.as_ref().map(|s| s.len()).unwrap_or(0);
- if self.visibility_and_focus.left_side_is_focused()
- && is_searching
- && marked_index.main_index == search_result_count.saturating_sub(1)
- {
- marked_index.main_index = 0;
- } else if self.visibility_and_focus.left_side_is_focused()
- && !is_searching
- && marked_index.main_index == self.left_side_panes.len().saturating_sub(1)
- {
- marked_index.main_index = 0;
- } else if self.visibility_and_focus.right_side_is_focused()
- && marked_index.main_index == self.right_side_panes.len().saturating_sub(1)
- {
- marked_index.main_index = 0;
- } else {
- marked_index.main_index += 1
- }
- },
- None => {
- if self.visibility_and_focus.left_side_is_focused() {
- let is_searching = self.search_results.is_some();
- let has_search_results = self
- .search_results
- .as_ref()
- .map(|s| !s.is_empty())
- .unwrap_or(false);
- if is_searching && has_search_results {
- self.marked_index = Some(MarkedIndex::new(0));
- } else if !is_searching && !self.left_side_panes.is_empty() {
- self.marked_index = Some(MarkedIndex::new(0));
- }
- } else if self.visibility_and_focus.right_side_is_focused()
- && !self.right_side_panes.is_empty()
- {
- self.marked_index = Some(MarkedIndex::new(0));
- }
- },
- }
- self.update_highlighted_panes();
- }
- pub fn move_marked_index_up(&mut self) {
- match self.marked_index.as_mut() {
- Some(marked_index) => {
- if self.visibility_and_focus.left_side_is_focused() && marked_index.main_index == 0
- {
- if let Some(search_result_count) = self.search_results.as_ref().map(|s| s.len())
- {
- marked_index.main_index = search_result_count.saturating_sub(1);
- } else {
- marked_index.main_index = self.left_side_panes.len().saturating_sub(1);
- }
- } else if self.visibility_and_focus.right_side_is_focused()
- && marked_index.main_index == 0
- {
- marked_index.main_index = self.right_side_panes.len().saturating_sub(1);
- } else {
- marked_index.main_index = marked_index.main_index.saturating_sub(1);
- }
- },
- None => {
- if self.visibility_and_focus.left_side_is_focused() {
- let is_searching = self.search_results.is_some();
- let has_search_results = self
- .search_results
- .as_ref()
- .map(|s| !s.is_empty())
- .unwrap_or(false);
- if is_searching && has_search_results {
- let search_results_count =
- self.search_results.as_ref().map(|s| s.len()).unwrap_or(0);
- self.marked_index =
- Some(MarkedIndex::new(search_results_count.saturating_sub(1)));
- } else if !is_searching && !self.left_side_panes.is_empty() {
- self.marked_index = Some(MarkedIndex::new(
- self.left_side_panes.len().saturating_sub(1),
- ));
- }
- } else if self.visibility_and_focus.right_side_is_focused()
- && !self.right_side_panes.is_empty()
- {
- self.marked_index = Some(MarkedIndex::new(
- self.right_side_panes.len().saturating_sub(1),
- ));
- }
- },
- }
- self.update_highlighted_panes();
- }
- pub fn mark_entry(&mut self) {
- if let Some(marked_index) = self.marked_index.as_mut() {
- marked_index.toggle_additional_mark();
- self.update_highlighted_panes();
- }
- }
- pub fn break_grouped_panes_to_new_tab(&mut self) {
- let pane_ids_to_break_to_new_tab: Vec<PaneId> =
- self.right_side_panes.drain(..).map(|p| p.id).collect();
- let title_for_new_tab = if !self.previous_search_string.is_empty() {
- Some(self.previous_search_string.clone())
- } else {
- None
- };
- break_panes_to_new_tab(&pane_ids_to_break_to_new_tab, title_for_new_tab, true);
- self.ungroup_panes_in_zellij(pane_ids_to_break_to_new_tab);
- close_self();
- }
- pub fn stack_grouped_panes(&mut self) {
- let pane_ids_to_stack: Vec<PaneId> =
- self.right_side_panes.drain(..).map(|p| p.id).collect();
- stack_panes(pane_ids_to_stack.clone());
- self.ungroup_panes_in_zellij(pane_ids_to_stack);
- close_self();
- }
- pub fn float_grouped_panes(&mut self) {
- let pane_ids_to_float: Vec<PaneId> =
- self.right_side_panes.drain(..).map(|p| p.id).collect();
- float_multiple_panes(pane_ids_to_float.clone());
- self.ungroup_panes_in_zellij(pane_ids_to_float);
- close_self();
- }
- pub fn embed_grouped_panes(&mut self) {
- let pane_ids_to_embed: Vec<PaneId> =
- self.right_side_panes.drain(..).map(|p| p.id).collect();
- embed_multiple_panes(pane_ids_to_embed.clone());
- self.ungroup_panes_in_zellij(pane_ids_to_embed);
- close_self();
- }
- pub fn break_grouped_panes_right(&mut self) {
- if let Some(own_tab_index) = self.own_tab_index {
- if Some(own_tab_index + 1) < self.total_tabs_in_session {
- let pane_ids_to_break_right: Vec<PaneId> =
- self.right_side_panes.drain(..).map(|p| p.id).collect();
- break_panes_to_tab_with_index(&pane_ids_to_break_right, own_tab_index + 1, true);
- } else {
- let pane_ids_to_break_to_new_tab: Vec<PaneId> =
- self.right_side_panes.drain(..).map(|p| p.id).collect();
- let title_for_new_tab = if !self.previous_search_string.is_empty() {
- Some(self.previous_search_string.clone())
- } else {
- None
- };
- break_panes_to_new_tab(&pane_ids_to_break_to_new_tab, title_for_new_tab, true);
- }
- close_self();
- }
- }
- pub fn break_grouped_panes_left(&mut self) {
- if let Some(own_tab_index) = self.own_tab_index {
- if own_tab_index > 0 {
- let pane_ids_to_break_left: Vec<PaneId> =
- self.right_side_panes.drain(..).map(|p| p.id).collect();
- break_panes_to_tab_with_index(
- &pane_ids_to_break_left,
- own_tab_index.saturating_sub(1),
- true,
- );
- } else {
- let pane_ids_to_break_to_new_tab: Vec<PaneId> =
- self.right_side_panes.drain(..).map(|p| p.id).collect();
- let title_for_new_tab = if !self.previous_search_string.is_empty() {
- Some(self.previous_search_string.clone())
- } else {
- None
- };
- break_panes_to_new_tab(&pane_ids_to_break_to_new_tab, title_for_new_tab, true);
- }
- close_self();
- }
- }
- pub fn close_grouped_panes(&mut self) {
- let pane_ids_to_close: Vec<PaneId> =
- self.right_side_panes.drain(..).map(|p| p.id).collect();
- close_multiple_panes(pane_ids_to_close);
- close_self();
- }
-}
diff --git a/default-plugins/multiple-select/src/ui.rs b/default-plugins/multiple-select/src/ui.rs
deleted file mode 100644
index 0c74b0fe..00000000
--- a/default-plugins/multiple-select/src/ui.rs
+++ /dev/null
@@ -1,873 +0,0 @@
-use crate::{App, VisibilityAndFocus};
-use zellij_tile::prelude::*;
-
-const TOP_LEFT_CORNER_CHARACTER: &'static str = "┌";
-const TOP_RIGHT_CORNER_CHARACTER: &'static str = "┐";
-const BOTTOM_LEFT_CORNER_CHARACTER: &'static str = "└";
-const BOTTOM_RIGHT_CORNER_CHARACTER: &'static str = "┘";
-const BOUNDARY_CHARACTER: &'static str = "│";
-const HORIZONTAL_BOUNDARY_CHARACTER: &'static str = "─";
-
-#[derive(Debug, Clone)]
-pub struct PaneItem {
- pub text: String,
- pub id: PaneId,
- pub color_indices: Vec<usize>,
-}
-
-impl PaneItem {
- pub fn clear(&mut self) {
- self.color_indices.clear();
- }
- pub fn render(&self, max_width_for_item: usize) -> NestedListItem {
- let pane_item_text_len = self.text.chars().count();
- if pane_item_text_len <= max_width_for_item {
- NestedListItem::new(&self.text)
- .color_range(0, ..)
- .color_indices(3, self.color_indices.iter().copied().collect())
- } else {
- let length_of_each_half = max_width_for_item.saturating_sub(3) / 2;
- let first_half: String = self.text.chars().take(length_of_each_half).collect();
- let second_half: String = self
- .text
- .chars()
- .rev()
- .take(length_of_each_half)
- .collect::<Vec<_>>()
- .iter()
- .rev()
- .collect();
- let second_half_start_index = pane_item_text_len.saturating_sub(length_of_each_half);
- let adjusted_indices: Vec<usize> = self
- .color_indices
- .iter()
- .filter_map(|i| {
- if i < &length_of_each_half {
- Some(*i)
- } else if i >= &second_half_start_index {
- Some(i.saturating_sub(second_half_start_index) + length_of_each_half + 3)
- //3 for the bulletin
- } else {
- None
- }
- })
- .collect();
- NestedListItem::new(format!("{}...{}", first_half, second_half))
- .color_range(0, ..)
- .color_indices(3, adjusted_indices)
- }
- }
-}
-
-// rendering code
-impl App {
- pub fn render_close_shortcut(&self, cols: usize) {
- let should_render_close_shortcut =
- self.visibility_and_focus.left_side_is_focused() && self.marked_index.is_none();
- if should_render_close_shortcut {
- let x_coordinates_right_padding =
- if self.visibility_and_focus.only_left_side_is_focused() {
- 5
- } else {
- 1
- };
- let ctrl_c_shortcut_text = "<Ctrl c> - Close";
- let ctrl_c_shortcut = Text::new(ctrl_c_shortcut_text).color_range(3, ..=7);
- print_text_with_coordinates(
- ctrl_c_shortcut,
- cols.saturating_sub(ctrl_c_shortcut_text.chars().count())
- .saturating_sub(x_coordinates_right_padding),
- 0,
- None,
- None,
- );
- }
- }
- pub fn render_tab_shortcut(&self, cols: usize, rows: usize) {
- match self.visibility_and_focus {
- VisibilityAndFocus::BothSidesVisibleRightSideFocused => {
- let side_width = self.calculate_side_width(cols);
- let tab_shortcut = Text::new("<TAB> - select more panes").color_range(3, ..=4);
- print_text_with_coordinates(
- tab_shortcut,
- side_width + 6,
- rows.saturating_sub(2),
- None,
- None,
- );
- },
- VisibilityAndFocus::BothSidesVisibleLeftSideFocused => {
- let side_width = self.calculate_side_width(cols);
- let tab_shortcut_text = "<TAB> - browse selected panes";
- let tab_shortcut = Text::new(tab_shortcut_text).color_range(3, ..=4);
- print_text_with_coordinates(
- tab_shortcut,
- side_width.saturating_sub(tab_shortcut_text.chars().count() + 1),
- rows.saturating_sub(2),
- None,
- None,
- );
- },
- VisibilityAndFocus::OnlyRightSideVisible => {
- let tab_shortcut = Text::new("<TAB> - select more panes").color_range(3, ..=4);
- print_text_with_coordinates(tab_shortcut, 4, rows.saturating_sub(2), None, None);
- },
- VisibilityAndFocus::OnlyLeftSideVisible => {
- // not visible
- },
- };
- }
- pub fn render_left_side(&self, rows: usize, cols: usize, is_focused: bool) {
- let title_y = 0;
- let left_side_base_x = 1;
- let list_y = 2;
- let side_width = self.calculate_side_width(cols);
- let max_left_list_height = rows.saturating_sub(8);
- let (
- extra_pane_count_on_top_left,
- extra_pane_count_on_bottom_left,
- extra_selected_item_count_on_top_left,
- extra_selected_item_count_on_bottom_left,
- left_side_panes,
- ) = self.left_side_panes_list(side_width, max_left_list_height);
- let (filter_prompt_text, filter_prompt) = self.filter_panes_prompt();
- let filter = self.filter(side_width.saturating_sub(filter_prompt_text.chars().count() + 1));
- let (
- _enter_select_panes_text,
- enter_select_panes,
- space_shortcut_text,
- space_shortcut,
- _escape_shortcut_text,
- escape_shortcut,
- ) = self.left_side_controls(side_width);
- print_text_with_coordinates(filter_prompt, left_side_base_x, title_y, None, None);
- if is_focused {
- print_text_with_coordinates(
- filter,
- left_side_base_x + filter_prompt_text.chars().count(),
- title_y,
- None,
- None,
- );
- }
- print_nested_list_with_coordinates(
- left_side_panes.clone(),
- left_side_base_x,
- list_y,
- Some(side_width),
- None,
- );
- if is_focused {
- if let Some(marked_index) = self.marked_index.as_ref().map(|i| i.main_index) {
- print_text_with_coordinates(
- Text::new(">").color_range(3, ..).selected(),
- left_side_base_x + 1,
- (list_y + marked_index).saturating_sub(extra_pane_count_on_top_left),
- None,
- None,
- );
- }
- }
- if extra_pane_count_on_top_left > 0 {
- self.print_extra_pane_count(
- extra_pane_count_on_top_left,
- extra_selected_item_count_on_top_left,
- list_y.saturating_sub(1),
- left_side_base_x,
- side_width,
- );
- }
- if extra_pane_count_on_bottom_left > 0 {
- self.print_extra_pane_count(
- extra_pane_count_on_bottom_left,
- extra_selected_item_count_on_bottom_left,
- list_y + left_side_panes.len(),
- left_side_base_x,
- side_width,
- );
- }
- if is_focused && !left_side_panes.is_empty() {
- let controls_x = 1;
- print_text_with_coordinates(
- enter_select_panes,
- controls_x,
- list_y + left_side_panes.len() + 1,
- None,
- None,
- );
- if self.marked_index.is_some() {
- print_text_with_coordinates(
- space_shortcut.clone(),
- controls_x,
- list_y + left_side_panes.len() + 2,
- None,
- None,
- );
- print_text_with_coordinates(
- escape_shortcut.clone(),
- controls_x + space_shortcut_text.chars().count() + 1,
- list_y + left_side_panes.len() + 2,
- None,
- None,
- );
- }
- }
- }
- pub fn render_right_side(&self, rows: usize, cols: usize, is_focused: bool) {
- let side_width = self.calculate_side_width(cols);
- let right_side_base_x = match self.visibility_and_focus {
- VisibilityAndFocus::OnlyLeftSideVisible | VisibilityAndFocus::OnlyRightSideVisible => 1,
- VisibilityAndFocus::BothSidesVisibleLeftSideFocused
- | VisibilityAndFocus::BothSidesVisibleRightSideFocused => side_width + 4,
- };
- let title_y = 0;
- let list_y: usize = 2;
- let max_right_list_height = rows.saturating_sub(11);
- let selected_prompt = self.selected_panes_title();
- let (
- extra_pane_count_on_top_right,
- extra_pane_count_on_bottom_right,
- extra_selected_item_count_on_top_right,
- extra_selected_item_count_on_bottom_right,
- right_side_panes,
- ) = self.right_side_panes_list(side_width, max_right_list_height);
- let right_side_pane_count = right_side_panes.len();
- let (
- right_side_controls_1,
- right_side_controls_2,
- right_side_controls_3,
- right_side_controls_4,
- ) = self.right_side_controls(side_width);
- if extra_pane_count_on_top_right > 0 {
- self.print_extra_pane_count(
- extra_pane_count_on_top_right,
- extra_selected_item_count_on_top_right,
- list_y.saturating_sub(1),
- right_side_base_x,
- side_width,
- );
- }
- if extra_pane_count_on_bottom_right > 0 {
- self.print_extra_pane_count(
- extra_pane_count_on_bottom_right,
- extra_selected_item_count_on_bottom_right,
- list_y + right_side_panes.len(),
- right_side_base_x,
- side_width,
- );
- }
- print_text_with_coordinates(selected_prompt, right_side_base_x + 3, title_y, None, None);
- print_nested_list_with_coordinates(
- right_side_panes,
- right_side_base_x,
- list_y,
- Some(side_width),
- None,
- );
- if is_focused {
- if let Some(marked_index) = self.marked_index.as_ref().map(|i| i.main_index) {
- print_text_with_coordinates(
- Text::new(">").color_range(3, ..).selected(),
- right_side_base_x + 1,
- (list_y + marked_index).saturating_sub(extra_pane_count_on_top_right),
- None,
- None,
- );
- }
- }
- if is_focused && !self.right_side_panes.is_empty() {
- print_text_with_coordinates(
- right_side_controls_1,
- right_side_base_x + 1,
- list_y + right_side_pane_count + 1,
- None,
- None,
- );
- print_text_with_coordinates(
- right_side_controls_2,
- right_side_base_x + 1,
- list_y + right_side_pane_count + 3,
- None,
- None,
- );
- print_text_with_coordinates(
- right_side_controls_3,
- right_side_base_x + 1,
- list_y + right_side_pane_count + 4,
- None,
- None,
- );
- print_text_with_coordinates(
- right_side_controls_4,
- right_side_base_x + 1,
- list_y + right_side_pane_count + 5,
- None,
- None,
- );
- }
- }
- pub fn render_help_line(&self, rows: usize, cols: usize) {
- let help_line_text = match self.visibility_and_focus {
- VisibilityAndFocus::OnlyLeftSideVisible => {
- let full_help_line = "Help: Select one or more panes to group for bulk operations";
- let short_help_line = "Help: Select panes to group";
- if cols >= full_help_line.chars().count() {
- full_help_line
- } else {
- short_help_line
- }
- },
- VisibilityAndFocus::OnlyRightSideVisible => {
- let full_help_line = "Help: Perform bulk operations on all selected panes";
- let short_help_line = "Help: Perform bulk operations";
- if cols >= full_help_line.chars().count() {
- full_help_line
- } else {
- short_help_line
- }
- },
- _ => {
- let full_help_line =
- "Help: Select panes on the left, then perform operations on the right.";
- let short_help_line = "Help: Group panes for bulk operations";
- if cols >= full_help_line.chars().count() {
- full_help_line
- } else {
- short_help_line
- }
- },
- };
- let help_line = Text::new(help_line_text);
- print_text_with_coordinates(help_line, 0, rows, None, None);
- }
- pub fn render_focus_boundary(&self, rows: usize, cols: usize) {
- let side_width = self.calculate_side_width(cols);
- let x = match self.visibility_and_focus {
- VisibilityAndFocus::OnlyRightSideVisible => 0,
- VisibilityAndFocus::BothSidesVisibleLeftSideFocused
- | VisibilityAndFocus::BothSidesVisibleRightSideFocused
- | VisibilityAndFocus::OnlyLeftSideVisible => side_width + 2,
- };
- let y = 0;
- let height = rows.saturating_sub(2);
- for i in y..=height {
- if i == y && self.visibility_and_focus.left_side_is_focused() {
- print_text_with_coordinates(
- Text::new(TOP_RIGHT_CORNER_CHARACTER),
- x,
- i,
- None,
- None,
- );
- print_text_with_coordinates(
- Text::new(HORIZONTAL_BOUNDARY_CHARACTER),
- x.saturating_sub(1),
- i,
- None,
- None,
- );
- print_text_with_coordinates(
- Text::new(HORIZONTAL_BOUNDARY_CHARACTER),
- x.saturating_sub(2),
- i,
- None,
- None,
- );
- } else if i == y && !self.visibility_and_focus.left_side_is_focused() {
- print_text_with_coordinates(Text::new(TOP_LEFT_CORNER_CHARACTER), x, i, None, None);
- print_text_with_coordinates(
- Text::new(HORIZONTAL_BOUNDARY_CHARACTER),
- x + 1,
- i,
- None,
- None,
- );
- print_text_with_coordinates(
- Text::new(HORIZONTAL_BOUNDARY_CHARACTER),
- x + 2,
- i,
- None,
- None,
- );
- } else if i == height && self.visibility_and_focus.left_side_is_focused() {
- print_text_with_coordinates(
- Text::new(BOTTOM_RIGHT_CORNER_CHARACTER),
- x,
- i,
- None,
- None,
- );
- print_text_with_coordinates(
- Text::new(HORIZONTAL_BOUNDARY_CHARACTER),
- x.saturating_sub(1),
- i,
- None,
- None,
- );
- print_text_with_coordinates(
- Text::new(HORIZONTAL_BOUNDARY_CHARACTER),
- x.saturating_sub(2),
- i,
- None,
- None,
- );
- } else if i == height && !self.visibility_and_focus.left_side_is_focused() {
- print_text_with_coordinates(
- Text::new(BOTTOM_LEFT_CORNER_CHARACTER),
- x,
- i,
- None,
- None,
- );
- print_text_with_coordinates(
- Text::new(HORIZONTAL_BOUNDARY_CHARACTER),
- x + 1,
- i,
- None,
- None,
- );
- print_text_with_coordinates(
- Text::new(HORIZONTAL_BOUNDARY_CHARACTER),
- x + 2,
- i,
- None,
- None,
- );
- } else {
- print_text_with_coordinates(Text::new(BOUNDARY_CHARACTER), x, i, None, None);
- }
- }
- }
- pub fn calculate_side_width(&self, cols: usize) -> usize {
- match self.visibility_and_focus {
- VisibilityAndFocus::OnlyLeftSideVisible | VisibilityAndFocus::OnlyRightSideVisible => {
- cols.saturating_sub(4)
- },
- VisibilityAndFocus::BothSidesVisibleLeftSideFocused
- | VisibilityAndFocus::BothSidesVisibleRightSideFocused => (cols / 2).saturating_sub(3),
- }
- }
-}
-
-// ui components
-impl App {
- fn filter_panes_prompt(&self) -> (&'static str, Text) {
- let search_prompt_text = if self.search_string.is_empty() {
- "ALL PANES "
- } else {
- "FILTER: "
- };
- let search_prompt = if self.visibility_and_focus.left_side_is_focused() {
- Text::new(&search_prompt_text).color_range(2, ..)
- } else {
- Text::new(&search_prompt_text)
- };
- (search_prompt_text, search_prompt)
- }
- fn filter(&self, max_width: usize) -> Text {
- let search_string_text = if self.marked_index.is_none() && self.search_string.is_empty() {
- let full = "[Type filter term...]";
- let short = "[...]";
- if max_width >= full.chars().count() {
- full.to_owned()
- } else {
- short.to_owned()
- }
- } else if self.marked_index.is_none() && !self.search_string.is_empty() {
- if max_width >= self.search_string.chars().count() + 1 {
- format!("{}_", self.search_string)
- } else {
- let truncated: String = self
- .search_string
- .chars()
- .rev()
- .take(max_width.saturating_sub(4))
- .collect::<Vec<_>>()
- .iter()
- .rev()
- .collect();
- format!("...{}_", truncated)
- }
- } else if self.marked_index.is_some() && !self.search_string.is_empty() {
- if max_width >= self.search_string.chars().count() {
- format!("{}", self.search_string)
- } else {
- let truncated: String = self
- .search_string
- .chars()
- .rev()
- .take(max_width.saturating_sub(4))
- .collect::<Vec<_>>()
- .iter()
- .rev()
- .collect();
- format!("...{}", truncated)
- }
- } else {
- format!("")
- };
- Text::new(&search_string_text).color_range(3, ..)
- }
- fn left_side_panes_list(
- &self,
- max_width: usize,
- max_list_height: usize,
- ) -> (usize, usize, usize, usize, Vec<NestedListItem>) {
- // returns: extra_pane_count_on_top, extra_pane_count_on_bottom,
- // extra_selected_item_count_on_top, extra_selected_item_count_on_bottom, list
- let mut left_side_panes = vec![];
- let pane_items_on_the_left = self
- .search_results
- .as_ref()
- .unwrap_or_else(|| &self.left_side_panes);
- let max_width_for_item = max_width.saturating_sub(3); // 3 for the list bulletin
- let item_count = pane_items_on_the_left.iter().count();
- let first_item_index = if self.visibility_and_focus.left_side_is_focused() {
- self.marked_index
- .as_ref()
- .map(|s| s.main_index.saturating_sub(max_list_height / 2))
- .unwrap_or(0)
- } else {
- 0
- };
- let last_item_index = std::cmp::min(
- (max_list_height + first_item_index).saturating_sub(1),
- item_count.saturating_sub(1),
- );
- for (i, pane_item) in pane_items_on_the_left
- .iter()
- .enumerate()
- .skip(first_item_index)
- {
- if i > last_item_index {
- break;
- }
- let mut item = pane_item.render(max_width_for_item);
- if Some(i) == self.marked_index.as_ref().map(|s| s.main_index)
- && self.visibility_and_focus.left_side_is_focused()
- {
- item = item.selected();
- if self
- .marked_index
- .as_ref()
- .map(|s| s.additional_indices.contains(&i))
- .unwrap_or(false)
- {
- item = item.selected().color_range(1, ..);
- }
- } else if self
- .marked_index
- .as_ref()
- .map(|s| s.additional_indices.contains(&i))
- .unwrap_or(false)
- && self.visibility_and_focus.left_side_is_focused()
- {
- item = item.selected();
- }
- left_side_panes.push(item);
- }
- let extra_panes_on_top = first_item_index;
- let extra_panes_on_bottom = item_count.saturating_sub(last_item_index + 1);
- let extra_selected_item_count_on_top = if self.visibility_and_focus.left_side_is_focused() {
- self.marked_index
- .as_ref()
- .map(|s| {
- s.additional_indices
- .iter()
- .filter(|a| a < &&first_item_index)
- .count()
- })
- .unwrap_or(0)
- } else {
- 0
- };
- let extra_selected_item_count_on_bottom =
- if self.visibility_and_focus.left_side_is_focused() {
- self.marked_index
- .as_ref()
- .map(|s| {
- s.additional_indices
- .iter()
- .filter(|a| a > &&last_item_index)
- .count()
- })
- .unwrap_or(0)
- } else {
- 0
- };
- (
- extra_panes_on_top,
- extra_panes_on_bottom,
- extra_selected_item_count_on_top,
- extra_selected_item_count_on_bottom,
- left_side_panes,
- )
- }
- fn left_side_controls(
- &self,
- max_width: usize,
- ) -> (&'static str, Text, &'static str, Text, &'static str, Text) {
- // returns three components and their text
- let (enter_select_panes_text, enter_select_panes) = if self.marked_index.is_some() {
- let enter_select_panes_text_full = "<ENTER> - select, <↓↑→> - navigate";
- let enter_select_panes_text_short = "<ENTER> / <↓↑→>...";
- if max_width >= enter_select_panes_text_full.chars().count() {
- let enter_select_panes_full = Text::new(enter_select_panes_text_full)
- .color_range(3, ..=6)
- .color_range(3, 18..=22);
- (enter_select_panes_text_full, enter_select_panes_full)
- } else {
- let enter_select_panes_short = Text::new(enter_select_panes_text_short)
- .color_range(3, ..=6)
- .color_range(3, 10..=14);
- (enter_select_panes_text_short, enter_select_panes_short)
- }
- } else {
- let enter_select_panes_text_full = "<ENTER> - select all, <↓↑> - navigate";
- let enter_select_panes_text_short = "<ENTER> / <↓↑>...";
- if max_width >= enter_select_panes_text_full.chars().count() {
- let enter_select_panes_full = Text::new(enter_select_panes_text_full)
- .color_range(3, ..=6)
- .color_range(3, 21..=25);
- (enter_select_panes_text_full, enter_select_panes_full)
- } else {
- let enter_select_panes_short = Text::new(enter_select_panes_text_short)
- .color_range(3, ..=6)
- .color_range(3, 10..=13);
- (enter_select_panes_text_short, enter_select_panes_short)
- }
- };
- let space_shortcut_text_full = "<SPACE> - mark many,";
- let space_shortcut_text_short = "<SPACE> /";
- if self.marked_index.is_some() {
- let escape_shortcut_text_full = "<Ctrl c> - remove marks";
- let escape_shortcut_text_short = "<Ctrl c>...";
- let (escape_shortcut, space_shortcut, escape_shortcut_text, space_shortcut_text) =
- if max_width
- >= space_shortcut_text_full.chars().count()
- + escape_shortcut_text_full.chars().count()
- {
- (
- Text::new(escape_shortcut_text_full).color_range(3, ..=7),
- Text::new(space_shortcut_text_full).color_range(3, ..=6),
- escape_shortcut_text_full,
- space_shortcut_text_full,
- )
- } else {
- (
- Text::new(escape_shortcut_text_short).color_range(3, ..=7),
- Text::new(space_shortcut_text_short).color_range(3, ..=6),
- escape_shortcut_text_short,
- space_shortcut_text_short,
- )
- };
- (
- enter_select_panes_text,
- enter_select_panes,
- space_shortcut_text,
- space_shortcut,
- escape_shortcut_text,
- escape_shortcut,
- )
- } else {
- let escape_shortcut_text = if self.right_side_panes.is_empty() {
- "<Ctrl c> - Close"
- } else {
- ""
- };
- let escape_shortcut = Text::new(escape_shortcut_text).color_range(3, ..=7);
- let space_shortcut = Text::new(space_shortcut_text_full).color_range(3, ..=6);
- (
- enter_select_panes_text,
- enter_select_panes,
- space_shortcut_text_full,
- space_shortcut,
- escape_shortcut_text,
- escape_shortcut,
- )
- }
- }
- fn selected_panes_title(&self) -> Text {
- let selected_prompt_text = "SELECTED PANES: ";
- let selected_prompt = if self.visibility_and_focus.left_side_is_focused() {
- Text::new(selected_prompt_text)
- } else {
- Text::new(selected_prompt_text).color_range(2, ..)
- };
- selected_prompt
- }
- fn right_side_panes_list(
- &self,
- max_width: usize,
- max_list_height: usize,
- ) -> (usize, usize, usize, usize, Vec<NestedListItem>) {
- // returns: extra_pane_count_on_top, extra_pane_count_on_bottom,
- // extra_selected_item_count_on_top, extra_selected_item_count_on_bottom, list
- let mut right_side_panes = vec![];
- let item_count = self.right_side_panes.iter().count();
- let first_item_index = if self.visibility_and_focus.left_side_is_focused() {
- 0
- } else {
- self.marked_index
- .as_ref()
- .map(|s| s.main_index.saturating_sub(max_list_height / 2))
- .unwrap_or(0)
- };
- let last_item_index = std::cmp::min(
- (max_list_height + first_item_index).saturating_sub(1),
- item_count.saturating_sub(1),
- );
-
- let max_width_for_item = max_width.saturating_sub(3); // 3 for the list bulletin
- for (i, pane_item) in self
- .right_side_panes
- .iter()
- .enumerate()
- .skip(first_item_index)
- {
- if i > last_item_index {
- break;
- }
- let mut item = pane_item.render(max_width_for_item);
- if &Some(i) == &self.marked_index.as_ref().map(|s| s.main_index)
- && self.visibility_and_focus.right_side_is_focused()
- {
- item = item.selected();
- if self
- .marked_index
- .as_ref()
- .map(|s| s.additional_indices.contains(&i))
- .unwrap_or(false)
- {
- item = item.selected().color_range(1, ..);
- }
- } else if self
- .marked_index
- .as_ref()
- .map(|s| s.additional_indices.contains(&i))
- .unwrap_or(false)
- && self.visibility_and_focus.right_side_is_focused()
- {
- item = item.selected();
- }
- right_side_panes.push(item);
- }
-
- let extra_panes_on_top = first_item_index;
- let extra_panes_on_bottom = self
- .right_side_panes
- .iter()
- .len()
- .saturating_sub(last_item_index + 1);
- let extra_selected_item_count_on_top = if self.visibility_and_focus.left_side_is_focused() {
- 0
- } else {
- self.marked_index
- .as_ref()
- .map(|s| {
- s.additional_indices
- .iter()
- .filter(|a| a < &&first_item_index)
- .count()
- })
- .unwrap_or(0)
- };
- let extra_selected_item_count_on_bottom =
- if self.visibility_and_focus.left_side_is_focused() {
- 0
- } else {
- self.marked_index
- .as_ref()
- .map(|s| {
- s.additional_indices
- .iter()
- .filter(|a| a > &&last_item_index)
- .count()
- })
- .unwrap_or(0)
- };
- (
- extra_panes_on_top,
- extra_panes_on_bottom,
- extra_selected_item_count_on_top,
- extra_selected_item_count_on_bottom,
- right_side_panes,
- )
- }
- fn right_side_controls(&self, cols: usize) -> (Text, Text, Text, Text) {
- let right_side_controls_text_1_full = "<←↓↑> - navigate, <Ctrl c> - clear";
- let right_side_controls_text_1_short = "<←↓↑>/<Ctrl c>...";
- let right_side_controls_1 = if cols >= right_side_controls_text_1_full.chars().count() {
- Text::new(right_side_controls_text_1_full)
- .color_range(3, ..=4)
- .color_range(3, 18..=25)
- } else {
- Text::new(right_side_controls_text_1_short)
- .color_range(3, ..=4)
- .color_range(3, 6..=13)
- };
- let right_side_controls_text_2_full = "<b> - break out, <s> - stack, <c> - close";
- let right_side_controls_text_2_short = "<b>/<s>/<c>...";
- let right_side_controls_2 = if cols >= right_side_controls_text_2_full.chars().count() {
- Text::new(right_side_controls_text_2_full)
- .color_range(3, ..=2)
- .color_range(3, 17..=19)
- .color_range(3, 30..=32)
- } else {
- Text::new(right_side_controls_text_2_short)
- .color_range(3, ..=2)
- .color_range(3, 4..=6)
- .color_range(3, 8..=10)
- };
- let right_side_controls_text_3_full = "<r> - break right, <l> - break left";
- let right_side_controls_text_3_short = "<r>/<l>...";
- let right_side_controls_3 = if cols >= right_side_controls_text_3_full.chars().count() {
- Text::new(right_side_controls_text_3_full)
- .color_range(3, ..=2)
- .color_range(3, 19..=21)
- } else {
- Text::new(right_side_controls_text_3_short)
- .color_range(3, ..=2)
- .color_range(3, 4..=6)
- };
- let right_side_controls_text_4_full = "<e> - embed, <f> - float";
- let right_side_controls_text_4_short = "<e>/<f>...";
- let right_side_controls_4 = if cols >= right_side_controls_text_4_full.chars().count() {
- Text::new(right_side_controls_text_4_full)
- .color_range(3, ..=2)
- .color_range(3, 13..=15)
- } else {
- Text::new(right_side_controls_text_4_short)
- .color_range(3, ..=2)
- .color_range(3, 4..=6)
- };
- (
- right_side_controls_1,
- right_side_controls_2,
- right_side_controls_3,
- right_side_controls_4,
- )
- }
- fn print_extra_pane_count(
- &self,
- count: usize,
- selected_count: usize,
- y: usize,
- list_x: usize,
- list_width: usize,
- ) {
- let extra_count_text = if selected_count > 0 {
- format!("[+{} ({} selected)]", count, selected_count)
- } else {
- format!("[+{}]", count)
- };
- let extra_count = Text::new(&extra_count_text).color_range(1, ..);
- print_text_with_coordinates(
- extra_count,
- (list_x + list_width).saturating_sub(extra_count_text.chars().count()),
- y,
- None,
- None,
- );
- }
-}