diff options
| author | Aram Drevekenin <aram@poor.dev> | 2026-02-24 13:10:40 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-24 13:10:40 +0100 |
| commit | 8cd6286c3ffc10d14fdafd5005fa89b0f7fdcd44 (patch) | |
| tree | c6341f5e3e251ae17db0c3427ddaf1e54d4fa91c /default-plugins/sequence/src/ui/components.rs | |
| parent | Allow closing replaced panes when opening panes in place (#4760) (diff) | |
| download | sl0ppij-8cd6286c3ffc10d14fdafd5005fa89b0f7fdcd44.tar.gz sl0ppij-8cd6286c3ffc10d14fdafd5005fa89b0f7fdcd44.tar.xz sl0ppij-8cd6286c3ffc10d14fdafd5005fa89b0f7fdcd44.zip | |
remove sequence plugin (#4761)
Diffstat (limited to 'default-plugins/sequence/src/ui/components.rs')
| -rw-r--r-- | default-plugins/sequence/src/ui/components.rs | 567 |
1 files changed, 0 insertions, 567 deletions
diff --git a/default-plugins/sequence/src/ui/components.rs b/default-plugins/sequence/src/ui/components.rs deleted file mode 100644 index bc319567..00000000 --- a/default-plugins/sequence/src/ui/components.rs +++ /dev/null @@ -1,567 +0,0 @@ -use crate::path_formatting::format_cwd; -use crate::state::{CommandStatus, State}; -use crate::ui::truncation::{calculate_available_cmd_width, truncate_middle}; -use std::path::PathBuf; -use zellij_tile::prelude::*; - -const SPINNER_FRAMES: &[&str] = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧"]; - -const INTRO_PART1: &str = "Paste a series of commands to run in sequence"; -const INTRO_PART2: &str = "or <e> to edit in your editor"; -const INTRO_FULL: &str = - "Paste a series of commands to run in sequence or <e> to edit in your editor"; - -const HELP_RUNNING_WITH_SELECTION: &str = "<Ctrl c> - interrupt, <Enter> - run from selected"; -const HELP_RUNNING_NO_SELECTION: &str = "<Ctrl c> - interrupt, <↓↑> - navigate"; -const HELP_STOPPED_WITH_SELECTION: &str = - "<Ctrl w> - close all, <Enter> - run from selected, <e> - edit"; -const HELP_STOPPED_NO_SELECTION: &str = - "<Ctrl w> - close all, <↓↑> - navigate, <Enter> - run from first"; - -const HELP_PENDING: &str = "<e> - edit, <Enter> - run"; - -fn select_help_text(sequence: &State) -> &'static str { - let is_running = sequence - .execution - .all_commands - .iter() - .any(|command| matches!(command.get_status(), CommandStatus::Running(_))); - let all_pending = sequence - .execution - .all_commands - .iter() - .all(|command| matches!(command.get_status(), CommandStatus::Pending)); - let has_selection = sequence.selection.current_selected_command_index.is_some(); - - if all_pending && !sequence.execution.is_running { - HELP_PENDING - } else if is_running { - if has_selection { - HELP_RUNNING_WITH_SELECTION - } else { - HELP_RUNNING_NO_SELECTION - } - } else { - if has_selection { - HELP_STOPPED_WITH_SELECTION - } else { - HELP_STOPPED_NO_SELECTION - } - } -} - -fn style_help_text(text: &str) -> Text { - Text::new(text) - .color_substring(3, "<Ctrl c>") - .color_substring(3, "<Ctrl w>") - .color_substring(3, "<↓↑>") - .color_substring(3, "<Esc>") - .color_substring(3, "<Enter>") - .color_substring(3, "<e>") -} - -pub fn render_help_lines( - sequence: &State, - max_width: Option<usize>, -) -> (Text, usize, Option<(Text, usize)>) { - let help_text = select_help_text(sequence); - - if let Some(width) = max_width { - if let Some((first_line, second_line)) = split_help_line(help_text, width) { - let first_len = first_line.chars().count(); - let first_styled = style_help_text(&first_line).unbold_all(); - - let second_line_width = second_line.chars().count(); - let (second_styled, second_len) = if second_line_width > width { - let (truncated, len) = truncate_help_line(&second_line, width); - (style_help_text(&truncated).unbold_all(), len) - } else { - ( - style_help_text(&second_line).unbold_all(), - second_line_width, - ) - }; - - return (first_styled, first_len, Some((second_styled, second_len))); - } - - let (truncated_text, help_len) = truncate_help_line(help_text, width); - ( - style_help_text(&truncated_text).unbold_all(), - help_len, - None, - ) - } else { - let help_len = help_text.chars().count(); - (style_help_text(help_text).unbold_all(), help_len, None) - } -} - -/// Returns `(text, x, y)` tuples for the main screen intro hint. -/// The text is centered horizontally; if it does not fit on one line, two lines -/// are returned separated by one blank line (1-line padding). -pub fn render_intro_hint(rows: usize, cols: usize) -> Vec<(Text, usize, usize)> { - let full_len = INTRO_FULL.chars().count(); - - if full_len <= cols { - let x = cols.saturating_sub(full_len) / 2; - let y = rows / 2; - vec![(Text::new(INTRO_FULL).color_substring(3, "<e>"), x, y)] - } else { - let p1_len = INTRO_PART1.chars().count(); - let p2_len = INTRO_PART2.chars().count(); - let x1 = cols.saturating_sub(p1_len) / 2; - let x2 = cols.saturating_sub(p2_len) / 2; - // 3 display rows: line1, padding, line2 — center the group vertically - let start_y = rows.saturating_sub(3) / 2; - vec![ - ( - Text::new(INTRO_PART1).color_substring(3, "<e>"), - x1, - start_y, - ), - ( - Text::new(INTRO_PART2).color_substring(3, "<e>"), - x2, - start_y + 2, - ), - ] - } -} - -fn split_help_line(help_text: &str, max_width: usize) -> Option<(String, String)> { - let text_width = help_text.chars().count(); - - if text_width <= max_width { - return None; - } - - let keybindings = parse_help_keybindings(help_text); - - if keybindings.is_empty() { - return None; - } - - for split_point in 1..keybindings.len() { - let first_part: Vec<String> = keybindings - .iter() - .take(split_point) - .map(|(key, _, action)| format!("{} - {}", key, action)) - .collect(); - let first_line = first_part.join(", "); - - let second_part: Vec<String> = keybindings - .iter() - .skip(split_point) - .map(|(key, _, action)| format!("{} - {}", key, action)) - .collect(); - let second_line = second_part.join(", "); - - if first_line.chars().count() <= max_width && second_line.chars().count() <= max_width { - return Some((first_line, second_line)); - } - } - - None -} - -fn folder_cell(cwd: &Option<PathBuf>) -> (Text, String) { - let cwd_display = if let Some(cwd) = cwd { - format_cwd(cwd) - } else { - "~".to_string() - }; - let folder_display = format!("{} >", cwd_display); - let text = Text::new(&folder_display).color_range(0, 0..cwd_display.len()); - (text, folder_display) -} - -fn command_cell(text: &str) -> Text { - let text = if text.is_empty() { " " } else { text }; - Text::new(text) -} - -fn chain_cell(chain_type: &crate::state::ChainType) -> Text { - let text = chain_type_to_str(chain_type); - if !text.is_empty() { - Text::new(text).color_range(1, 0..text.len()) - } else { - Text::new(text) - } -} - -pub fn get_spinner_frame(frame: usize) -> &'static str { - SPINNER_FRAMES[frame % SPINNER_FRAMES.len()] -} - -pub fn format_status_text(status: &CommandStatus, spinner_frame: usize) -> String { - match status { - CommandStatus::Running(_) => { - let spinner = get_spinner_frame(spinner_frame); - format!("[RUNNING] {}", spinner) - }, - CommandStatus::Interrupted(_) => "[INTERRUPTED]".to_string(), - CommandStatus::Exited(Some(code), _) => format!("[EXIT CODE: {}]", code), - CommandStatus::Exited(None, _) => "[EXITED]".to_string(), - CommandStatus::Pending => " ".to_string(), - } -} - -fn apply_status_color(text: Text, status: &CommandStatus, status_str: &str) -> Text { - match status { - CommandStatus::Running(_) => text.color_range(3, 0..status_str.len()), - CommandStatus::Interrupted(_) => text.error_color_range(0..status_str.len()), - CommandStatus::Exited(Some(0), _) => { - let number_start = 12; - let number_end = status_str.len() - 1; - text.success_color_range(number_start..number_end) - }, - CommandStatus::Exited(Some(_), _) => { - let number_start = 12; - let number_end = status_str.len() - 1; - text.error_color_range(number_start..number_end) - }, - _ => text, - } -} - -fn status_cell(status: &CommandStatus, spinner_frame: usize) -> Text { - let status_str = format_status_text(status, spinner_frame); - let text = Text::new(&status_str); - apply_status_color(text, status, &status_str) -} - -fn apply_row_styles(cells: Vec<Text>, is_selected: bool) -> Vec<Text> { - let mut styled_cells = cells; - - if is_selected { - styled_cells = styled_cells.into_iter().map(|c| c.selected()).collect(); - } - - styled_cells -} - -fn overflow_cell(indicator: &str, is_selected: bool) -> Text { - let mut text = Text::new(indicator).dim_all(); - if is_selected { - text = text.selected(); - } - text -} - -fn command_sequence_row( - state: &State, - cwd: &Option<PathBuf>, - truncated_cmd_text: &str, - chain_type: &crate::state::ChainType, - status: &crate::state::CommandStatus, - is_selected: bool, - overflow_indicator: Option<String>, -) -> (Vec<Text>, usize) { - let (folder_text, folder_display) = folder_cell(cwd); - - let mut cells = vec![ - folder_text, - command_cell(truncated_cmd_text), - chain_cell(chain_type), - status_cell(status, state.layout.spinner_frame), - ]; - - cells = apply_row_styles(cells, is_selected); - - let truncated_cmd_text = if truncated_cmd_text.is_empty() { - " " - } else { - truncated_cmd_text - }; - let mut row_length = folder_display.chars().count() - + truncated_cmd_text.chars().count() - + chain_type_to_str(chain_type).chars().count() - + format_status_text(status, state.layout.spinner_frame) - .chars() - .count() - + 3; - - if let Some(indicator) = overflow_indicator { - let indicator_len = indicator.chars().count(); - cells.push(overflow_cell(&indicator, is_selected)); - row_length += indicator_len + 1; - } - - (cells, row_length) -} - -pub fn build_table_header(has_overflow: bool) -> Table { - let header_cols = if has_overflow { - vec![" ", " ", " ", " ", " "] - } else { - vec![" ", " ", " ", " "] - }; - Table::new().add_row(header_cols) -} - -fn calculate_overflow_indicator( - visible_idx: usize, - visible_count: usize, - hidden_above: usize, - hidden_below: usize, -) -> Option<String> { - if visible_idx == 0 && hidden_above > 0 { - Some(format!("[+{}]", hidden_above)) - } else if visible_idx == visible_count.saturating_sub(1) && hidden_below > 0 { - Some(format!("[+{}]", hidden_below)) - } else if hidden_above > 0 || hidden_below > 0 { - Some(format!(" ")) - } else { - None - } -} - -pub fn calculate_row_layout_info( - index: usize, - offset: usize, - visible_count: usize, - hidden_above: usize, - hidden_below: usize, - cols: usize, - folder_width: usize, - max_chain_width: usize, - max_status_width: usize, -) -> Option<(Option<String>, usize)> { - if index < offset || index >= offset + visible_count { - return None; - } - - let visible_idx = index.saturating_sub(offset); - let overflow_indicator = - calculate_overflow_indicator(visible_idx, visible_count, hidden_above, hidden_below); - - let available_cmd_width = calculate_available_cmd_width( - cols, - folder_width, - overflow_indicator.as_ref(), - max_chain_width, - max_status_width, - ); - - Some((overflow_indicator, available_cmd_width)) -} - -pub fn add_command_row( - table: Table, - state: &State, - index: usize, - offset: usize, - visible_count: usize, - hidden_above: usize, - hidden_below: usize, -) -> Table { - let command = match state.execution.all_commands.get(index) { - Some(cmd) => cmd, - None => return table, - }; - - let cmd_text = command.get_text(); - let cmd_text = cmd_text.trim(); - let chain_type = &command.get_chain_type(); - let status = &command.get_status(); - let command_cwd = command.get_cwd().or_else(|| state.cwd.clone()); - - let cols = state.own_columns.unwrap_or(80); - let longest_cwd_display = state.execution.longest_cwd_display(&state.cwd); - let folder_display = format!("{} >", longest_cwd_display); - let folder_width = folder_display.chars().count(); - - let (max_chain_width, max_status_width) = - calculate_max_widths(&state.execution.all_commands, state.layout.spinner_frame); - - let Some((overflow_indicator, available_cmd_width)) = calculate_row_layout_info( - index, - offset, - visible_count, - hidden_above, - hidden_below, - cols, - folder_width, - max_chain_width, - max_status_width, - ) else { - return table; - }; - - let truncated_cmd_text = truncate_middle(cmd_text, available_cmd_width, None).0; - - let (row, _) = command_sequence_row( - state, - &command_cwd, - &truncated_cmd_text, - chain_type, - status, - state.selection.current_selected_command_index == Some(index), - overflow_indicator, - ); - - table.add_styled_row(row) -} - -pub fn calculate_max_widths( - commands: &[crate::state::CommandEntry], - spinner_frame: usize, -) -> (usize, usize) { - let max_chain_width = commands - .iter() - .map(|cmd| chain_type_to_str(&cmd.get_chain_type()).chars().count()) - .max() - .unwrap_or(0); - - let max_status_width = commands - .iter() - .map(|cmd| { - format_status_text(&cmd.get_status(), spinner_frame) - .chars() - .count() - }) - .max() - .unwrap_or(0); - - (max_chain_width, max_status_width) -} - -pub fn calculate_longest_line( - longest_cwd_display: &str, - longest_command: usize, - max_chain_width: usize, - max_status_width: usize, -) -> usize { - let folder_display = format!("{} >", longest_cwd_display); - let cell_padding = 3; - folder_display.chars().count() - + longest_command - + max_chain_width - + max_status_width - + cell_padding -} - -pub fn chain_type_to_str(chain_type: &crate::state::ChainType) -> &'static str { - match chain_type { - crate::state::ChainType::And => "AND", - crate::state::ChainType::Or => "OR", - crate::state::ChainType::Then => "THEN", - crate::state::ChainType::None => " ", - } -} - -pub fn truncate_help_line(help_text: &str, max_width: usize) -> (String, usize) { - let text_width = help_text.chars().count(); - - if text_width <= max_width { - return (help_text.to_string(), text_width); - } - - let keybindings = parse_help_keybindings(help_text); - - if let Some(result) = try_shortened_help(&keybindings, max_width) { - return result; - } - - if let Some(result) = try_keys_spaced(&keybindings, max_width) { - return result; - } - - if let Some(result) = try_keys_tight(&keybindings, max_width) { - return result; - } - - truncate_with_ellipsis( - &keybindings - .iter() - .map(|(k, _, _)| *k) - .collect::<Vec<_>>() - .join("/"), - max_width, - ) -} - -fn parse_help_keybindings(help_text: &str) -> Vec<(&str, &str, &str)> { - help_text - .split(", ") - .filter_map(|part| { - part.find(" - ").map(|dash_pos| { - let key = &part[..dash_pos]; - let action = &part[dash_pos + 3..]; - let first_word = action.split_whitespace().next().unwrap_or(action); - (key, first_word, action) - }) - }) - .collect() -} - -fn try_shortened_help( - keybindings: &[(&str, &str, &str)], - max_width: usize, -) -> Option<(String, usize)> { - let shortened: Vec<String> = keybindings - .iter() - .map(|(key, first_word, _)| format!("{} - {}", key, first_word)) - .collect(); - let shortened_text = shortened.join(", "); - let shortened_width = shortened_text.chars().count(); - - if shortened_width <= max_width { - Some((shortened_text, shortened_width)) - } else { - None - } -} - -fn try_keys_spaced( - keybindings: &[(&str, &str, &str)], - max_width: usize, -) -> Option<(String, usize)> { - let keys_spaced_text = keybindings - .iter() - .map(|(key, _, _)| *key) - .collect::<Vec<_>>() - .join(" / "); - let keys_spaced_width = keys_spaced_text.chars().count(); - - if keys_spaced_width <= max_width { - Some((keys_spaced_text, keys_spaced_width)) - } else { - None - } -} - -fn try_keys_tight(keybindings: &[(&str, &str, &str)], max_width: usize) -> Option<(String, usize)> { - let keys_tight_text = keybindings - .iter() - .map(|(key, _, _)| *key) - .collect::<Vec<_>>() - .join("/"); - let keys_tight_width = keys_tight_text.chars().count(); - - if keys_tight_width <= max_width { - Some((keys_tight_text, keys_tight_width)) - } else { - None - } -} - -fn truncate_with_ellipsis(text: &str, max_width: usize) -> (String, usize) { - let available = max_width.saturating_sub(3); - let mut truncated = String::new(); - let mut current_width = 0; - - for ch in text.chars() { - if current_width + 1 <= available { - truncated.push(ch); - current_width += 1; - } else { - break; - } - } - - truncated.push_str("..."); - let truncated_width = truncated.chars().count(); - (truncated, truncated_width) -} |
