diff options
| author | Aram Drevekenin <aram@poor.dev> | 2025-12-11 14:25:42 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-12-11 14:25:42 +0100 |
| commit | 693fa082def5ef0fe030cd7fdc3b1df051b35dc6 (patch) | |
| tree | b3f651462bbc1cd2fa5b2d79207343413ef6b83c /default-plugins/sequence/src/state/execution.rs | |
| parent | fix(zellij-server): unblock building on 32bit targets (#4484) (diff) | |
| download | sl0ppij-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.rs | 265 |
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() + } +} |
