aboutsummaryrefslogtreecommitdiffstats
path: root/default-plugins/sequence/src/state/command_parser.rs
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 /default-plugins/sequence/src/state/command_parser.rs
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)
Diffstat (limited to 'default-plugins/sequence/src/state/command_parser.rs')
-rw-r--r--default-plugins/sequence/src/state/command_parser.rs370
1 files changed, 0 insertions, 370 deletions
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")));
- }
-}