diff options
| author | Aram Drevekenin <aram@poor.dev> | 2026-02-17 15:43:28 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-17 15:43:28 +0100 |
| commit | 4e66a0beff5e41bda1dbde521fa1d2858c46fe7b (patch) | |
| tree | 380bee5eeac56bea2ebf341cccbe69615585e394 /default-plugins/sequence/src/state/command_parser.rs | |
| parent | Fix startup message and cache file creation (#4705) (diff) | |
| download | sl0ppij-4e66a0beff5e41bda1dbde521fa1d2858c46fe7b.tar.gz sl0ppij-4e66a0beff5e41bda1dbde521fa1d2858c46fe7b.tar.xz sl0ppij-4e66a0beff5e41bda1dbde521fa1d2858c46fe7b.zip | |
Revamp the sequence plugin (#4713)
* initial pipe interface
* initial open_x_pane_in_new_tab plugin api implementation
* fix: send CommandPaneExited for command panes opened as initial_terminals in new tabs from plugins
* open panes in place of specific pane id plugin commands - initial implementation
* sequence and plugin api ux
* focus self when starting
* combine ui and pipe paths
* highlight selected pane
* initial sequence removal of editing capabilities
* fix: translate plugin paths in api
* properly trim commands when loaded from pipe or pasted text
* minor ux fixes
* get ctrl c to work properly
* behavior fixes
* more ui fixes
* fix tests
* close tab and end sequence properly
* default to AND
* zellij sequence alias
* add alias
* consolidate paths
* rustfmt
* fix ui padding
* add pr
* fix: reset scrolling when resetting terminal state
Diffstat (limited to 'default-plugins/sequence/src/state/command_parser.rs')
| -rw-r--r-- | default-plugins/sequence/src/state/command_parser.rs | 177 |
1 files changed, 110 insertions, 67 deletions
diff --git a/default-plugins/sequence/src/state/command_parser.rs b/default-plugins/sequence/src/state/command_parser.rs index 7f0898f5..35935cd3 100644 --- a/default-plugins/sequence/src/state/command_parser.rs +++ b/default-plugins/sequence/src/state/command_parser.rs @@ -1,4 +1,4 @@ -use super::ChainType; +use super::{ChainType, CommandEntry}; #[derive(Debug, Clone, Copy, PartialEq)] enum ParseState { @@ -104,63 +104,105 @@ pub fn split_by_chain_operators(text: &str) -> Vec<(String, Option<ChainType>)> segments } -pub fn detect_chain_operator_at_end(text: &str) -> Option<(String, ChainType)> { - let segments = split_by_chain_operators(text); - - if segments.is_empty() { - return None; - } +pub fn detect_cd_command(text: &str) -> Option<String> { + let trimmed = text.trim(); - if segments.len() == 1 { - if let Some((text, Some(chain_type))) = segments.first() { - return Some((text.clone(), *chain_type)); - } - return None; + if trimmed == "cd" { + return Some("~".to_string()); } - if let Some((first_text, Some(chain_type))) = segments.first() { - return Some((first_text.clone(), *chain_type)); + if trimmed.starts_with("cd ") { + let path = trimmed[3..].trim(); + return Some(path.to_string()); } None } -pub fn get_remaining_after_first_segment(text: &str) -> Option<String> { - let segments = split_by_chain_operators(text); +/// 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(); - if segments.len() <= 1 { - return None; - } + result.push_str("# One cmd per line. Use && || ; to chain. cd <path> sets cwd.\n"); + result.push_str("\n"); - let mut result = String::new(); - for (i, (segment_text, chain_type_opt)) in segments.iter().enumerate().skip(1) { - if i > 1 { - if let Some(chain_type) = chain_type_opt { - result.push_str(&format!(" {} ", chain_type.as_str())); - } + let mut prev_cwd: Option<std::path::PathBuf> = None; + + for cmd in commands { + if cmd.get_text().trim().is_empty() { + continue; } - result.push_str(segment_text); - if let Some(chain_type) = chain_type_opt { - result.push_str(&format!(" {} ", chain_type.as_str())); + + 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)); } - Some(result.trim().to_string()) + result } -pub fn detect_cd_command(text: &str) -> Option<String> { - let trimmed = text.trim(); +/// 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; - if trimmed == "cd" { - return Some("~".to_string()); + 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); + } + } } - if trimmed.starts_with("cd ") { - let path = trimmed[3..].trim(); - return Some(path.to_string()); + // 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(); + } } - None + commands } #[cfg(test)] @@ -278,36 +320,6 @@ mod tests { } #[test] - fn test_detect_operator_just_typed_and() { - let result = detect_chain_operator_at_end("ls &&"); - assert_eq!(result, Some(("ls".to_string(), ChainType::And))); - } - - #[test] - fn test_detect_operator_just_typed_or() { - let result = detect_chain_operator_at_end("cmd ||"); - assert_eq!(result, Some(("cmd".to_string(), ChainType::Or))); - } - - #[test] - fn test_detect_operator_just_typed_semicolon() { - let result = detect_chain_operator_at_end("echo ;"); - assert_eq!(result, Some(("echo".to_string(), ChainType::Then))); - } - - #[test] - fn test_detect_operator_no_operator() { - let result = detect_chain_operator_at_end("ls"); - assert_eq!(result, None); - } - - #[test] - fn test_detect_operator_in_quotes() { - let result = detect_chain_operator_at_end("echo '&&'"); - assert_eq!(result, None); - } - - #[test] fn test_whitespace_trimming() { let result = split_by_chain_operators(" cmd1 && cmd2 "); assert_eq!( @@ -324,4 +336,35 @@ mod tests { 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"))); + } } |
