aboutsummaryrefslogtreecommitdiffstats
path: root/default-plugins/sequence/src/state/command_parser.rs
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2026-02-17 15:43:28 +0100
committerGitHub <noreply@github.com>2026-02-17 15:43:28 +0100
commit4e66a0beff5e41bda1dbde521fa1d2858c46fe7b (patch)
tree380bee5eeac56bea2ebf341cccbe69615585e394 /default-plugins/sequence/src/state/command_parser.rs
parentFix startup message and cache file creation (#4705) (diff)
downloadsl0ppij-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.rs177
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")));
+ }
}