aboutsummaryrefslogtreecommitdiffstats
path: root/default-plugins/sequence/src/main.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/main.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/main.rs')
-rw-r--r--default-plugins/sequence/src/main.rs798
1 files changed, 0 insertions, 798 deletions
diff --git a/default-plugins/sequence/src/main.rs b/default-plugins/sequence/src/main.rs
deleted file mode 100644
index 9c99ac5d..00000000
--- a/default-plugins/sequence/src/main.rs
+++ /dev/null
@@ -1,798 +0,0 @@
-mod path_formatting;
-mod state;
-mod ui;
-
-use crate::state::CommandStatus;
-use crate::ui::components;
-use crate::ui::layout_calculations::calculate_viewport;
-use state::{parse_commands, ChainType, CommandEntry, SequenceMode, State};
-use zellij_tile::prelude::*;
-
-use std::collections::BTreeMap;
-use std::path::PathBuf;
-
-register_plugin!(State);
-
-impl ZellijPlugin for State {
- fn load(&mut self, _configuration: BTreeMap<String, String>) {
- subscribe(&[
- EventType::ModeUpdate,
- EventType::Key,
- EventType::PermissionRequestResult,
- EventType::HostFolderChanged,
- EventType::CommandPaneExited,
- EventType::CommandPaneOpened,
- EventType::EditPaneExited,
- EventType::Timer,
- EventType::PastedText,
- EventType::TabUpdate,
- EventType::BeforeClose,
- ]);
-
- // Store our own plugin ID and client ID
- let plugin_ids = get_plugin_ids();
- self.plugin_id = Some(plugin_ids.plugin_id);
- self.client_id = Some(plugin_ids.client_id);
- self.cwd = Some(plugin_ids.initial_cwd);
- rename_plugin_pane(plugin_ids.plugin_id, "Sequence");
- update_title(self);
- }
-
- fn update(&mut self, event: Event) -> bool {
- handle_event(self, event)
- }
-
- #[cfg(target_family = "wasm")]
- fn pipe(&mut self, pipe_message: PipeMessage) -> bool {
- if !pipe_message.is_private {
- return false;
- }
-
- let Some(payload) = pipe_message.payload else {
- return false;
- };
-
- let payload = payload.trim().to_string();
- if payload.is_empty() {
- return false;
- }
-
- // If already running, emit error
- if self.execution.is_running {
- if let PipeSource::Cli(pipe_id) = &pipe_message.source {
- #[cfg(target_family = "wasm")]
- cli_pipe_output(pipe_id, "error: sequence already running\n");
- }
- return false;
- }
-
- self.load_commands(&payload, None);
-
- // Handle blocking: keep CLI alive until sequence finishes
- if pipe_message
- .args
- .get("blocking")
- .map(|v| v == "true")
- .unwrap_or(false)
- {
- if let PipeSource::Cli(pipe_id) = &pipe_message.source {
- block_cli_pipe_input(pipe_id);
- self.blocking_pipe_id = Some(pipe_id.clone());
- }
- }
-
- show_self(true);
-
- true
- }
-
- fn render(&mut self, rows: usize, cols: usize) {
- // Store dimensions for use in calculations
- self.own_rows = Some(rows);
- self.own_columns = Some(cols);
-
- // Main screen: plugin just opened, no commands entered yet
- if self.all_commands_are_pending() && !self.execution.can_run_sequence() {
- for (text, x, y) in components::render_intro_hint(rows, cols) {
- print_text_with_coordinates(text, x, y, None, None);
- }
- return;
- }
-
- let is_staging = self.all_commands_are_pending() && self.execution.can_run_sequence();
-
- // Reserve: 1 header above + 1 padding-to-controls + 1 controls + 1 below/ribbon
- let max_visible_rows = rows.saturating_sub(4);
-
- let (offset, visible_count, hidden_above, hidden_below) = calculate_viewport(
- self.execution.all_commands.len(),
- max_visible_rows,
- self.selection.current_selected_command_index,
- self.execution.current_running_command_index,
- );
-
- // For the staging screen, center the content both horizontally and vertically.
- // Content height: 1 header row + visible_count command rows + 1 gap + 1 help row = visible_count + 3
- let (base_x, base_y, table_max_width) = if is_staging {
- let longest_cwd = self.execution.longest_cwd_display(&self.cwd);
- let longest_cmd = self
- .execution
- .all_commands
- .iter()
- .map(|c| c.get_text().chars().count())
- .max()
- .unwrap_or(1);
- let (max_chain_w, max_status_w) = components::calculate_max_widths(
- &self.execution.all_commands,
- self.layout.spinner_frame,
- );
- let content_width = components::calculate_longest_line(
- &longest_cwd,
- longest_cmd,
- max_chain_w,
- max_status_w,
- )
- .min(cols);
- // header + commands + gap + help + ribbon/below = visible_count + 4
- let content_height = visible_count + 4;
- let bx = cols.saturating_sub(content_width) / 2;
- let by = rows.saturating_sub(content_height) / 2;
- (bx, by, Some(content_width))
- } else {
- (1, 0, self.own_columns.map(|o| o.saturating_sub(1)))
- };
-
- let mut table = components::build_table_header(hidden_above > 0 || hidden_below > 0);
-
- for index in offset..offset + visible_count {
- table = components::add_command_row(
- table,
- self,
- index,
- offset,
- visible_count,
- hidden_above,
- hidden_below,
- );
- }
-
- print_table_with_coordinates(table, base_x, base_y, table_max_width, None);
-
- let help_y = base_y + visible_count + 2;
- let (first_help, first_help_len, second_help) =
- components::render_help_lines(self, Some(cols));
- let help_x = if is_staging {
- cols.saturating_sub(first_help_len) / 2
- } else {
- base_x
- };
- print_text_with_coordinates(first_help, help_x, help_y, None, None);
-
- if let Some((second_help_text, second_help_len)) = second_help {
- let second_help_x = if is_staging {
- cols.saturating_sub(second_help_len) / 2
- } else {
- base_x
- };
- print_text_with_coordinates(second_help_text, second_help_x, help_y + 1, None, None);
- }
-
- // Mode ribbon: "<Tab> [Single Pane] [Spread]" — only while running
- if self.execution.is_running {
- let ribbon_y = help_y + 1;
- print_text_with_coordinates(
- Text::new("<Tab>").color_all(3),
- base_x,
- ribbon_y,
- None,
- None,
- );
- let ribbon_x = base_x + 6; // "<Tab>" (5) + space (1)
- let (single_ribbon, spread_ribbon) = if self.mode == SequenceMode::SinglePane {
- (Text::new("Single Pane").selected(), Text::new("Spread"))
- } else {
- (Text::new("Single Pane"), Text::new("Spread").selected())
- };
- print_ribbon_with_coordinates(single_ribbon, ribbon_x, ribbon_y, Some(15), None);
- print_ribbon_with_coordinates(spread_ribbon, ribbon_x + 15, ribbon_y, Some(10), None);
- }
- }
-}
-
-pub fn handle_event(state: &mut State, event: Event) -> bool {
- use std::path::PathBuf;
-
- match event {
- Event::PermissionRequestResult(_) => {
- change_host_folder(PathBuf::from("/"));
- update_title(state);
- true
- },
- Event::ModeUpdate(mode_info) => {
- state.shell = mode_info.shell.clone();
- false
- },
- Event::Key(key) => {
- let should_render = handle_key_event(state, key);
- let repositioned = state.reposition_plugin();
- if repositioned {
- // we only want to render once we have repositioned, we will do this in TabUpdate
- return false;
- }
- show_cursor(None);
- should_render
- },
- Event::Timer(_elapsed) => {
- let should_render = update_spinner(state);
- update_title(state);
- should_render
- },
- Event::PastedText(pasted_text) => {
- if state.execution.is_running {
- return false;
- }
- let payload = pasted_text.trim().to_string();
- if payload.is_empty() {
- return false;
- }
- state.load_commands(&payload, None);
- let repositioned = state.reposition_plugin();
- !repositioned
- },
- Event::TabUpdate(tab_infos) => {
- if let Some(tab_info) = tab_infos.iter().find(|t| t.active) {
- state.total_viewport_columns = Some(tab_info.viewport_columns);
- state.total_viewport_rows = Some(tab_info.viewport_rows);
- }
- state.reposition_plugin()
- },
- Event::CommandPaneExited(terminal_pane_id, exit_code, _context) => {
- let should_render = handle_command_pane_exited(state, terminal_pane_id, exit_code);
- let repositioned = state.reposition_plugin();
- update_title(state);
- should_render || repositioned
- },
- Event::CommandPaneOpened(_terminal_pane_id, _context) => {
- // Schedule spinner on first pane open
- if !state.layout.spinner_timer_scheduled {
- set_timeout(0.1);
- state.layout.spinner_timer_scheduled = true;
- }
- update_title(state);
- true
- },
- Event::EditPaneExited(terminal_pane_id, _exit_code, _context) => {
- let should_render = handle_editor_pane_exited(state, terminal_pane_id);
- let repositioned = state.reposition_plugin();
- should_render || repositioned
- },
- Event::BeforeClose => {
- if let Some(pipe_id) = state.blocking_pipe_id.take() {
- unblock_cli_pipe_input(&pipe_id);
- }
- false
- },
- _ => false,
- }
-}
-
-fn handle_command_pane_exited(
- state: &mut State,
- terminal_pane_id: u32,
- exit_code: Option<i32>,
-) -> bool {
- let pane_id = PaneId::Terminal(terminal_pane_id);
-
- // Find which command this pane belongs to — stale events produce no match
- let Some(cmd_index) =
- state.execution.all_commands.iter().position(
- |c| matches!(c.get_status(), CommandStatus::Running(Some(id)) if id == pane_id),
- )
- else {
- return false;
- };
-
- state.execution.all_commands[cmd_index]
- .set_status(CommandStatus::Exited(exit_code, Some(pane_id)));
-
- if !state.execution.is_running {
- return true;
- }
-
- let chain_type = state.execution.all_commands[cmd_index].get_chain_type();
- let should_continue = match chain_type {
- ChainType::And => exit_code.unwrap_or(0) == 0,
- ChainType::Or => exit_code.unwrap_or(0) != 0,
- ChainType::Then => true,
- ChainType::None => false,
- };
-
- let next_index = cmd_index + 1;
- if should_continue && next_index < state.execution.all_commands.len() {
- launch_command_at_index(state, next_index, Some(pane_id), false);
- } else {
- state.execution.is_running = false;
- state.selection.current_selected_command_index = None;
- // Unblock blocking CLI pipe if one is waiting
- if let Some(pipe_id) = state.blocking_pipe_id.take() {
- unblock_cli_pipe_input(&pipe_id);
- }
- }
- true
-}
-
-fn handle_editor_pane_exited(state: &mut State, terminal_pane_id: u32) -> bool {
- let pane_id = PaneId::Terminal(terminal_pane_id);
-
- // Ignore events from unrelated panes
- if state.editor_pane_id != Some(pane_id) {
- return false;
- }
-
- // Read the temp file contents
- let contents = if let Some(ref path) = state.editor_temp_file {
- std::fs::read_to_string(path).unwrap_or_default()
- } else {
- String::new()
- };
-
- // Delete the temp file
- if let Some(ref path) = state.editor_temp_file {
- let _ = std::fs::remove_file(path);
- }
-
- // Clear editor state
- state.editor_pane_id = None;
- state.editor_temp_file = None;
-
- // Parse file into commands
- let commands = parse_commands(&contents, state.cwd.clone());
-
- if !commands.is_empty() {
- state.execution.all_commands = commands;
- state.execution.current_running_command_index = 0;
- state.selection.current_selected_command_index = None;
- state.execution.is_running = false;
- } else {
- // Reset to empty pending state
- state.execution.all_commands = vec![CommandEntry::new("", state.cwd.clone())];
- state.execution.current_running_command_index = 0;
- state.selection.current_selected_command_index = None;
- state.execution.is_running = false;
- }
-
- show_self(true);
-
- true
-}
-
-fn launch_command_at_index(
- state: &mut State,
- index: usize,
- replace_pane_id: Option<PaneId>,
- force_visible: bool,
-) {
- let cmd = &state.execution.all_commands[index];
- let cmd_text = cmd.get_text().trim().to_string();
- let command = CommandToRun {
- path: state
- .shell
- .clone()
- .unwrap_or_else(|| PathBuf::from("/bin/bash")),
- args: vec!["-ic".to_string(), cmd_text.clone()],
- cwd: cmd.get_cwd(),
- };
- let user_is_watching =
- replace_pane_id.is_some() && replace_pane_id == state.execution.displayed_pane_id;
- let new_pane_id = if state.mode == SequenceMode::Spread {
- open_command_pane_near_plugin(command, BTreeMap::new())
- } else if user_is_watching {
- let prev_pane_id = replace_pane_id.unwrap();
- let pane_id =
- open_command_pane_in_place_of_pane_id(prev_pane_id, command, false, BTreeMap::new());
- if let Some(pane_id) = pane_id {
- state.execution.displayed_pane_id = Some(pane_id);
- }
- pane_id
- } else if force_visible {
- let (tab_id, pane_id) = open_command_pane_in_new_tab(command, BTreeMap::new());
- if let Some(pane_id) = pane_id {
- state.execution.displayed_pane_id = Some(pane_id);
- }
- if let (Some(tab_id), Some(plugin_id)) = (tab_id, state.plugin_id) {
- state.sequence_tab_id = Some(tab_id);
- break_panes_to_tab_with_id(&[PaneId::Plugin(plugin_id)], tab_id, true);
- focus_pane_with_id(PaneId::Plugin(plugin_id), false, false);
- }
- pane_id
- } else {
- // User navigated away — open in background, invisible until they navigate to it
- open_command_pane_background(command, BTreeMap::new())
- };
- if let Some(pane_id) = new_pane_id {
- rename_pane_with_id(pane_id, &cmd_text);
- state.execution.all_commands[index].set_status(CommandStatus::Running(Some(pane_id)));
- state.execution.current_running_command_index = index;
- state.selection.current_selected_command_index = Some(index);
- state.show_selected_pane();
- }
-}
-
-fn handle_key_event(state: &mut State, key: KeyWithModifier) -> bool {
- // Ctrl+Space — copy to clipboard
- if key.has_modifiers(&[KeyModifier::Ctrl]) && matches!(key.bare_key, BareKey::Char(' ')) {
- state.copy_to_clipboard();
- return false;
- }
-
- // Ctrl+W — close tab if sequence has run; close plugin if staging; no-op if running
- if key.has_modifiers(&[KeyModifier::Ctrl]) && matches!(key.bare_key, BareKey::Char('w')) {
- if state.all_commands_are_pending() {
- if let Some(plugin_id) = state.plugin_id {
- close_pane_with_id(PaneId::Plugin(plugin_id));
- }
- } else if !state.execution.is_running {
- close_panes_and_return_to_shell(state);
- }
- return true;
- }
-
- // Ctrl+C — interrupt if running; clear commands if staging; close plugin if empty
- if key.has_modifiers(&[KeyModifier::Ctrl]) && matches!(key.bare_key, BareKey::Char('c')) {
- if state.execution.is_running {
- interrupt_sequence(state);
- return true;
- } else if state.all_commands_are_pending() {
- if state.execution.all_commands.iter().any(|c| !c.is_empty()) {
- state.clear_all_commands();
- } else if let Some(plugin_id) = state.plugin_id {
- close_pane_with_id(PaneId::Plugin(plugin_id));
- }
- return true;
- }
- }
-
- let is_staging = state.all_commands_are_pending() && state.execution.can_run_sequence();
-
- if matches!(key.bare_key, BareKey::Up)
- && !key.has_modifiers(&[KeyModifier::Ctrl])
- && !key.has_modifiers(&[KeyModifier::Alt])
- {
- if !is_staging {
- state.move_selection_up();
- return true;
- }
- return false;
- }
-
- if matches!(key.bare_key, BareKey::Down)
- && !key.has_modifiers(&[KeyModifier::Ctrl])
- && !key.has_modifiers(&[KeyModifier::Alt])
- {
- if !is_staging {
- state.move_selection_down();
- return true;
- }
- return false;
- }
-
- // e — open external editor (when not running)
- if !state.execution.is_running
- && key.has_no_modifiers()
- && matches!(key.bare_key, BareKey::Char('e'))
- {
- state.open_editor();
- return true;
- }
-
- // Enter — rerun sequence
- if key.has_no_modifiers() && matches!(key.bare_key, BareKey::Enter) {
- rerun_sequence(state);
- return true;
- }
-
- // Tab — toggle spread mode (when sequence has run)
- if key.has_no_modifiers()
- && matches!(key.bare_key, BareKey::Tab)
- && !state.all_commands_are_pending()
- {
- toggle_spread_mode(state);
- return true;
- }
-
- false
-}
-
-fn close_panes_and_return_to_shell(state: &mut State) -> bool {
- if let Some(tab_id) = state.sequence_tab_id {
- close_tab_with_id(tab_id as u64);
- }
- true
-}
-
-fn update_spinner(state: &mut State) -> bool {
- // Advance spinner frame for RUNNING animation
- state.layout.spinner_frame = (state.layout.spinner_frame + 1) % 8;
-
- // Reset timer scheduled flag since this timer just fired
- state.layout.spinner_timer_scheduled = false;
-
- // If we have a running command, schedule next timer event
- let has_running = state
- .execution
- .all_commands
- .iter()
- .any(|command| matches!(command.get_status(), CommandStatus::Running(_)));
- if has_running {
- set_timeout(0.1);
- state.layout.spinner_timer_scheduled = true;
- }
- has_running
-}
-
-fn rerun_sequence(state: &mut State) {
- if !state.can_run_sequence() {
- return;
- }
-
- state.execution.all_commands.retain(|c| !c.is_empty());
-
- if state.execution.all_commands.is_empty() {
- return;
- }
-
- let selected_index = state
- .selection
- .current_selected_command_index
- .unwrap_or(0)
- .min(state.execution.all_commands.len().saturating_sub(1));
-
- state.execution.is_running = true;
- state.execution.current_running_command_index = selected_index;
- ensure_spinner_running(state);
- match state.execution.all_commands[selected_index].get_pane_id() {
- Some(PaneId::Terminal(id)) => {
- let pane_id = PaneId::Terminal(id);
-
- // Replace the displayed pane with the starting pane first, while
- // displayed_pane_id is still valid. The replaced pane is suppressed
- // and can then be closed safely below.
- if state.mode == SequenceMode::SinglePane {
- if let Some(displayed) = state.execution.displayed_pane_id {
- if displayed != pane_id {
- replace_pane_with_existing_pane(displayed, pane_id, true);
- }
- }
- state.execution.displayed_pane_id = Some(pane_id);
- }
-
- // Close and reset commands after selected (now suppressed).
- for i in (selected_index + 1)..state.execution.all_commands.len() {
- if let Some(p) = state.execution.all_commands[i].get_pane_id() {
- close_pane_with_id(p);
- }
- state.execution.all_commands[i].set_status(CommandStatus::Pending);
- }
-
- state.execution.all_commands[selected_index]
- .set_status(CommandStatus::Running(Some(pane_id)));
- state.selection.current_selected_command_index = Some(selected_index);
- rerun_command_pane(id);
- },
- _ => {
- // Close and reset commands after selected.
- for i in (selected_index + 1)..state.execution.all_commands.len() {
- if let Some(p) = state.execution.all_commands[i].get_pane_id() {
- close_pane_with_id(p);
- }
- state.execution.all_commands[i].set_status(CommandStatus::Pending);
- }
- let replace_pane_id = state.execution.displayed_pane_id;
- launch_command_at_index(state, selected_index, replace_pane_id, true);
- },
- }
-}
-
-fn toggle_spread_mode(state: &mut State) {
- match state.mode {
- SequenceMode::SinglePane => enter_spread_mode(state),
- SequenceMode::Spread => exit_spread_mode(state),
- }
-}
-
-fn enter_spread_mode(state: &mut State) {
- state.mode = SequenceMode::Spread;
-
- let first_pane_id = state
- .execution
- .all_commands
- .first()
- .and_then(|c| c.get_pane_id());
- let Some(first_pane_id) = first_pane_id else {
- return;
- };
-
- if let Some(displayed) = state.execution.displayed_pane_id {
- if displayed != first_pane_id {
- replace_pane_with_existing_pane(displayed, first_pane_id, true);
- }
- }
- state.execution.displayed_pane_id = Some(first_pane_id);
-
- for cmd in state.execution.all_commands.iter().skip(1) {
- if let Some(pane_id) = cmd.get_pane_id() {
- show_pane_with_id(pane_id, false, false);
- }
- }
- state.show_selected_pane();
-}
-
-fn exit_spread_mode(state: &mut State) {
- // Remove all highlights before leaving spread mode
- let all_pane_ids: Vec<PaneId> = state
- .execution
- .all_commands
- .iter()
- .filter_map(|c| c.get_pane_id())
- .collect();
- if !all_pane_ids.is_empty() {
- highlight_and_unhighlight_panes(vec![], all_pane_ids);
- }
- state.mode = SequenceMode::SinglePane;
-
- let first_pane_id = state
- .execution
- .all_commands
- .first()
- .and_then(|c| c.get_pane_id());
- let selected_pane_id = state
- .selection
- .current_selected_command_index
- .and_then(|i| state.execution.all_commands.get(i))
- .and_then(|c| c.get_pane_id())
- .or(first_pane_id);
-
- let Some(selected_pane_id) = selected_pane_id else {
- return;
- };
-
- if let Some(first_pane_id) = first_pane_id {
- if first_pane_id != selected_pane_id {
- replace_pane_with_existing_pane(first_pane_id, selected_pane_id, true);
- }
- }
-
- for cmd in &state.execution.all_commands {
- if let Some(pane_id) = cmd.get_pane_id() {
- if pane_id != selected_pane_id {
- hide_pane_with_id(pane_id);
- }
- }
- }
-
- state.execution.displayed_pane_id = Some(selected_pane_id);
-}
-
-fn interrupt_sequence(state: &mut State) {
- if state.execution.is_running {
- for command in state.execution.all_commands.iter_mut() {
- if let CommandStatus::Running(pane_id) = command.get_status() {
- if let Some(pane_id) = pane_id {
- send_sigkill_to_pane_id(pane_id);
- }
- command.set_status(CommandStatus::Interrupted(pane_id));
- }
- }
- state.execution.is_running = false;
- state.selection.current_selected_command_index = None;
- // Unblock blocking CLI pipe if interrupted
- if let Some(pipe_id) = state.blocking_pipe_id.take() {
- unblock_cli_pipe_input(&pipe_id);
- }
- } else {
- eprintln!("Cannot interrupt a sequence that is not running.");
- }
-}
-
-pub fn update_title(state: &mut State) {
- let Some(tab_id) = state.sequence_tab_id else {
- return;
- };
- let tab_id_u64 = tab_id as u64;
-
- // All pending and not yet running
- if state.all_commands_are_pending() && !state.execution.is_running {
- rename_tab_with_id(tab_id_u64, "Run one or more commands in sequence");
- return;
- }
-
- // Currently running: "<cmd> <n>/<total> <spinner>"
- let has_running = state
- .execution
- .all_commands
- .iter()
- .any(|c| matches!(c.get_status(), CommandStatus::Running(..)));
- if has_running {
- let idx = state.execution.current_running_command_index;
- let total = state.execution.all_commands.len();
- let cmd_text = state
- .execution
- .all_commands
- .get(idx)
- .map(|c| c.get_text())
- .unwrap_or_default();
- let spinner = components::get_spinner_frame(state.layout.spinner_frame);
- rename_tab_with_id(
- tab_id_u64,
- &format!("{} {}/{} {}", cmd_text, idx + 1, total, spinner),
- );
- return;
- }
-
- // Interrupted: "<cmd> [INTERRUPTED]"
- if let Some(cmd) = state
- .execution
- .all_commands
- .iter()
- .find(|c| matches!(c.get_status(), CommandStatus::Interrupted(_)))
- {
- let status_str =
- components::format_status_text(&cmd.get_status(), state.layout.spinner_frame);
- rename_tab_with_id(tab_id_u64, &format!("{} {}", cmd.get_text(), status_str));
- return;
- }
-
- // All exited
- let all_exited = state
- .execution
- .all_commands
- .iter()
- .all(|c| matches!(c.get_status(), CommandStatus::Exited(..)));
- if all_exited {
- let all_success = state
- .execution
- .all_commands
- .iter()
- .all(|c| matches!(c.get_status(), CommandStatus::Exited(Some(0), _)));
- if all_success {
- rename_tab_with_id(tab_id_u64, "Sequence Complete");
- } else {
- // Show first command with non-zero or unknown exit
- if let Some(cmd) = state
- .execution
- .all_commands
- .iter()
- .find(|c| !matches!(c.get_status(), CommandStatus::Exited(Some(0), _)))
- {
- let status_str =
- components::format_status_text(&cmd.get_status(), state.layout.spinner_frame);
- rename_tab_with_id(tab_id_u64, &format!("{} {}", cmd.get_text(), status_str));
- } else {
- rename_tab_with_id(tab_id_u64, "Sequence Complete");
- }
- }
- return;
- }
-
- // Partial (chain condition failed): show last exited command with its status
- if let Some(cmd) = state
- .execution
- .all_commands
- .iter()
- .rev()
- .find(|c| matches!(c.get_status(), CommandStatus::Exited(..)))
- {
- let status_str =
- components::format_status_text(&cmd.get_status(), state.layout.spinner_frame);
- rename_tab_with_id(tab_id_u64, &format!("{} {}", cmd.get_text(), status_str));
- } else {
- rename_tab_with_id(tab_id_u64, "Stopped");
- }
-}
-
-fn ensure_spinner_running(state: &mut State) {
- if !state.layout.spinner_timer_scheduled {
- set_timeout(0.1);
- state.layout.spinner_timer_scheduled = true;
- }
-}