aboutsummaryrefslogtreecommitdiffstats
path: root/default-plugins/sequence/src/ui/components.rs
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2026-02-24 13:10:40 +0100
committerGitHub <noreply@github.com>2026-02-24 13:10:40 +0100
commit8cd6286c3ffc10d14fdafd5005fa89b0f7fdcd44 (patch)
treec6341f5e3e251ae17db0c3427ddaf1e54d4fa91c /default-plugins/sequence/src/ui/components.rs
parentAllow closing replaced panes when opening panes in place (#4760) (diff)
downloadsl0ppij-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.rs567
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)
-}