aboutsummaryrefslogtreecommitdiffstats
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
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)
-rw-r--r--Cargo.lock14
-rw-r--r--Cargo.toml1
-rw-r--r--default-plugins/configuration/src/presets.rs36
-rw-r--r--default-plugins/sequence/.cargo/config.toml2
-rw-r--r--default-plugins/sequence/.gitignore1
-rw-r--r--default-plugins/sequence/Cargo.toml17
l---------default-plugins/sequence/LICENSE.md1
-rw-r--r--default-plugins/sequence/src/lib.rs5
-rw-r--r--default-plugins/sequence/src/main.rs798
-rw-r--r--default-plugins/sequence/src/path_formatting.rs71
-rw-r--r--default-plugins/sequence/src/state/chain_type.rs45
-rw-r--r--default-plugins/sequence/src/state/command_entry.rs76
-rw-r--r--default-plugins/sequence/src/state/command_parser.rs370
-rw-r--r--default-plugins/sequence/src/state/command_status.rs26
-rw-r--r--default-plugins/sequence/src/state/execution.rs87
-rw-r--r--default-plugins/sequence/src/state/layout.rs19
-rw-r--r--default-plugins/sequence/src/state/mod.rs231
-rw-r--r--default-plugins/sequence/src/state/selection.rs47
-rw-r--r--default-plugins/sequence/src/state/sequence_mode.rs6
-rw-r--r--default-plugins/sequence/src/ui/components.rs567
-rw-r--r--default-plugins/sequence/src/ui/layout_calculations.rs28
-rw-r--r--default-plugins/sequence/src/ui/mod.rs3
-rw-r--r--default-plugins/sequence/src/ui/truncation.rs373
-rw-r--r--example/default.kdl1
-rw-r--r--src/main.rs27
-rw-r--r--src/tests/e2e/cases.rs2
-rw-r--r--xtask/src/main.rs4
-rw-r--r--zellij-utils/assets/config/default.kdl7
-rwxr-xr-xzellij-utils/assets/plugins/sequence.wasmbin1482266 -> 0 bytes
-rw-r--r--zellij-utils/src/cli.rs28
-rw-r--r--zellij-utils/src/consts.rs1
-rw-r--r--zellij-utils/src/input/plugins.rs1
-rw-r--r--zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap7
-rw-r--r--zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap7
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap43
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap43
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap12
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap43
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap43
39 files changed, 1 insertions, 3092 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c8a6b960..fe30634a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3326,20 +3326,6 @@ dependencies = [
]
[[package]]
-name = "sequence"
-version = "0.1.0"
-dependencies = [
- "fuzzy-matcher",
- "regex",
- "serde",
- "serde_json",
- "strum",
- "unicode-width 0.1.10",
- "uuid",
- "zellij-tile",
-]
-
-[[package]]
name = "serde"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 80972838..1794dbdd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -52,7 +52,6 @@ members = [
"default-plugins/about",
"default-plugins/share",
"default-plugins/multiple-select",
- "default-plugins/sequence",
"default-plugins/layout-manager",
"zellij-client",
"zellij-server",
diff --git a/default-plugins/configuration/src/presets.rs b/default-plugins/configuration/src/presets.rs
index 03d05171..4eb65042 100644
--- a/default-plugins/configuration/src/presets.rs
+++ b/default-plugins/configuration/src/presets.rs
@@ -155,12 +155,6 @@ keybinds clear-defaults=true {{
}};
SwitchToMode "Locked"
}}
- bind "q" {{
- LaunchPlugin "zellij:sequence" {{
- floating true
- }};
- SwitchToMode "Locked"
- }}
bind "l" {{
LaunchOrFocusPlugin "zellij:layout-manager" {{
floating true
@@ -384,12 +378,6 @@ keybinds clear-defaults=true {{
}};
SwitchToMode "Normal"
}}
- bind "q" {{
- LaunchPlugin "zellij:sequence" {{
- floating true
- }};
- SwitchToMode "Normal"
- }}
bind "l" {{
LaunchOrFocusPlugin "zellij:layout-manager" {{
floating true
@@ -613,12 +601,6 @@ keybinds clear-defaults=true {{
}};
SwitchToMode "Normal"
}}
- bind "q" {{
- LaunchPlugin "zellij:sequence" {{
- floating true
- }};
- SwitchToMode "Normal"
- }}
bind "l" {{
LaunchOrFocusPlugin "zellij:layout-manager" {{
floating true
@@ -823,12 +805,6 @@ keybinds clear-defaults=true {{
}};
SwitchToMode "Normal"
}}
- bind "q" {{
- LaunchPlugin "zellij:sequence" {{
- floating true
- }};
- SwitchToMode "Normal"
- }}
bind "l" {{
LaunchOrFocusPlugin "zellij:layout-manager" {{
floating true
@@ -1033,12 +1009,6 @@ keybinds clear-defaults=true {{
}};
SwitchToMode "Normal"
}}
- bind "q" {{
- LaunchPlugin "zellij:sequence" {{
- floating true
- }};
- SwitchToMode "Normal"
- }}
bind "l" {{
LaunchOrFocusPlugin "zellij:layout-manager" {{
floating true
@@ -1238,12 +1208,6 @@ keybinds clear-defaults=true {{
}};
SwitchToMode "Normal"
}}
- bind "q" {{
- LaunchPlugin "zellij:sequence" {{
- floating true
- }};
- SwitchToMode "Normal"
- }}
bind "l" {{
LaunchOrFocusPlugin "zellij:layout-manager" {{
floating true
diff --git a/default-plugins/sequence/.cargo/config.toml b/default-plugins/sequence/.cargo/config.toml
deleted file mode 100644
index 6b509f5b..00000000
--- a/default-plugins/sequence/.cargo/config.toml
+++ /dev/null
@@ -1,2 +0,0 @@
-[build]
-target = "wasm32-wasip1"
diff --git a/default-plugins/sequence/.gitignore b/default-plugins/sequence/.gitignore
deleted file mode 100644
index ea8c4bf7..00000000
--- a/default-plugins/sequence/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/target
diff --git a/default-plugins/sequence/Cargo.toml b/default-plugins/sequence/Cargo.toml
deleted file mode 100644
index 4498d8d0..00000000
--- a/default-plugins/sequence/Cargo.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-[package]
-name = "sequence"
-version = "0.1.0"
-authors = ["Aram Drevekenin <aram@poor.dev>"]
-edition.workspace = true
-license.workspace = true
-
-[dependencies]
-zellij-tile = { path = "../../zellij-tile" }
-unicode-width = "0.1"
-fuzzy-matcher = "0.3"
-# TODO: get this from Zellij so that we don't have to match versions
-strum = "0.27"
-regex = "1"
-serde = { version = "1.0", features = ["derive"] }
-serde_json = "1.0"
-uuid = { version = "1.0", features = ["v4"] }
diff --git a/default-plugins/sequence/LICENSE.md b/default-plugins/sequence/LICENSE.md
deleted file mode 120000
index f0608a63..00000000
--- a/default-plugins/sequence/LICENSE.md
+++ /dev/null
@@ -1 +0,0 @@
-../../LICENSE.md \ No newline at end of file
diff --git a/default-plugins/sequence/src/lib.rs b/default-plugins/sequence/src/lib.rs
deleted file mode 100644
index 1e124979..00000000
--- a/default-plugins/sequence/src/lib.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-pub mod path_formatting;
-pub mod state;
-pub mod ui;
-
-pub use state::State;
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;
- }
-}
diff --git a/default-plugins/sequence/src/path_formatting.rs b/default-plugins/sequence/src/path_formatting.rs
deleted file mode 100644
index 01bf50e6..00000000
--- a/default-plugins/sequence/src/path_formatting.rs
+++ /dev/null
@@ -1,71 +0,0 @@
-use std::path::PathBuf;
-
-/// Expand a path string to an absolute PathBuf
-/// Handles ~, ./, .., relative and absolute paths
-pub fn expand_path(path_str: &str, current_cwd: Option<&PathBuf>) -> Option<PathBuf> {
- let expanded = if path_str.starts_with("~/") || path_str == "~" {
- let home_dir = std::env::var("HOME").ok()?;
- if path_str == "~" {
- PathBuf::from(home_dir)
- } else {
- PathBuf::from(home_dir).join(&path_str[2..])
- }
- } else if path_str.starts_with('/') {
- PathBuf::from(path_str)
- } else {
- let base = current_cwd?;
- base.join(path_str)
- };
-
- let mut normalized = PathBuf::new();
- for component in expanded.components() {
- match component {
- std::path::Component::ParentDir => {
- normalized.pop();
- },
- std::path::Component::CurDir => {},
- _ => {
- normalized.push(component);
- },
- }
- }
-
- Some(normalized)
-}
-
-/// Format a path for display in the prompt
-/// Example: /home/user/some/long/path -> ~/s/l/path
-pub fn format_cwd(cwd: &PathBuf) -> String {
- let path_str = cwd.to_string_lossy().to_string();
-
- let home_dir = std::env::var("HOME").unwrap_or_default();
- let path_str = if !home_dir.is_empty() && path_str.starts_with(&home_dir) {
- path_str.replacen(&home_dir, "~", 1)
- } else {
- path_str
- };
-
- let parts: Vec<&str> = path_str.split('/').collect();
-
- if parts.len() <= 1 {
- return path_str;
- }
-
- let mut formatted_parts = Vec::new();
- for (i, part) in parts.iter().enumerate() {
- if i == parts.len() - 1 {
- formatted_parts.push(part.to_string());
- } else if part.is_empty() {
- formatted_parts.push(String::new());
- } else {
- formatted_parts.push(part.chars().next().unwrap_or_default().to_string());
- }
- }
-
- formatted_parts.join("/")
-}
-
-/// Alias for expand_path for backwards compatibility
-pub fn resolve_path(current_cwd: Option<&PathBuf>, path_str: &str) -> Option<PathBuf> {
- expand_path(path_str, current_cwd)
-}
diff --git a/default-plugins/sequence/src/state/chain_type.rs b/default-plugins/sequence/src/state/chain_type.rs
deleted file mode 100644
index 3da3316c..00000000
--- a/default-plugins/sequence/src/state/chain_type.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-use serde::{Deserialize, Serialize};
-use zellij_tile::prelude::*;
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
-pub enum ChainType {
- And,
- Or,
- Then,
- None,
-}
-
-impl Default for ChainType {
- fn default() -> Self {
- ChainType::None
- }
-}
-
-impl ChainType {
- pub fn to_unblock_condition(&self) -> Option<UnblockCondition> {
- match self {
- ChainType::And => Some(UnblockCondition::OnExitSuccess),
- ChainType::Or => Some(UnblockCondition::OnExitFailure),
- ChainType::Then => Some(UnblockCondition::OnAnyExit),
- ChainType::None => None,
- }
- }
-
- pub fn as_str(&self) -> &str {
- match self {
- ChainType::And => "&&",
- ChainType::Or => "||",
- ChainType::Then => ";",
- ChainType::None => "",
- }
- }
-
- pub fn cycle_next(&mut self) {
- *self = match self {
- ChainType::And => ChainType::Or,
- ChainType::Or => ChainType::Then,
- ChainType::Then => ChainType::And,
- ChainType::None => ChainType::And,
- };
- }
-}
diff --git a/default-plugins/sequence/src/state/command_entry.rs b/default-plugins/sequence/src/state/command_entry.rs
deleted file mode 100644
index 4ca9350a..00000000
--- a/default-plugins/sequence/src/state/command_entry.rs
+++ /dev/null
@@ -1,76 +0,0 @@
-use super::{ChainType, CommandStatus};
-use std::path::PathBuf;
-use zellij_tile::prelude::PaneId;
-
-#[derive(Debug, Clone)]
-pub struct CommandEntry {
- text: String,
- cwd: Option<PathBuf>,
- pub(super) chain_type: ChainType,
- pub(super) status: CommandStatus,
-}
-
-impl Default for CommandEntry {
- fn default() -> Self {
- CommandEntry {
- text: String::default(),
- cwd: None,
- chain_type: ChainType::default(),
- status: CommandStatus::default(),
- }
- }
-}
-
-impl CommandEntry {
- pub fn new(text: &str, cwd: Option<PathBuf>) -> Self {
- CommandEntry {
- text: text.to_owned(),
- cwd,
- ..Default::default()
- }
- }
- pub fn get_text(&self) -> String {
- self.text.clone()
- }
- pub fn set_text(&mut self, text: String) {
- self.text = text;
- }
- pub fn clear_text(&mut self) {
- self.text.clear();
- }
- pub fn get_chain_type(&self) -> ChainType {
- self.chain_type
- }
- pub fn set_chain_type(&mut self, chain_type: ChainType) {
- self.chain_type = chain_type;
- }
- pub fn get_status(&self) -> CommandStatus {
- self.status.clone()
- }
- pub fn set_status(&mut self, status: CommandStatus) {
- self.status = status;
- }
- pub fn get_pane_id(&self) -> Option<PaneId> {
- self.status.get_pane_id()
- }
- pub fn is_empty(&self) -> bool {
- self.text.trim().is_empty()
- }
- pub fn fill_chain_type_if_empty(&mut self) {
- if let ChainType::None = self.chain_type {
- self.chain_type = ChainType::And;
- }
- }
- pub fn clear_chain_type(&mut self) {
- self.chain_type = ChainType::None;
- }
- pub fn clear_status(&mut self) {
- self.status = CommandStatus::Pending;
- }
- pub fn get_cwd(&self) -> Option<PathBuf> {
- self.cwd.clone()
- }
- pub fn set_cwd(&mut self, cwd: Option<PathBuf>) {
- self.cwd = cwd;
- }
-}
diff --git a/default-plugins/sequence/src/state/command_parser.rs b/default-plugins/sequence/src/state/command_parser.rs
deleted file mode 100644
index 35935cd3..00000000
--- a/default-plugins/sequence/src/state/command_parser.rs
+++ /dev/null
@@ -1,370 +0,0 @@
-use super::{ChainType, CommandEntry};
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-enum ParseState {
- Normal,
- InSingleQuote,
- InDoubleQuote,
- Escaped(EscapeContext),
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-enum EscapeContext {
- Normal,
- DoubleQuote,
-}
-
-pub fn split_by_chain_operators(text: &str) -> Vec<(String, Option<ChainType>)> {
- let mut segments = Vec::new();
- let mut current_segment = String::new();
- let mut state = ParseState::Normal;
- let chars: Vec<char> = text.chars().collect();
- let mut i = 0;
-
- while i < chars.len() {
- let ch = chars[i];
-
- match state {
- ParseState::Normal => {
- if ch == '\\' {
- state = ParseState::Escaped(EscapeContext::Normal);
- current_segment.push(ch);
- i += 1;
- } else if ch == '\'' {
- state = ParseState::InSingleQuote;
- current_segment.push(ch);
- i += 1;
- } else if ch == '"' {
- state = ParseState::InDoubleQuote;
- current_segment.push(ch);
- i += 1;
- } else if ch == '&' && i + 1 < chars.len() && chars[i + 1] == '&' {
- let segment_text = current_segment.trim().to_string();
- if !segment_text.is_empty() {
- segments.push((segment_text, Some(ChainType::And)));
- }
- current_segment.clear();
- i += 2;
- } else if ch == '|' && i + 1 < chars.len() && chars[i + 1] == '|' {
- let segment_text = current_segment.trim().to_string();
- if !segment_text.is_empty() {
- segments.push((segment_text, Some(ChainType::Or)));
- }
- current_segment.clear();
- i += 2;
- } else if ch == ';' {
- let segment_text = current_segment.trim().to_string();
- if !segment_text.is_empty() {
- segments.push((segment_text, Some(ChainType::Then)));
- }
- current_segment.clear();
- i += 1;
- } else {
- current_segment.push(ch);
- i += 1;
- }
- },
- ParseState::InSingleQuote => {
- current_segment.push(ch);
- if ch == '\'' {
- state = ParseState::Normal;
- }
- i += 1;
- },
- ParseState::InDoubleQuote => {
- if ch == '\\' {
- state = ParseState::Escaped(EscapeContext::DoubleQuote);
- current_segment.push(ch);
- i += 1;
- } else if ch == '"' {
- current_segment.push(ch);
- state = ParseState::Normal;
- i += 1;
- } else {
- current_segment.push(ch);
- i += 1;
- }
- },
- ParseState::Escaped(context) => {
- current_segment.push(ch);
- state = match context {
- EscapeContext::Normal => ParseState::Normal,
- EscapeContext::DoubleQuote => ParseState::InDoubleQuote,
- };
- i += 1;
- },
- }
- }
-
- let final_segment = current_segment.trim().to_string();
- if !final_segment.is_empty() {
- segments.push((final_segment, None));
- }
-
- segments
-}
-
-pub fn detect_cd_command(text: &str) -> Option<String> {
- let trimmed = text.trim();
-
- if trimmed == "cd" {
- return Some("~".to_string());
- }
-
- if trimmed.starts_with("cd ") {
- let path = trimmed[3..].trim();
- return Some(path.to_string());
- }
-
- None
-}
-
-/// Serialize a sequence of commands to editor format.
-/// CWD changes are emitted as `cd <path>` lines; chain type as trailing ` &&`, ` ||`, ` ;`.
-pub fn serialize_sequence_to_editor(commands: &[CommandEntry]) -> String {
- let mut result = String::new();
-
- result.push_str("# One cmd per line. Use && || ; to chain. cd <path> sets cwd.\n");
- result.push_str("\n");
-
- let mut prev_cwd: Option<std::path::PathBuf> = None;
-
- for cmd in commands {
- if cmd.get_text().trim().is_empty() {
- continue;
- }
-
- let cmd_cwd = cmd.get_cwd();
- if cmd_cwd != prev_cwd && cmd_cwd.is_some() {
- let path = cmd_cwd.as_ref().unwrap();
- result.push_str(&format!("cd {}\n", path.display()));
- }
- prev_cwd = cmd_cwd;
-
- let op_suffix = match cmd.get_chain_type() {
- ChainType::And => " &&",
- ChainType::Or => " ||",
- ChainType::Then => " ;",
- ChainType::None => "",
- };
-
- result.push_str(&format!("{}{}\n", cmd.get_text(), op_suffix));
- }
-
- result
-}
-
-/// Parse command text into a Vec<CommandEntry>.
-/// `cd <path>` lines set the cwd for subsequent commands.
-/// Blank lines and `#` comment lines are ignored.
-pub fn parse_commands(
- contents: &str,
- initial_cwd: Option<std::path::PathBuf>,
-) -> Vec<CommandEntry> {
- use crate::path_formatting;
- let mut commands = Vec::new();
- let mut current_cwd = initial_cwd;
-
- for line in contents.lines() {
- let trimmed = line.trim();
- if trimmed.is_empty() || trimmed.starts_with('#') {
- continue;
- }
-
- // Split by chain operators first so that "cd /path &&" doesn't absorb the operator
- // into the path. Each resulting segment is then checked for cd independently.
- let segments = split_by_chain_operators(trimmed);
- for (text, chain_type_opt) in segments {
- let text = text.trim().to_string();
- if text.is_empty() {
- continue;
- }
- if let Some(path) = detect_cd_command(&text) {
- if let Some(new_cwd) = path_formatting::resolve_path(current_cwd.as_ref(), &path) {
- current_cwd = Some(new_cwd);
- }
- // cd segments are not added as command entries; their chain type is discarded
- } else {
- let mut entry = CommandEntry::new(&text, current_cwd.clone());
- if let Some(chain_type) = chain_type_opt {
- entry.set_chain_type(chain_type);
- }
- commands.push(entry);
- }
- }
- }
-
- // Commands with no explicit operator default to &&
- if !commands.is_empty() {
- let last_idx = commands.len() - 1;
- for cmd in &mut commands[..last_idx] {
- cmd.fill_chain_type_if_empty();
- }
- }
-
- commands
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_single_command_no_operator() {
- let result = split_by_chain_operators("ls");
- assert_eq!(result, vec![("ls".to_string(), None)]);
- }
-
- #[test]
- fn test_two_commands_with_and() {
- let result = split_by_chain_operators("ls && pwd");
- assert_eq!(
- result,
- vec![
- ("ls".to_string(), Some(ChainType::And)),
- ("pwd".to_string(), None)
- ]
- );
- }
-
- #[test]
- fn test_two_commands_with_or() {
- let result = split_by_chain_operators("cmd1 || cmd2");
- assert_eq!(
- result,
- vec![
- ("cmd1".to_string(), Some(ChainType::Or)),
- ("cmd2".to_string(), None)
- ]
- );
- }
-
- #[test]
- fn test_two_commands_with_semicolon() {
- let result = split_by_chain_operators("echo hi ; ls");
- assert_eq!(
- result,
- vec![
- ("echo hi".to_string(), Some(ChainType::Then)),
- ("ls".to_string(), None)
- ]
- );
- }
-
- #[test]
- fn test_three_commands_mixed() {
- let result = split_by_chain_operators("a && b || c");
- assert_eq!(
- result,
- vec![
- ("a".to_string(), Some(ChainType::And)),
- ("b".to_string(), Some(ChainType::Or)),
- ("c".to_string(), None)
- ]
- );
- }
-
- #[test]
- fn test_operator_in_single_quotes() {
- let result = split_by_chain_operators("echo '&&' && ls");
- assert_eq!(
- result,
- vec![
- ("echo '&&'".to_string(), Some(ChainType::And)),
- ("ls".to_string(), None)
- ]
- );
- }
-
- #[test]
- fn test_operator_in_double_quotes() {
- let result = split_by_chain_operators("echo \"||\" || pwd");
- assert_eq!(
- result,
- vec![
- ("echo \"||\"".to_string(), Some(ChainType::Or)),
- ("pwd".to_string(), None)
- ]
- );
- }
-
- #[test]
- fn test_escaped_operator() {
- let result = split_by_chain_operators("echo \\&& test");
- assert_eq!(result, vec![("echo \\&& test".to_string(), None)]);
- }
-
- #[test]
- fn test_complex_quoting() {
- let result = split_by_chain_operators("cmd1 && echo \"a || b\" && cmd2");
- assert_eq!(
- result,
- vec![
- ("cmd1".to_string(), Some(ChainType::And)),
- ("echo \"a || b\"".to_string(), Some(ChainType::And)),
- ("cmd2".to_string(), None)
- ]
- );
- }
-
- #[test]
- fn test_empty_segments_filtered() {
- let result = split_by_chain_operators("cmd1 && && cmd2");
- assert_eq!(
- result,
- vec![
- ("cmd1".to_string(), Some(ChainType::And)),
- ("cmd2".to_string(), None)
- ]
- );
- }
-
- #[test]
- fn test_whitespace_trimming() {
- let result = split_by_chain_operators(" cmd1 && cmd2 ");
- assert_eq!(
- result,
- vec![
- ("cmd1".to_string(), Some(ChainType::And)),
- ("cmd2".to_string(), None)
- ]
- );
- }
-
- #[test]
- fn test_trailing_operator_with_space() {
- let result = split_by_chain_operators("ls && ");
- assert_eq!(result, vec![("ls".to_string(), Some(ChainType::And))]);
- }
-
- #[test]
- fn test_editor_file_no_operator_defaults_to_and() {
- let contents = "cmd1\ncmd2\ncmd3";
- let result = parse_commands(contents, None);
- assert_eq!(result.len(), 3);
- assert_eq!(result[0].get_chain_type(), ChainType::And);
- assert_eq!(result[1].get_chain_type(), ChainType::And);
- assert_eq!(result[2].get_chain_type(), ChainType::None);
- }
-
- #[test]
- fn test_editor_file_explicit_operator_preserved() {
- let contents = "cmd1 ||\ncmd2";
- let result = parse_commands(contents, None);
- assert_eq!(result.len(), 2);
- assert_eq!(result[0].get_chain_type(), ChainType::Or);
- assert_eq!(result[1].get_chain_type(), ChainType::None);
- }
-
- #[test]
- fn test_cd_with_chain_operator_does_not_absorb_operator_into_path() {
- use super::super::CommandEntry;
- use std::path::PathBuf;
-
- let contents = "cd /some/path && cargo build";
- let result = parse_commands(contents, None);
- assert_eq!(result.len(), 1);
- assert_eq!(result[0].get_text(), "cargo build");
- assert_eq!(result[0].get_cwd(), Some(PathBuf::from("/some/path")));
- }
-}
diff --git a/default-plugins/sequence/src/state/command_status.rs b/default-plugins/sequence/src/state/command_status.rs
deleted file mode 100644
index 39717e8b..00000000
--- a/default-plugins/sequence/src/state/command_status.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-use zellij_tile::prelude::*;
-
-#[derive(Debug, Clone)]
-pub enum CommandStatus {
- Exited(Option<i32>, Option<PaneId>),
- Running(Option<PaneId>),
- Pending,
- Interrupted(Option<PaneId>),
-}
-
-impl CommandStatus {
- pub fn get_pane_id(&self) -> Option<PaneId> {
- match self {
- CommandStatus::Exited(_, pane_id) => *pane_id,
- CommandStatus::Running(pane_id) => *pane_id,
- CommandStatus::Pending => None,
- CommandStatus::Interrupted(pane_id) => *pane_id,
- }
- }
-}
-
-impl Default for CommandStatus {
- fn default() -> Self {
- CommandStatus::Pending
- }
-}
diff --git a/default-plugins/sequence/src/state/execution.rs b/default-plugins/sequence/src/state/execution.rs
deleted file mode 100644
index 42d4112a..00000000
--- a/default-plugins/sequence/src/state/execution.rs
+++ /dev/null
@@ -1,87 +0,0 @@
-use crate::path_formatting::format_cwd;
-use crate::state::{CommandEntry, CommandStatus};
-use std::path::PathBuf;
-use zellij_tile::prelude::*;
-
-pub struct Execution {
- pub all_commands: Vec<CommandEntry>,
- pub current_running_command_index: usize,
- pub is_running: bool,
- pub displayed_pane_id: Option<PaneId>,
-}
-
-impl Execution {
- pub fn new() -> Self {
- Self {
- all_commands: vec![CommandEntry::default()],
- current_running_command_index: 0,
- is_running: false,
- displayed_pane_id: None,
- }
- }
-
- pub fn longest_cwd_display(&self, global_cwd: &Option<PathBuf>) -> String {
- self.all_commands
- .iter()
- .map(|cmd| {
- let cwd = cmd.get_cwd().or_else(|| global_cwd.clone());
- if let Some(cwd) = &cwd {
- format_cwd(cwd)
- } else {
- "~".to_string()
- }
- })
- .max_by_key(|s| s.len())
- .unwrap_or_else(|| "~".to_string())
- }
-
- pub fn remove_empty_commands(&mut self) {
- if self.all_commands.iter().len() > 1 {
- self.all_commands.retain(|c| !c.get_text().is_empty());
- }
- }
-
- pub fn get_first_command(&self) -> Option<CommandEntry> {
- self.all_commands.iter().next().cloned()
- }
-
- pub fn set_command_status(&mut self, command_index: usize, status: CommandStatus) {
- self.all_commands
- .get_mut(command_index)
- .map(|c| c.set_status(status));
- }
-
- pub fn set_current_running_command_status(&mut self, status: CommandStatus) {
- self.all_commands
- .get_mut(self.current_running_command_index)
- .map(|c| c.set_status(status));
- }
-
- pub fn get_current_running_command_status(&self) -> Option<CommandStatus> {
- self.all_commands
- .get(self.current_running_command_index)
- .map(|c| c.get_status())
- }
-
- pub fn can_run_sequence(&self) -> bool {
- self.all_commands.iter().any(|command| !command.is_empty())
- }
-
- pub fn copy_to_clipboard(&self) {
- let text_to_copy = self
- .all_commands
- .iter()
- .map(|c| format!("{}", c.get_text()))
- .collect::<Vec<_>>()
- .join("\n");
- if !text_to_copy.is_empty() {
- copy_to_clipboard(text_to_copy);
- }
- }
-}
-
-impl Default for Execution {
- fn default() -> Self {
- Self::new()
- }
-}
diff --git a/default-plugins/sequence/src/state/layout.rs b/default-plugins/sequence/src/state/layout.rs
deleted file mode 100644
index 6b070778..00000000
--- a/default-plugins/sequence/src/state/layout.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-pub struct Layout {
- pub spinner_frame: usize,
- pub spinner_timer_scheduled: bool,
-}
-
-impl Layout {
- pub fn new() -> Self {
- Self {
- spinner_frame: 0,
- spinner_timer_scheduled: false,
- }
- }
-}
-
-impl Default for Layout {
- fn default() -> Self {
- Self::new()
- }
-}
diff --git a/default-plugins/sequence/src/state/mod.rs b/default-plugins/sequence/src/state/mod.rs
deleted file mode 100644
index 148e6702..00000000
--- a/default-plugins/sequence/src/state/mod.rs
+++ /dev/null
@@ -1,231 +0,0 @@
-mod chain_type;
-mod command_entry;
-mod command_parser;
-mod command_status;
-mod execution;
-mod layout;
-mod selection;
-mod sequence_mode;
-
-pub use chain_type::ChainType;
-pub use command_entry::CommandEntry;
-pub use command_parser::{parse_commands, serialize_sequence_to_editor};
-pub use command_status::CommandStatus;
-pub use execution::Execution;
-pub use layout::Layout;
-pub use selection::Selection;
-pub use sequence_mode::SequenceMode;
-
-use std::collections::BTreeMap;
-use std::path::PathBuf;
-use zellij_tile::prelude::*;
-
-#[derive(Default)]
-pub struct State {
- pub shell: Option<PathBuf>,
- pub plugin_id: Option<u32>,
- pub client_id: Option<u16>,
- pub own_rows: Option<usize>,
- pub own_columns: Option<usize>,
- pub cwd: Option<PathBuf>,
- pub total_viewport_columns: Option<usize>,
- pub total_viewport_rows: Option<usize>,
- pub selection: Selection,
- pub execution: Execution,
- pub layout: Layout,
- pub current_position: Option<FloatingPaneCoordinates>,
- pub mode: SequenceMode,
- pub editor_pane_id: Option<PaneId>,
- pub editor_temp_file: Option<PathBuf>,
- pub sequence_tab_id: Option<usize>,
- pub blocking_pipe_id: Option<String>,
-}
-
-impl State {
- pub fn set_plugin_id(&mut self, plugin_id: u32) {
- self.plugin_id = Some(plugin_id);
- }
-
- pub fn move_selection_up(&mut self) {
- self.selection.move_up(&self.execution.all_commands);
- self.show_selected_pane();
- }
-
- pub fn move_selection_down(&mut self) {
- self.selection.move_down(&self.execution.all_commands);
- self.show_selected_pane();
- }
-
- pub fn show_selected_pane(&mut self) {
- if self.mode == SequenceMode::Spread {
- let all_pane_ids: Vec<PaneId> = self
- .execution
- .all_commands
- .iter()
- .filter_map(|c| c.get_pane_id())
- .collect();
- let selected_pane_id = self
- .current_selected_command()
- .and_then(|c| c.get_pane_id());
- if let Some(selected) = selected_pane_id {
- let to_unhighlight: Vec<PaneId> = all_pane_ids
- .into_iter()
- .filter(|&p| p != selected)
- .collect();
- highlight_and_unhighlight_panes(vec![selected], to_unhighlight);
- }
- return;
- }
- let target_pane_id = self
- .current_selected_command()
- .and_then(|c| c.get_pane_id());
- if let (Some(displayed), Some(target)) = (self.execution.displayed_pane_id, target_pane_id)
- {
- if displayed != target {
- replace_pane_with_existing_pane(displayed, target, true);
- self.execution.displayed_pane_id = Some(target);
- }
- }
- }
-
- pub fn remove_empty_commands(&mut self) {
- self.execution.remove_empty_commands();
- }
-
- pub fn clear_all_commands(&mut self) {
- self.execution.all_commands = vec![CommandEntry::new("", self.cwd.clone())];
- self.selection.current_selected_command_index = None;
- self.execution.current_running_command_index = 0;
- }
-
- pub fn load_commands(&mut self, command_string: &str, cwd_override: Option<PathBuf>) {
- let effective_cwd = cwd_override.or_else(|| self.cwd.clone());
- let commands = parse_commands(command_string, effective_cwd);
- if commands.is_empty() {
- return;
- }
- self.execution.all_commands = commands;
- self.execution.current_running_command_index = 0;
- self.selection.current_selected_command_index = None;
- }
-
- pub fn can_run_sequence(&self) -> bool {
- self.execution.can_run_sequence()
- }
-
- pub fn copy_to_clipboard(&mut self) {
- self.execution.copy_to_clipboard();
- }
-
- pub fn current_selected_command_mut(&mut self) -> Option<&mut CommandEntry> {
- let Some(i) = self.selection.current_selected_command_index else {
- return None;
- };
- self.execution.all_commands.get_mut(i)
- }
-
- /// Serialize current sequence to a temp file and open it in the user's $EDITOR.
- /// Stores the editor pane id and temp file path for later retrieval.
- pub fn open_editor(&mut self) {
- if self.execution.is_running {
- return;
- }
- let Some(plugin_id) = self.plugin_id else {
- return;
- };
-
- let serialized = serialize_sequence_to_editor(&self.execution.all_commands);
- let temp_path = PathBuf::from(format!("/tmp/zellij-sequence-{}.sh", plugin_id));
-
- if let Err(e) = std::fs::write(&temp_path, &serialized) {
- eprintln!("Failed to write sequence to temp file: {}", e);
- return;
- }
-
- let file_to_open = FileToOpen::new(temp_path.clone());
- let editor_pane_id = open_file_in_place_of_plugin(file_to_open, false, BTreeMap::new());
- self.editor_pane_id = editor_pane_id;
- self.editor_temp_file = Some(temp_path);
- }
-
- pub fn reposition_plugin(&mut self) -> bool {
- let mut repositioned = false;
- let Some(plugin_id) = self.plugin_id else {
- return repositioned;
- };
- let Some(total_viewport_rows) = self.total_viewport_rows else {
- return repositioned;
- };
- let Some(total_viewport_columns) = self.total_viewport_columns else {
- return repositioned;
- };
- if self.all_commands_are_pending() {
- return false;
- }
-
- let total_commands = std::cmp::max(self.execution.all_commands.iter().len(), 1);
- let height_padding = 7;
-
- // Calculate longest table row width
- let longest_cwd_display = self.execution.longest_cwd_display(&self.cwd);
- let longest_command = self
- .execution
- .all_commands
- .iter()
- .map(|cmd| cmd.get_text().chars().count())
- .max()
- .unwrap_or(1);
-
- let (max_chain_width, max_status_width) = crate::ui::components::calculate_max_widths(
- &self.execution.all_commands,
- self.layout.spinner_frame,
- );
-
- let longest_line = crate::ui::components::calculate_longest_line(
- &longest_cwd_display,
- longest_command,
- max_chain_width,
- max_status_width,
- );
-
- let ui_width = std::cmp::max(longest_line, 50) + 4; // 2 for ui-padding, 2 for pane frame
- let width = std::cmp::min(ui_width, total_viewport_columns.saturating_sub(2));
-
- let height = (height_padding + total_commands).min((total_viewport_rows * 50) / 100);
-
- // Position: top-right with 1-space margins
- let x = total_viewport_columns.saturating_sub(width);
- let y = 1;
-
- let coordinates = FloatingPaneCoordinates::new(
- Some(x.to_string()),
- Some(y.to_string()),
- Some(width.to_string()),
- Some(height.to_string()),
- Some(true), // should be pinned for sequence
- Some(false),
- );
- coordinates.map(|coordinates| {
- if Some(&coordinates) != self.current_position.as_ref() {
- self.current_position = Some(coordinates.clone());
- repositioned = true;
- change_floating_panes_coordinates(vec![(PaneId::Plugin(plugin_id), coordinates)]);
- }
- });
- repositioned
- }
-
- pub fn all_commands_are_pending(&self) -> bool {
- self.execution
- .all_commands
- .iter()
- .all(|command| matches!(command.get_status(), CommandStatus::Pending))
- }
-
- fn current_selected_command(&self) -> Option<&CommandEntry> {
- let Some(i) = self.selection.current_selected_command_index else {
- return None;
- };
- self.execution.all_commands.get(i)
- }
-}
diff --git a/default-plugins/sequence/src/state/selection.rs b/default-plugins/sequence/src/state/selection.rs
deleted file mode 100644
index 2137f47d..00000000
--- a/default-plugins/sequence/src/state/selection.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-use super::CommandEntry;
-
-pub struct Selection {
- pub current_selected_command_index: Option<usize>,
-}
-
-impl Selection {
- pub fn new() -> Self {
- Self {
- current_selected_command_index: None,
- }
- }
-
- pub fn move_up(&mut self, all_commands: &[CommandEntry]) {
- match self.current_selected_command_index.as_mut() {
- Some(i) if *i > 0 => {
- *i -= 1;
- },
- None => {
- self.current_selected_command_index = Some(all_commands.len().saturating_sub(1));
- },
- _ => {
- self.current_selected_command_index = None;
- },
- }
- }
-
- pub fn move_down(&mut self, all_commands: &[CommandEntry]) {
- match self.current_selected_command_index.as_mut() {
- Some(i) if *i < all_commands.len().saturating_sub(1) => {
- *i += 1;
- },
- None => {
- self.current_selected_command_index = Some(0);
- },
- _ => {
- self.current_selected_command_index = None;
- },
- }
- }
-}
-
-impl Default for Selection {
- fn default() -> Self {
- Self::new()
- }
-}
diff --git a/default-plugins/sequence/src/state/sequence_mode.rs b/default-plugins/sequence/src/state/sequence_mode.rs
deleted file mode 100644
index 3d079d70..00000000
--- a/default-plugins/sequence/src/state/sequence_mode.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-#[derive(Default, Debug, Clone, PartialEq)]
-pub enum SequenceMode {
- #[default]
- SinglePane,
- Spread,
-}
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)
-}
diff --git a/default-plugins/sequence/src/ui/layout_calculations.rs b/default-plugins/sequence/src/ui/layout_calculations.rs
deleted file mode 100644
index 911dbc62..00000000
--- a/default-plugins/sequence/src/ui/layout_calculations.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-pub fn calculate_viewport(
- total_commands: usize,
- max_visible: usize,
- selected_index: Option<usize>,
- running_index: usize,
-) -> (usize, usize, usize, usize) {
- if total_commands <= max_visible {
- return (0, total_commands, 0, 0);
- }
-
- let focus_index = selected_index.unwrap_or(running_index);
- let half_visible = max_visible / 2;
-
- let offset = if focus_index < half_visible {
- 0
- } else if focus_index >= total_commands.saturating_sub(half_visible) {
- total_commands.saturating_sub(max_visible)
- } else {
- focus_index.saturating_sub(half_visible)
- };
-
- let offset = offset.min(total_commands.saturating_sub(max_visible));
- let visible_count = max_visible.min(total_commands.saturating_sub(offset));
- let hidden_above = offset;
- let hidden_below = total_commands.saturating_sub(offset + visible_count);
-
- (offset, visible_count, hidden_above, hidden_below)
-}
diff --git a/default-plugins/sequence/src/ui/mod.rs b/default-plugins/sequence/src/ui/mod.rs
deleted file mode 100644
index 7255fa35..00000000
--- a/default-plugins/sequence/src/ui/mod.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-pub mod components;
-pub mod layout_calculations;
-pub mod truncation;
diff --git a/default-plugins/sequence/src/ui/truncation.rs b/default-plugins/sequence/src/ui/truncation.rs
deleted file mode 100644
index 0c4073a3..00000000
--- a/default-plugins/sequence/src/ui/truncation.rs
+++ /dev/null
@@ -1,373 +0,0 @@
-use unicode_width::UnicodeWidthStr;
-
-const BASE_SEPARATOR_WIDTH: usize = 2;
-
-pub fn calculate_available_cmd_width(
- cols: usize,
- folder_width: usize,
- overflow_indicator: Option<&String>,
- chain_width: usize,
- status_width: usize,
-) -> usize {
- let mut separator_width = BASE_SEPARATOR_WIDTH;
- if overflow_indicator.is_some() {
- separator_width += 1;
- }
- if status_width > 0 {
- separator_width += 1;
- }
-
- let overflow_width = overflow_indicator.map(|s| s.chars().count()).unwrap_or(0);
-
- cols.saturating_sub(folder_width)
- .saturating_sub(chain_width)
- .saturating_sub(status_width)
- .saturating_sub(separator_width)
- .saturating_sub(overflow_width)
- .saturating_sub(2)
- .max(1)
-}
-
-pub fn truncate_middle(
- text: &str,
- max_width: usize,
- cursor_position: Option<usize>,
-) -> (String, Option<usize>) {
- let text_width = text.width();
-
- if text_width <= max_width {
- return (text.to_string(), cursor_position);
- }
-
- if max_width < 5 {
- return truncate_minimal(text, max_width, cursor_position);
- }
-
- if let Some(cursor_char_idx) = cursor_position {
- return truncate_with_cursor(text, max_width, cursor_char_idx);
- }
-
- truncate_no_cursor(text, max_width)
-}
-
-fn truncate_minimal(
- text: &str,
- max_width: usize,
- cursor_position: Option<usize>,
-) -> (String, Option<usize>) {
- let mut result = String::new();
- let mut current_width = 0;
- let mut new_cursor_pos = None;
- let mut char_pos = 0;
-
- for ch in text.chars() {
- let ch_width = ch.to_string().width();
- if current_width + ch_width <= max_width {
- result.push(ch);
- if let Some(cursor_char) = cursor_position {
- if char_pos == cursor_char {
- new_cursor_pos = Some(char_pos);
- }
- }
- char_pos += 1;
- current_width += ch_width;
- } else {
- break;
- }
- }
- (result, new_cursor_pos)
-}
-
-fn truncate_no_cursor(text: &str, max_width: usize) -> (String, Option<usize>) {
- let available_for_text = max_width.saturating_sub(3);
- let left_width = available_for_text / 2;
- let right_width = available_for_text - left_width;
-
- let mut left_part = String::new();
- let mut current_width = 0;
- for ch in text.chars() {
- let ch_width = ch.to_string().width();
- if current_width + ch_width <= left_width {
- left_part.push(ch);
- current_width += ch_width;
- } else {
- break;
- }
- }
-
- let chars: Vec<char> = text.chars().collect();
- let mut right_part = String::new();
- let mut current_width = 0;
- for ch in chars.iter().rev() {
- let ch_width = ch.to_string().width();
- if current_width + ch_width <= right_width {
- right_part.insert(0, *ch);
- current_width += ch_width;
- } else {
- break;
- }
- }
-
- (format!("{}...{}", left_part, right_part), None)
-}
-
-fn truncate_with_cursor(
- text: &str,
- max_width: usize,
- cursor_char_idx: usize,
-) -> (String, Option<usize>) {
- let chars: Vec<char> = text.chars().collect();
- let char_widths: Vec<usize> = chars.iter().map(|ch| ch.to_string().width()).collect();
-
- let width_before_cursor: usize = char_widths[..cursor_char_idx].iter().sum();
- let width_after_cursor: usize = char_widths[cursor_char_idx..].iter().sum();
- let available_one_ellipsis = max_width.saturating_sub(3);
-
- let (start_idx, end_idx) = if width_before_cursor <= available_one_ellipsis {
- calculate_end_truncation(&chars, &char_widths, available_one_ellipsis)
- } else if width_after_cursor <= available_one_ellipsis {
- calculate_start_truncation(&chars, &char_widths, available_one_ellipsis)
- } else {
- calculate_middle_truncation(&char_widths, max_width, cursor_char_idx)
- };
-
- let (start_idx, end_idx) =
- adjust_small_truncations(start_idx, end_idx, &char_widths, cursor_char_idx, &chars);
- let (start_idx, end_idx) =
- trim_excess(&char_widths, start_idx, end_idx, max_width, cursor_char_idx);
-
- build_truncated_result(&chars, start_idx, end_idx, cursor_char_idx)
-}
-
-fn calculate_end_truncation(
- chars: &[char],
- char_widths: &[usize],
- available: usize,
-) -> (usize, usize) {
- let mut end_idx = 0;
- let mut width = 0;
- while end_idx < chars.len() && width + char_widths[end_idx] <= available {
- width += char_widths[end_idx];
- end_idx += 1;
- }
- (0, end_idx)
-}
-
-fn calculate_start_truncation(
- chars: &[char],
- char_widths: &[usize],
- available: usize,
-) -> (usize, usize) {
- let mut start_idx = chars.len();
- let mut width = 0;
- while start_idx > 0 && width + char_widths[start_idx - 1] <= available {
- start_idx -= 1;
- width += char_widths[start_idx];
- }
- (start_idx, chars.len())
-}
-
-fn calculate_middle_truncation(
- char_widths: &[usize],
- max_width: usize,
- cursor_char_idx: usize,
-) -> (usize, usize) {
- let available_both_ellipsis = max_width.saturating_sub(6);
- let target_before = available_both_ellipsis / 2;
- let target_after = available_both_ellipsis - target_before;
-
- let mut start_idx = cursor_char_idx;
- let mut width_before = 0;
- while start_idx > 0 && width_before + char_widths[start_idx - 1] <= target_before {
- start_idx -= 1;
- width_before += char_widths[start_idx];
- }
-
- let mut end_idx = cursor_char_idx;
- let mut width_after = 0;
- while end_idx < char_widths.len() && width_after + char_widths[end_idx] <= target_after {
- width_after += char_widths[end_idx];
- end_idx += 1;
- }
-
- let leftover_before = target_before.saturating_sub(width_before);
- let leftover_after = target_after.saturating_sub(width_after);
-
- if leftover_before > 0 {
- let mut extra = leftover_before;
- while end_idx < char_widths.len() && char_widths[end_idx] <= extra {
- extra -= char_widths[end_idx];
- end_idx += 1;
- }
- }
- if leftover_after > 0 {
- let mut extra = leftover_after;
- while start_idx > 0 && char_widths[start_idx - 1] <= extra {
- start_idx -= 1;
- extra -= char_widths[start_idx];
- }
- }
-
- (start_idx, end_idx)
-}
-
-fn adjust_small_truncations(
- start_idx: usize,
- end_idx: usize,
- char_widths: &[usize],
- cursor_char_idx: usize,
- chars: &[char],
-) -> (usize, usize) {
- let width_truncated_start: usize = char_widths[..start_idx].iter().sum();
- let width_truncated_end: usize = char_widths[end_idx..].iter().sum();
-
- let mut start_idx = start_idx;
- let mut end_idx = end_idx;
-
- if width_truncated_start > 0 && width_truncated_start < 3 && end_idx < chars.len() {
- let gained = width_truncated_start;
- start_idx = 0;
- let mut removed = 0;
- while end_idx > cursor_char_idx + 1 && removed < gained {
- end_idx -= 1;
- removed += char_widths[end_idx];
- }
- } else if width_truncated_end > 0 && width_truncated_end < 3 && start_idx > 0 {
- let gained = width_truncated_end;
- end_idx = chars.len();
- let mut removed = 0;
- while start_idx < cursor_char_idx && removed < gained {
- removed += char_widths[start_idx];
- start_idx += 1;
- }
- }
-
- (start_idx, end_idx)
-}
-
-fn trim_excess(
- char_widths: &[usize],
- start_idx: usize,
- end_idx: usize,
- max_width: usize,
- cursor_char_idx: usize,
-) -> (usize, usize) {
- let truncate_start = start_idx > 0;
- let truncate_end = end_idx < char_widths.len();
- let ellipsis_width = match (truncate_start, truncate_end) {
- (true, true) => 6,
- (true, false) | (false, true) => 3,
- (false, false) => 0,
- };
-
- let visible_width: usize = char_widths[start_idx..end_idx].iter().sum();
- let mut start_idx = start_idx;
- let mut end_idx = end_idx;
-
- if visible_width + ellipsis_width > max_width {
- let mut excess = visible_width + ellipsis_width - max_width;
- if truncate_end {
- while excess > 0 && end_idx > cursor_char_idx + 1 {
- end_idx -= 1;
- excess = excess.saturating_sub(char_widths[end_idx]);
- }
- }
- if excess > 0 && truncate_start {
- while excess > 0 && start_idx < cursor_char_idx {
- excess = excess.saturating_sub(char_widths[start_idx]);
- start_idx += 1;
- }
- }
- }
-
- (start_idx, end_idx)
-}
-
-fn build_truncated_result(
- chars: &[char],
- start_idx: usize,
- end_idx: usize,
- cursor_char_idx: usize,
-) -> (String, Option<usize>) {
- let mut result = String::new();
- let mut new_cursor_char_pos = 0;
- let mut current_char_pos = 0;
-
- if start_idx > 0 {
- result.push_str("...");
- current_char_pos = 3;
- }
-
- for i in start_idx..end_idx {
- if i == cursor_char_idx {
- new_cursor_char_pos = current_char_pos;
- }
- result.push(chars[i]);
- current_char_pos += 1;
- }
-
- if cursor_char_idx >= end_idx {
- new_cursor_char_pos = current_char_pos;
- }
-
- if end_idx < chars.len() {
- result.push_str("...");
- }
-
- (result, Some(new_cursor_char_pos))
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_truncate_middle_no_truncation_needed() {
- let text = "hello world";
- let (result, cursor_pos) = truncate_middle(text, 20, Some(6));
- assert_eq!(result, "hello world");
- assert_eq!(cursor_pos, Some(6));
- }
-
- #[test]
- fn test_truncate_middle_no_cursor() {
- let text = "this is a very long string that needs truncation";
- let (result, cursor_pos) = truncate_middle(text, 20, None);
- assert!(result.contains("..."));
- assert_eq!(cursor_pos, None);
- }
-
- #[test]
- fn test_truncate_middle_cursor_at_start() {
- let text = "this is a very long string that needs truncation";
- let (result, cursor_pos) = truncate_middle(text, 20, Some(0));
- assert!(result.starts_with("this"));
- assert!(result.ends_with("..."));
- assert_eq!(cursor_pos, Some(0));
- }
-
- #[test]
- fn test_truncate_middle_cursor_at_end() {
- let text = "this is a very long string that needs truncation";
- let cursor_char = text.chars().count();
- let (result, _cursor_pos) = truncate_middle(text, 20, Some(cursor_char));
- assert!(result.starts_with("..."));
- assert!(result.ends_with("truncation"));
- }
-
- #[test]
- fn test_truncate_middle_cursor_in_middle() {
- let text = "this is a very long string that needs truncation";
- let cursor_char = "this is a very ".chars().count();
- let (result, cursor_pos) = truncate_middle(text, 20, Some(cursor_char));
- assert!(result.contains("very"));
- assert!(cursor_pos.is_some());
- }
-
- #[test]
- fn test_truncate_middle_wide_chars() {
- let text = "こんにちは世界";
- let (result, _) = truncate_middle(text, 10, None);
- assert!(result.width() <= 10);
- }
-}
diff --git a/example/default.kdl b/example/default.kdl
index 59e70eb7..ebe7f29c 100644
--- a/example/default.kdl
+++ b/example/default.kdl
@@ -242,7 +242,6 @@ plugins {
configuration location="zellij:configuration"
plugin-manager location="zellij:plugin-manager"
about location="zellij:about"
- sequence location="zellij:sequence"
}
// Plugins to load in the background when a new session starts
diff --git a/src/main.rs b/src/main.rs
index 48fcf67e..f7fe74e6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -216,33 +216,6 @@ fn main() {
commands::send_action_to_session(command_cli_action, opts.session, config);
std::process::exit(0);
}
- if let Some(Command::Sessions(Sessions::Sequence { payload, blocking })) = opts.command {
- let args = if blocking {
- let mut args: std::collections::BTreeMap<String, String> =
- std::collections::BTreeMap::new();
- args.insert("blocking".to_string(), "true".to_string());
- Some(zellij_utils::input::layout::PluginUserConfiguration::new(
- args,
- ))
- } else {
- None
- };
- let command_cli_action = CliAction::Pipe {
- name: None,
- payload,
- args,
- plugin: Some("zellij:sequence".to_string()),
- plugin_configuration: None,
- force_launch_plugin: true,
- skip_plugin_cache: false,
- floating_plugin: None,
- in_place_plugin: None,
- plugin_cwd: None,
- plugin_title: None,
- };
- commands::send_action_to_session(command_cli_action, opts.session, config);
- std::process::exit(0);
- }
}
if let Some(Command::Sessions(Sessions::ListSessions {
diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs
index 96fb9c12..4def0981 100644
--- a/src/tests/e2e/cases.rs
+++ b/src/tests/e2e/cases.rs
@@ -1,6 +1,6 @@
#![allow(unused)]
-use ::insta::assert_snapshot;
+use insta::assert_snapshot;
use zellij_utils::{
pane_size::Size,
position::{Column, Line, Position},
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index 96a50e3f..d78d9433 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -79,10 +79,6 @@ fn workspace_members() -> &'static Vec<WorkspaceMember> {
build: true,
},
WorkspaceMember {
- crate_name: "default-plugins/sequence",
- build: true,
- },
- WorkspaceMember {
crate_name: "default-plugins/layout-manager",
build: true,
},
diff --git a/zellij-utils/assets/config/default.kdl b/zellij-utils/assets/config/default.kdl
index 50a68e07..8c6cb627 100644
--- a/zellij-utils/assets/config/default.kdl
+++ b/zellij-utils/assets/config/default.kdl
@@ -150,12 +150,6 @@ keybinds {
};
SwitchToMode "Normal"
}
- bind "q" {
- LaunchPlugin "zellij:sequence" {
- floating true
- };
- SwitchToMode "Normal"
- }
bind "l" {
LaunchOrFocusPlugin "zellij:layout-manager" {
floating true
@@ -248,7 +242,6 @@ plugins {
configuration location="zellij:configuration"
plugin-manager location="zellij:plugin-manager"
about location="zellij:about"
- sequence location="zellij:sequence"
}
// Plugins to load in the background when a new session starts
diff --git a/zellij-utils/assets/plugins/sequence.wasm b/zellij-utils/assets/plugins/sequence.wasm
deleted file mode 100755
index b2e470a1..00000000
--- a/zellij-utils/assets/plugins/sequence.wasm
+++ /dev/null
Binary files differ
diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs
index afe198b8..7b64e147 100644
--- a/zellij-utils/src/cli.rs
+++ b/zellij-utils/src/cli.rs
@@ -630,34 +630,6 @@ tail -f /tmp/my-live-logfile | zellij pipe --name logs --plugin https://example.
#[clap(short('c'), long, value_parser, display_order(4))]
plugin_configuration: Option<PluginUserConfiguration>,
},
- /// Send commands to the sequence plugin (sugar for: zellij pipe --plugin zellij:sequence)
- #[clap(
- visible_alias = "seq",
- override_usage(
- r#"
-zellij sequence [OPTIONS] [--] <COMMANDS>
-
-* Run a sequence of commands:
-
-zellij sequence -- 'echo hello && echo world'
-
-* Pipe commands from STDIN:
-
-echo 'echo hello && echo world' | zellij sequence
-
-* Wait for the sequence to complete before returning:
-
-zellij sequence --blocking -- 'echo hello && echo world'
-"#
- )
- )]
- Sequence {
- /// The commands to run (if blank, will listen to STDIN)
- payload: Option<String>,
- /// Block until the sequence finishes before exiting
- #[clap(short, long, value_parser, takes_value(false), default_value("false"))]
- blocking: bool,
- },
}
#[derive(Debug, Subcommand, Clone, Serialize, Deserialize)]
diff --git a/zellij-utils/src/consts.rs b/zellij-utils/src/consts.rs
index ef28b9a9..12cc5c2a 100644
--- a/zellij-utils/src/consts.rs
+++ b/zellij-utils/src/consts.rs
@@ -138,7 +138,6 @@ mod not_wasm {
add_plugin!(assets, "about.wasm");
add_plugin!(assets, "share.wasm");
add_plugin!(assets, "multiple-select.wasm");
- add_plugin!(assets, "sequence.wasm");
add_plugin!(assets, "layout-manager.wasm");
assets
};
diff --git a/zellij-utils/src/input/plugins.rs b/zellij-utils/src/input/plugins.rs
index f2e2c1e6..69af52f9 100644
--- a/zellij-utils/src/input/plugins.rs
+++ b/zellij-utils/src/input/plugins.rs
@@ -67,7 +67,6 @@ impl PluginConfig {
|| tag == "about"
|| tag == "share"
|| tag == "multiple-select"
- || tag == "sequence"
|| tag == "layout-manager"
{
Some(PluginConfig {
diff --git a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap
index 9fa94e0b..bc5feeff 100644
--- a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap
+++ b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap
@@ -129,12 +129,6 @@ keybinds clear-defaults=true {
}
SwitchToMode "normal"
}
- bind "q" {
- LaunchPlugin "zellij:sequence" {
- floating true
- }
- SwitchToMode "normal"
- }
bind "s" {
LaunchOrFocusPlugin "zellij:share" {
floating true
@@ -268,7 +262,6 @@ plugins {
cwd "/"
}
plugin-manager location="zellij:plugin-manager"
- sequence location="zellij:sequence"
session-manager location="zellij:session-manager"
status-bar location="zellij:status-bar"
strider location="zellij:strider"
diff --git a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap
index 3d9cd208..26a1be67 100644
--- a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap
+++ b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap
@@ -129,12 +129,6 @@ keybinds clear-defaults=true {
}
SwitchToMode "normal"
}
- bind "q" {
- LaunchPlugin "zellij:sequence" {
- floating true
- }
- SwitchToMode "normal"
- }
bind "s" {
LaunchOrFocusPlugin "zellij:share" {
floating true
@@ -271,7 +265,6 @@ plugins {
cwd "/"
}
plugin-manager location="zellij:plugin-manager"
- sequence location="zellij:sequence"
session-manager location="zellij:session-manager"
status-bar location="zellij:status-bar"
strider location="zellij:strider"
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap
index 4bfd7dc2..d794c05f 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap
@@ -4483,37 +4483,6 @@ Config {
bare_key: Char(
'q',
),
- key_modifiers: {},
- }: [
- LaunchPlugin {
- plugin: RunPlugin(
- RunPlugin {
- _allow_exec_host_cmd: false,
- location: Zellij(
- PluginTag(
- "sequence",
- ),
- ),
- configuration: PluginUserConfiguration(
- {},
- ),
- initial_cwd: None,
- },
- ),
- should_float: true,
- should_open_in_place: false,
- close_replaced_pane: false,
- skip_cache: false,
- cwd: None,
- },
- SwitchToMode {
- input_mode: Normal,
- },
- ],
- KeyWithModifier {
- bare_key: Char(
- 'q',
- ),
key_modifiers: {
Ctrl,
},
@@ -6111,18 +6080,6 @@ Config {
),
initial_cwd: None,
},
- "sequence": RunPlugin {
- _allow_exec_host_cmd: false,
- location: Zellij(
- PluginTag(
- "sequence",
- ),
- ),
- configuration: PluginUserConfiguration(
- {},
- ),
- initial_cwd: None,
- },
"session-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap
index 9b47705b..4151c52a 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap
@@ -4483,37 +4483,6 @@ Config {
bare_key: Char(
'q',
),
- key_modifiers: {},
- }: [
- LaunchPlugin {
- plugin: RunPlugin(
- RunPlugin {
- _allow_exec_host_cmd: false,
- location: Zellij(
- PluginTag(
- "sequence",
- ),
- ),
- configuration: PluginUserConfiguration(
- {},
- ),
- initial_cwd: None,
- },
- ),
- should_float: true,
- should_open_in_place: false,
- close_replaced_pane: false,
- skip_cache: false,
- cwd: None,
- },
- SwitchToMode {
- input_mode: Normal,
- },
- ],
- KeyWithModifier {
- bare_key: Char(
- 'q',
- ),
key_modifiers: {
Ctrl,
},
@@ -6111,18 +6080,6 @@ Config {
),
initial_cwd: None,
},
- "sequence": RunPlugin {
- _allow_exec_host_cmd: false,
- location: Zellij(
- PluginTag(
- "sequence",
- ),
- ),
- configuration: PluginUserConfiguration(
- {},
- ),
- initial_cwd: None,
- },
"session-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap
index b9be4e99..eb5db32b 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap
@@ -198,18 +198,6 @@ Config {
),
initial_cwd: None,
},
- "sequence": RunPlugin {
- _allow_exec_host_cmd: false,
- location: Zellij(
- PluginTag(
- "sequence",
- ),
- ),
- configuration: PluginUserConfiguration(
- {},
- ),
- initial_cwd: None,
- },
"session-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap
index a9e97f93..c7e71adf 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap
@@ -4483,37 +4483,6 @@ Config {
bare_key: Char(
'q',
),
- key_modifiers: {},
- }: [
- LaunchPlugin {
- plugin: RunPlugin(
- RunPlugin {
- _allow_exec_host_cmd: false,
- location: Zellij(
- PluginTag(
- "sequence",
- ),
- ),
- configuration: PluginUserConfiguration(
- {},
- ),
- initial_cwd: None,
- },
- ),
- should_float: true,
- should_open_in_place: false,
- close_replaced_pane: false,
- skip_cache: false,
- cwd: None,
- },
- SwitchToMode {
- input_mode: Normal,
- },
- ],
- KeyWithModifier {
- bare_key: Char(
- 'q',
- ),
key_modifiers: {
Ctrl,
},
@@ -7882,18 +7851,6 @@ Config {
),
initial_cwd: None,
},
- "sequence": RunPlugin {
- _allow_exec_host_cmd: false,
- location: Zellij(
- PluginTag(
- "sequence",
- ),
- ),
- configuration: PluginUserConfiguration(
- {},
- ),
- initial_cwd: None,
- },
"session-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap
index f124cb6a..b5211847 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap
@@ -4483,37 +4483,6 @@ Config {
bare_key: Char(
'q',
),
- key_modifiers: {},
- }: [
- LaunchPlugin {
- plugin: RunPlugin(
- RunPlugin {
- _allow_exec_host_cmd: false,
- location: Zellij(
- PluginTag(
- "sequence",
- ),
- ),
- configuration: PluginUserConfiguration(
- {},
- ),
- initial_cwd: None,
- },
- ),
- should_float: true,
- should_open_in_place: false,
- close_replaced_pane: false,
- skip_cache: false,
- cwd: None,
- },
- SwitchToMode {
- input_mode: Normal,
- },
- ],
- KeyWithModifier {
- bare_key: Char(
- 'q',
- ),
key_modifiers: {
Ctrl,
},
@@ -6111,18 +6080,6 @@ Config {
),
initial_cwd: None,
},
- "sequence": RunPlugin {
- _allow_exec_host_cmd: false,
- location: Zellij(
- PluginTag(
- "sequence",
- ),
- ),
- configuration: PluginUserConfiguration(
- {},
- ),
- initial_cwd: None,
- },
"session-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(