use crate::api::schema::{ Method, PaneCurrentParams, PaneDirection, PaneEdgesParams, PaneFocusDirectionParams, PaneLayoutParams, PaneListParams, PaneMoveDestination, PaneMoveParams, PaneNeighborParams, PaneProcessInfoParams, PaneReadParams, PaneReleaseAgentParams, PaneRenameParams, PaneReportAgentParams, PaneReportAgentSessionParams, PaneReportMetadataParams, PaneResizeParams, PaneSendInputParams, PaneSendKeysParams, PaneSendTextParams, PaneSplitParams, PaneSwapParams, PaneTarget, PaneZoomMode, PaneZoomParams, ReadFormat, ReadSource, Request, SplitDirection, }; pub(super) fn run_pane_command(args: &[String]) -> std::io::Result { let Some(subcommand) = args.first().map(|arg| arg.as_str()) else { print_pane_help(); return Ok(2); }; match subcommand { "list" => pane_list(&args[1..]), "current" => pane_current(&args[1..]), "get" => pane_get(&args[1..]), "layout" => pane_layout(&args[1..]), "process-info" => pane_process_info(&args[1..]), "neighbor" => pane_neighbor(&args[1..]), "edges" => pane_edges(&args[1..]), "focus" => pane_focus(&args[1..]), "resize" => pane_resize(&args[1..]), "zoom" => pane_zoom(&args[1..]), "read" => pane_read(&args[1..]), "rename" => pane_rename(&args[1..]), "split" => pane_split(&args[1..]), "swap" => pane_swap(&args[1..]), "move" => pane_move(&args[1..]), "close" => pane_close(&args[1..]), "send-text" => pane_send_text(&args[1..]), "send-keys" => pane_send_keys(&args[1..]), "report-agent" => pane_report_agent(&args[1..]), "report-agent-session" => pane_report_agent_session(&args[1..]), "release-agent" => pane_release_agent(&args[1..]), "report-metadata" => pane_report_metadata(&args[1..]), "run" => pane_run(&args[1..]), "help" | "--help" | "-h" => { print_pane_help(); Ok(0) } _ => { print_pane_help(); Ok(2) } } } fn pane_list(args: &[String]) -> std::io::Result { let mut workspace_id = None; let mut index = 0; while index < args.len() { match args[index].as_str() { "--workspace" => { let Some(value) = args.get(index + 1) else { eprintln!("missing value for --workspace"); return Ok(2); }; workspace_id = Some(super::normalize_workspace_id(value)); index += 2; } other => { eprintln!("unknown option: {other}"); return Ok(2); } } } super::print_response(&super::send_request(&Request { id: "cli:pane:list".into(), method: Method::PaneList(PaneListParams { workspace_id }), })?) } fn pane_get(args: &[String]) -> std::io::Result { let Some(raw_pane_id) = args.first() else { eprintln!("usage: herdr pane get "); return Ok(2); }; if args.len() != 1 { eprintln!("usage: herdr pane get "); return Ok(2); } super::print_response(&super::send_request(&Request { id: "cli:pane:get".into(), method: Method::PaneGet(PaneTarget { pane_id: super::normalize_pane_id(raw_pane_id), }), })?) } fn pane_current(args: &[String]) -> std::io::Result { let env_pane_id = std::env::var("HERDR_PANE_ID") .ok() .filter(|value| !value.trim().is_empty()); let caller_pane_id = match parse_pane_current_args(args, env_pane_id.as_deref()) { Ok(caller_pane_id) => caller_pane_id, Err(message) => { eprintln!("{message}"); return Ok(2); } }; super::print_response(&super::send_request(&Request { id: "cli:pane:current".into(), method: Method::PaneCurrent(PaneCurrentParams { caller_pane_id }), })?) } fn parse_pane_current_args( args: &[String], env_pane_id: Option<&str>, ) -> Result, String> { let mut caller_pane_id = env_pane_id.map(super::normalize_pane_id); let mut index = 0; while index < args.len() { match args[index].as_str() { "--pane" => { let Some(value) = args.get(index + 1) else { return Err("missing value for --pane".into()); }; caller_pane_id = Some(super::normalize_pane_id(value)); index += 2; } "--current" => { caller_pane_id = env_pane_id.map(super::normalize_pane_id); index += 1; } other => return Err(format!("unknown option: {other}")), } } Ok(caller_pane_id) } fn pane_layout(args: &[String]) -> std::io::Result { let pane_id = match parse_optional_current_pane_args(args) { Ok(pane_id) => pane_id, Err(message) => { eprintln!("{message}"); return Ok(2); } }; super::print_response(&super::send_request(&Request { id: "cli:pane:layout".into(), method: Method::PaneLayout(PaneLayoutParams { pane_id }), })?) } fn pane_process_info(args: &[String]) -> std::io::Result { let pane_id = match parse_optional_current_pane_args(args) { Ok(pane_id) => pane_id, Err(message) => { eprintln!("{message}"); return Ok(2); } }; super::print_response(&super::send_request(&Request { id: "cli:pane:process_info".into(), method: Method::PaneProcessInfo(PaneProcessInfoParams { pane_id }), })?) } fn pane_edges(args: &[String]) -> std::io::Result { let pane_id = match parse_optional_current_pane_args(args) { Ok(pane_id) => pane_id, Err(message) => { eprintln!("{message}"); return Ok(2); } }; super::print_response(&super::send_request(&Request { id: "cli:pane:edges".into(), method: Method::PaneEdges(PaneEdgesParams { pane_id }), })?) } fn pane_neighbor(args: &[String]) -> std::io::Result { let params = match parse_pane_neighbor_args(args) { Ok(params) => params, Err(message) => { eprintln!("{message}"); return Ok(2); } }; super::print_response(&super::send_request(&Request { id: "cli:pane:neighbor".into(), method: Method::PaneNeighbor(params), })?) } fn pane_focus(args: &[String]) -> std::io::Result { let params = match parse_pane_focus_args(args) { Ok(params) => params, Err(message) => { eprintln!("{message}"); return Ok(2); } }; super::print_response(&super::send_request(&Request { id: "cli:pane:focus".into(), method: Method::PaneFocusDirection(params), })?) } fn pane_resize(args: &[String]) -> std::io::Result { let params = match parse_pane_resize_args(args) { Ok(params) => params, Err(message) => { eprintln!("{message}"); return Ok(2); } }; super::print_response(&super::send_request(&Request { id: "cli:pane:resize".into(), method: Method::PaneResize(params), })?) } fn parse_optional_current_pane_args(args: &[String]) -> Result, String> { let mut pane_id = None; let mut index = 0; while index < args.len() { match args[index].as_str() { "--pane" => { let Some(value) = args.get(index + 1) else { return Err("missing value for --pane".into()); }; pane_id = Some(super::normalize_pane_id(value)); index += 2; } "--current" => { pane_id = None; index += 1; } other => return Err(format!("unknown option: {other}")), } } Ok(pane_id) } fn parse_pane_neighbor_args(args: &[String]) -> Result { let mut pane_id = None; let mut direction = None; let mut index = 0; while index < args.len() { match args[index].as_str() { "--pane" => { let Some(value) = args.get(index + 1) else { return Err("missing value for --pane".into()); }; pane_id = Some(super::normalize_pane_id(value)); index += 2; } "--current" => { pane_id = None; index += 1; } "--direction" => { let Some(value) = args.get(index + 1) else { return Err("missing value for --direction".into()); }; direction = Some(parse_pane_direction(value)?); index += 2; } other => return Err(format!("unknown option: {other}")), } } let Some(direction) = direction else { return Err( "usage: herdr pane neighbor --direction left|right|up|down [--pane ID|--current]" .into(), ); }; Ok(PaneNeighborParams { pane_id, direction }) } fn parse_pane_focus_args(args: &[String]) -> Result { let params = parse_pane_neighbor_args(args).map_err(|_| { "usage: herdr pane focus --direction left|right|up|down [--pane ID|--current]".to_string() })?; Ok(PaneFocusDirectionParams { pane_id: params.pane_id, direction: params.direction, }) } fn parse_pane_resize_args(args: &[String]) -> Result { let mut pane_id = None; let mut direction = None; let mut amount = None; let mut index = 0; while index < args.len() { match args[index].as_str() { "--pane" => { let Some(value) = args.get(index + 1) else { return Err("missing value for --pane".into()); }; pane_id = Some(super::normalize_pane_id(value)); index += 2; } "--current" => { pane_id = None; index += 1; } "--direction" => { let Some(value) = args.get(index + 1) else { return Err("missing value for --direction".into()); }; direction = Some(parse_pane_direction(value)?); index += 2; } "--amount" => { let Some(value) = args.get(index + 1) else { return Err("missing value for --amount".into()); }; let parsed = value .parse::() .map_err(|_| format!("invalid amount: {value}"))?; if !parsed.is_finite() { return Err(format!("invalid amount: {value}")); } amount = Some(parsed); index += 2; } other => return Err(format!("unknown option: {other}")), } } let Some(direction) = direction else { return Err( "usage: herdr pane resize --direction left|right|up|down [--amount FLOAT] [--pane ID|--current]" .into(), ); }; Ok(PaneResizeParams { pane_id, direction, amount, }) } fn pane_zoom(args: &[String]) -> std::io::Result { let params = match parse_pane_zoom_args(args) { Ok(params) => params, Err(message) => { eprintln!("{message}"); return Ok(2); } }; super::print_response(&super::send_request(&Request { id: "cli:pane:zoom".into(), method: Method::PaneZoom(params), })?) } fn parse_pane_zoom_args(args: &[String]) -> Result { let mut pane_id = None; let mut mode = PaneZoomMode::Toggle; let mut mode_seen = false; let mut index = 0; if args .first() .is_some_and(|arg| !arg.as_str().starts_with("--")) { pane_id = args.first().map(|arg| super::normalize_pane_id(arg)); index = 1; } while index < args.len() { match args[index].as_str() { "--pane" => { let Some(value) = args.get(index + 1) else { return Err("missing value for --pane".into()); }; pane_id = Some(super::normalize_pane_id(value)); index += 2; } "--current" => { pane_id = None; index += 1; } "--toggle" => { if mode_seen { return Err("provide only one of --toggle, --on, or --off".into()); } mode = PaneZoomMode::Toggle; mode_seen = true; index += 1; } "--on" => { if mode_seen { return Err("provide only one of --toggle, --on, or --off".into()); } mode = PaneZoomMode::On; mode_seen = true; index += 1; } "--off" => { if mode_seen { return Err("provide only one of --toggle, --on, or --off".into()); } mode = PaneZoomMode::Off; mode_seen = true; index += 1; } other => return Err(format!("unknown option: {other}")), } } Ok(PaneZoomParams { pane_id, mode }) } fn pane_rename(args: &[String]) -> std::io::Result { let Some(raw_pane_id) = args.first() else { eprintln!("usage: herdr pane rename