aboutsummaryrefslogtreecommitdiffstats
path: root/default-plugins/sequence/src/state/execution.rs
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2025-12-11 14:25:42 +0100
committerGitHub <noreply@github.com>2025-12-11 14:25:42 +0100
commit693fa082def5ef0fe030cd7fdc3b1df051b35dc6 (patch)
treeb3f651462bbc1cd2fa5b2d79207343413ef6b83c /default-plugins/sequence/src/state/execution.rs
parentfix(zellij-server): unblock building on 32bit targets (#4484) (diff)
downloadsl0ppij-693fa082def5ef0fe030cd7fdc3b1df051b35dc6.tar.gz
sl0ppij-693fa082def5ef0fe030cd7fdc3b1df051b35dc6.tar.xz
sl0ppij-693fa082def5ef0fe030cd7fdc3b1df051b35dc6.zip
Command Sequences, Blocking CLI Commands, New Plugin APIs (#4546)
* fix deadlocks * initial logging to plugin implementation * only log actions, include keys as Action::Write * send actions to plugins as a new Event::UserAction * add Action::Write metadata to plugin API * run_action plugin command and permission * block-until-exit/success/failure in the cli * fix tests * add NewBlockingPane to the plugin API * run actions in a different thread so as not to block plugin execution * add custom context to run_action flow * fixes for floating pane z_index sorting and unknown protobuf events not crashing plugins * always render the frame of pinned panes * macros module * add macros to config * KDL macro parsing * thread macros to the plugin thread * get/update/delete macros from plugins * add macros updated event * add ability to rename macros * fix deadlock * fix z_indices with pinned unselectable panes bug * allow plugins to opt-in to show_cursor * change show_cursor(bool) to show_cursor(Option<coordinates>) * test * upgrade strum to allow proper enum iteration and case insensitive fromstr implementation for Actions * better cwd tracking for exited command panes * add initial panes to NewTab Action (no-op for now) * add initial panes to cli newtab action * make initial panes work * add close_on_exit and start_suspended to the cli * properly convert newtab action (potentially breaking for plugins) * allow first pane in tab to block * make distinction between opening panes with the last active client and the current pane in the cli * improve some apis * fix fixed size closing pane bug * fix plugin cursor coordinates * fix issues with launch-or-focus-plugin having to do with in-place and suppressed panes * fix: set affected pane in replace pane * allow setting a pane in place when it is suppressed and being focused (tests failing) * fix tests * some fixes for unsuppression of panes * some fixes for completion_tx in new tab action * allow replace/suppressed rather than replace/close only * fixes for inserting suppressed panes without conflicts * pane focus history and explicit unsuppress plugin api * fix: prefer explicitly provided pane id in placement in actions that provide it * fix floating pane coordinate change and z_index * fix plugin cache dir to persist between sessions * fix some stack focusing issues when adding panes without a client id * make sure pinned panes are always above non-pinned floating panes * allow plugins to copy to clipboard * initial text styling additions implementations - buggy * fix dimming issues * remove extras, stay with just dim and unbold * unbold and dim as indices * allow ui components to render success/error colors * allow plugins to send sigint/sigkill to processes, as well as receive their pid * ui component fixes and send originating plugin context with run_action * originating plugin for new tab too * make plugn show pane command also expand stacked panes * add sequence plugin * remove macros * remove snapshots * add shortcut for sequence plugin * fix plugin tests * plugin tests for new apis * update snapshots for sequence shortcut * cleanups and formatting * Event::CwdChanged * moar cleanups * remove some warnings * remove mroe warnings * remove dead code * remove more dead code * fix minor bugs in sequence plugin * update sequence help and format codebase * fix conditional blocking cli commands * fix initial-command new-tab cli flag * add client_ids to CwdChanged Event * last fixes * add pr
Diffstat (limited to 'default-plugins/sequence/src/state/execution.rs')
-rw-r--r--default-plugins/sequence/src/state/execution.rs265
1 files changed, 265 insertions, 0 deletions
diff --git a/default-plugins/sequence/src/state/execution.rs b/default-plugins/sequence/src/state/execution.rs
new file mode 100644
index 00000000..ff630ec0
--- /dev/null
+++ b/default-plugins/sequence/src/state/execution.rs
@@ -0,0 +1,265 @@
+use crate::path_formatting::format_cwd;
+use crate::state::{ChainType, CommandEntry, CommandStatus};
+use std::collections::BTreeMap;
+use std::path::PathBuf;
+use std::time::Duration;
+use zellij_tile::prelude::*;
+
+pub struct Execution {
+ pub all_commands: Vec<CommandEntry>,
+ pub current_running_command_index: usize,
+ pub is_running: bool,
+ pub sequence_id: u64,
+ pub primary_pane_id_before_sequence: Option<PaneId>,
+}
+
+impl Execution {
+ pub fn new() -> Self {
+ Self {
+ all_commands: vec![CommandEntry::default()],
+ current_running_command_index: 0,
+ is_running: false,
+ sequence_id: 0,
+ primary_pane_id_before_sequence: 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);
+ }
+ }
+
+ pub fn update_pane_id_for_command(&mut self, pane_id: PaneId, command_text: &str) {
+ for command in self.all_commands.iter_mut() {
+ if let CommandStatus::Pending | CommandStatus::Running(None) = command.get_status() {
+ let cmd_text = command.get_text();
+ if cmd_text == command_text {
+ command.set_status(CommandStatus::Running(Some(pane_id)));
+ break;
+ }
+ }
+ }
+ }
+
+ pub fn update_exited_command_statuses(&mut self, pane_manifest: &PaneManifest) -> bool {
+ let mut updated = false;
+ for command in self.all_commands.iter_mut() {
+ let status = command.get_status();
+ let pane_id_opt = match &status {
+ CommandStatus::Running(pid) => *pid,
+ CommandStatus::Exited(_, pid) => *pid,
+ CommandStatus::Interrupted(pid) => *pid,
+ _ => None,
+ };
+
+ if let Some(pane_id) = pane_id_opt {
+ let mut pane_found = false;
+ for (_tab_index, panes) in &pane_manifest.panes {
+ for pane_info in panes {
+ let pane_matches = match pane_id {
+ PaneId::Terminal(id) => !pane_info.is_plugin && pane_info.id == id,
+ PaneId::Plugin(id) => pane_info.is_plugin && pane_info.id == id,
+ };
+
+ if pane_matches {
+ pane_found = true;
+ if pane_info.exited {
+ command.set_status(CommandStatus::Exited(
+ pane_info.exit_status,
+ Some(pane_id),
+ ));
+ updated = true;
+ }
+ break;
+ }
+ }
+ if pane_found {
+ break;
+ }
+ }
+
+ if !pane_found && command.start_time.elapsed() > Duration::from_millis(400) {
+ match command.get_status() {
+ CommandStatus::Running(_) => {
+ eprintln!(
+ "Pane {:?} was closed while running, setting pane_id to None",
+ pane_id
+ );
+ command.set_status(CommandStatus::Running(None));
+ updated = true;
+ },
+ CommandStatus::Exited(exit_code, _) => {
+ eprintln!(
+ "Pane {:?} was closed after exiting, setting pane_id to None",
+ pane_id
+ );
+ command.set_status(CommandStatus::Exited(exit_code, None));
+ updated = true;
+ },
+ CommandStatus::Interrupted(_) => {
+ eprintln!("Pane {:?} was closed after being interrupted, setting pane_id to None", pane_id);
+ command.set_status(CommandStatus::Interrupted(None));
+ updated = true;
+ },
+ _ => {},
+ }
+ }
+ }
+ }
+ updated
+ }
+
+ pub fn update_sequence_stopped_state(&mut self) -> bool {
+ let mut needs_rerender = false;
+ if self.is_running {
+ let current_idx = self.current_running_command_index;
+ if let Some(command) = self.all_commands.get_mut(current_idx) {
+ if let CommandStatus::Exited(exit_code, _) = command.get_status() {
+ let should_stop;
+ if current_idx >= self.all_commands.len().saturating_sub(1) {
+ should_stop = true;
+ } else {
+ if let Some(chain_type) = &self
+ .all_commands
+ .get(current_idx)
+ .map(|c| c.get_chain_type())
+ {
+ match chain_type {
+ ChainType::And => {
+ should_stop = exit_code.unwrap_or(0) != 0;
+ },
+ ChainType::Or => {
+ should_stop = exit_code.unwrap_or(0) == 0;
+ },
+ ChainType::Then => {
+ should_stop = false;
+ },
+ ChainType::None => {
+ should_stop = true;
+ },
+ }
+ } else {
+ should_stop = true;
+ }
+ };
+ if should_stop {
+ self.is_running = false;
+ }
+ }
+ }
+ needs_rerender = true;
+ }
+ needs_rerender
+ }
+
+ pub fn execute_command_sequence(
+ &mut self,
+ shell: &Option<PathBuf>,
+ global_cwd: &Option<PathBuf>,
+ primary_pane_id: Option<PaneId>,
+ ) {
+ use zellij_tile::prelude::actions::{Action, RunCommandAction};
+
+ self.all_commands.retain(|c| !c.is_empty());
+
+ let Some(first_active_sequence_command) = self.get_first_command() else {
+ return;
+ };
+
+ let shell = shell.clone().unwrap_or_else(|| PathBuf::from("/bin/bash"));
+
+ let first_command = first_active_sequence_command.get_text();
+ let first_chain_type = first_active_sequence_command.get_chain_type();
+ let command_cwd = first_active_sequence_command
+ .get_cwd()
+ .or_else(|| global_cwd.clone());
+
+ let command = RunCommandAction {
+ command: shell.clone(),
+ args: vec!["-ic".to_string(), first_command.trim().to_string()],
+ cwd: command_cwd,
+ hold_on_close: true,
+ ..Default::default()
+ };
+
+ let placement = NewPanePlacement::InPlace {
+ pane_id_to_replace: primary_pane_id,
+ close_replaced_pane: false,
+ };
+
+ let action = Action::NewBlockingPane {
+ placement,
+ command: Some(command),
+ pane_name: Some(first_command.trim().to_string()),
+ unblock_condition: first_chain_type.to_unblock_condition(),
+ near_current_pane: true,
+ };
+
+ self.sequence_id += 1;
+
+ let mut context = BTreeMap::new();
+ context.insert("sequence_id".to_string(), self.sequence_id.to_string());
+ run_action(action, context);
+ self.set_command_status(0, CommandStatus::Running(None));
+ }
+}
+
+impl Default for Execution {
+ fn default() -> Self {
+ Self::new()
+ }
+}