diff options
Diffstat (limited to 'src/cli')
| -rw-r--r-- | src/cli/pane.rs | 171 | ||||
| -rw-r--r-- | src/cli/runtime.rs | 14 | ||||
| -rw-r--r-- | src/cli/spec.rs | 12 |
3 files changed, 178 insertions, 19 deletions
diff --git a/src/cli/pane.rs b/src/cli/pane.rs index 5979fd99..363a2e05 100644 --- a/src/cli/pane.rs +++ b/src/cli/pane.rs @@ -1,11 +1,12 @@ use crate::api::schema::{ Method, OutputMatch, PaneCurrentParams, PaneDirection, PaneEdgesParams, - PaneFocusDirectionParams, PaneLayoutParams, PaneListParams, PaneMoveDestination, - PaneMoveParams, PaneNeighborParams, PaneProcessInfoParams, PaneReadParams, + PaneFocusDirectionParams, PaneInputSetParams, PaneLayoutParams, PaneListParams, + PaneMoveDestination, PaneMoveParams, PaneNeighborParams, PaneProcessInfoParams, PaneReadParams, PaneReleaseAgentParams, PaneRenameParams, PaneReportAgentParams, PaneReportAgentSessionParams, - PaneReportMetadataParams, PaneResizeParams, PaneSendInputParams, PaneSendKeysParams, - PaneSendTextParams, PaneSplitParams, PaneSwapParams, PaneTarget, PaneWaitForOutputParams, - PaneZoomMode, PaneZoomParams, ReadFormat, ReadSource, Request, SplitDirection, + PaneReportMetadataParams, PaneResizeParams, PaneRightClickTarget, PaneSendInputParams, + PaneSendKeysParams, PaneSendTextParams, PaneSplitParams, PaneSwapParams, PaneTarget, + PaneWaitForOutputParams, PaneZoomMode, PaneZoomParams, ReadFormat, ReadSource, Request, + SplitDirection, }; pub(super) fn run_pane_command(args: &[String]) -> std::io::Result<i32> { @@ -27,6 +28,7 @@ pub(super) fn run_pane_command(args: &[String]) -> std::io::Result<i32> { "zoom" => pane_zoom(&args[1..]), "read" => pane_read(&args[1..]), "rename" => pane_rename(&args[1..]), + "input" => pane_input(&args[1..]), "split" => pane_split(&args[1..]), "swap" => pane_swap(&args[1..]), "move" => pane_move(&args[1..]), @@ -538,6 +540,86 @@ fn parse_pane_read_args(args: &[String]) -> Result<PaneReadParams, String> { }) } +fn pane_input(args: &[String]) -> std::io::Result<i32> { + let env_pane_id = std::env::var("HERDR_PANE_ID") + .ok() + .filter(|value| !value.trim().is_empty()); + let params = match parse_pane_input_args(args, env_pane_id.as_deref()) { + Ok(params) => params, + Err(message) => { + eprintln!("{message}"); + return Ok(2); + } + }; + super::runtime::pane_input_set(params) +} + +fn parse_pane_input_args( + args: &[String], + env_pane_id: Option<&str>, +) -> Result<PaneInputSetParams, String> { + const USAGE: &str = + "usage: herdr pane input [<pane_id>|--pane ID|--current] --right-click herdr|pane"; + + let args = super::expand_equals_args(args, &["--pane", "--right-click"]); + let mut pane_id = None; + let mut right_click = None; + let mut index = 0; + while index < args.len() { + match args[index].as_str() { + "--pane" => { + if pane_id.is_some() { + return Err("provide only one pane selector".into()); + } + 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" => { + if pane_id.is_some() { + return Err("provide only one pane selector".into()); + } + pane_id = Some( + env_pane_id + .map(super::normalize_pane_id) + .ok_or("--current requires HERDR_PANE_ID")?, + ); + index += 1; + } + "--right-click" => { + let Some(value) = args.get(index + 1) else { + return Err("missing value for --right-click".into()); + }; + right_click = Some(parse_right_click_target(value)?); + index += 2; + } + option if option.starts_with('-') => return Err(format!("unknown option: {option}")), + positional => { + if pane_id.is_some() { + return Err(format!("unexpected argument: {positional}")); + } + pane_id = Some(super::normalize_pane_id(positional)); + index += 1; + } + } + } + + Ok(PaneInputSetParams { + pane_id: pane_id.ok_or(USAGE)?, + right_click: right_click.ok_or(USAGE)?, + }) +} + +fn parse_right_click_target(value: &str) -> Result<PaneRightClickTarget, String> { + match value { + "herdr" => Ok(PaneRightClickTarget::Herdr), + "pane" => Ok(PaneRightClickTarget::Pane), + _ => Err(format!("invalid right-click target: {value}")), + } +} + fn pane_split(args: &[String]) -> std::io::Result<i32> { let env_pane_id = std::env::var("HERDR_PANE_ID") .ok() @@ -557,12 +639,14 @@ fn parse_pane_split_args( args: &[String], env_pane_id: Option<&str>, ) -> Result<PaneSplitParams, String> { + let args = super::expand_equals_args(args, &["--right-click"]); let mut env = std::collections::HashMap::new(); let mut pane_id = None; let mut direction = None; let mut ratio = None; let mut cwd = None; let mut focus = false; + let mut right_click = PaneRightClickTarget::Herdr; let mut index = 0; if args @@ -582,7 +666,11 @@ fn parse_pane_split_args( index += 2; } "--current" => { - pane_id = env_pane_id.map(super::normalize_pane_id); + pane_id = Some( + env_pane_id + .map(super::normalize_pane_id) + .ok_or("--current requires HERDR_PANE_ID")?, + ); index += 1; } "--direction" => { @@ -613,6 +701,13 @@ fn parse_pane_split_args( cwd = Some(value.clone()); index += 2; } + "--right-click" => { + let Some(value) = args.get(index + 1) else { + return Err("missing value for --right-click".into()); + }; + right_click = parse_right_click_target(value)?; + index += 2; + } "--focus" => { focus = true; index += 1; @@ -635,7 +730,7 @@ fn parse_pane_split_args( let Some(direction) = direction else { return Err( - "usage: herdr pane split [<pane_id>|--pane ID|--current] --direction right|down [--ratio FLOAT] [--cwd PATH] [--env KEY=VALUE] [--focus] [--no-focus]" + "usage: herdr pane split [<pane_id>|--pane ID|--current] --direction right|down [--ratio FLOAT] [--cwd PATH] [--env KEY=VALUE] [--right-click herdr|pane] [--focus] [--no-focus]" .into(), ); }; @@ -647,6 +742,7 @@ fn parse_pane_split_args( ratio, cwd, focus, + right_click, env, }) } @@ -1545,8 +1641,9 @@ fn print_pane_help() { eprintln!(" herdr pane zoom [<pane_id>|--pane ID|--current] [--toggle|--on|--off]"); eprintln!(" herdr pane rename <pane_id> <label>|--clear"); eprintln!(" herdr pane read <pane_id> [--source visible|recent|recent-unwrapped] [--lines N] [--format text|ansi] [--ansi]"); + eprintln!(" herdr pane input [<pane_id>|--pane ID|--current] --right-click herdr|pane"); eprintln!( - " herdr pane split [<pane_id>|--pane ID|--current] --direction right|down [--ratio FLOAT] [--cwd PATH] [--env KEY=VALUE] [--focus] [--no-focus]" + " herdr pane split [<pane_id>|--pane ID|--current] --direction right|down [--ratio FLOAT] [--cwd PATH] [--env KEY=VALUE] [--right-click herdr|pane] [--focus] [--no-focus]" ); eprintln!(" herdr pane swap --direction left|right|up|down [--pane ID|--current]"); eprintln!(" herdr pane swap --source-pane ID --target-pane ID"); @@ -1583,6 +1680,56 @@ mod tests { assert_eq!(params.target_pane_id, Some("issue-1".into())); assert_eq!(params.direction, crate::api::schema::SplitDirection::Right); assert_eq!(params.ratio, Some(0.333)); + assert_eq!(params.right_click, PaneRightClickTarget::Herdr); + } + + #[test] + fn parse_pane_split_args_accepts_pane_right_click_target() { + let params = parse_pane_split_args( + &args(&["--direction", "right", "--right-click", "pane"]), + None, + ) + .unwrap(); + + assert_eq!(params.right_click, PaneRightClickTarget::Pane); + } + + #[test] + fn parse_pane_input_args_requires_and_uses_calling_pane() { + let params = parse_pane_input_args( + &args(&["--current", "--right-click", "pane"]), + Some("issue-1:p1"), + ) + .unwrap(); + + assert_eq!(params.pane_id, "issue-1:p1"); + assert_eq!(params.right_click, PaneRightClickTarget::Pane); + assert!( + parse_pane_input_args(&args(&["--current", "--right-click", "pane"]), None).is_err() + ); + } + + #[test] + fn parse_pane_input_args_rejects_conflicting_selectors() { + assert!(parse_pane_input_args( + &args(&["pane-a", "--pane", "pane-b", "--right-click", "pane"]), + None, + ) + .is_err()); + assert!(parse_pane_input_args( + &args(&["--pane", "pane-a", "--current", "--right-click", "pane",]), + Some("pane-b"), + ) + .is_err()); + } + + #[test] + fn parse_pane_split_args_accepts_equals_right_click_target() { + let params = + parse_pane_split_args(&args(&["--direction", "right", "--right-click=pane"]), None) + .unwrap(); + + assert_eq!(params.right_click, PaneRightClickTarget::Pane); } #[test] @@ -1598,12 +1745,8 @@ mod tests { } #[test] - fn parse_pane_split_args_current_without_env_keeps_focused_fallback() { - let params = - parse_pane_split_args(&args(&["--direction", "down", "--current"]), None).unwrap(); - - assert_eq!(params.target_pane_id, None); - assert_eq!(params.direction, crate::api::schema::SplitDirection::Down); + fn parse_pane_split_args_current_without_env_is_rejected() { + assert!(parse_pane_split_args(&args(&["--direction", "down", "--current"]), None).is_err()); } #[test] diff --git a/src/cli/runtime.rs b/src/cli/runtime.rs index 0d1251b1..16d2d8b3 100644 --- a/src/cli/runtime.rs +++ b/src/cli/runtime.rs @@ -1,9 +1,9 @@ use crate::api::schema::{ - EmptyParams, Method, PaneFocusDirectionParams, PaneMoveParams, PaneRenameParams, - PaneResizeParams, PaneSplitParams, PaneSwapParams, PaneTarget, PaneZoomParams, Request, - TabCreateParams, TabListParams, TabRenameParams, TabTarget, WorkspaceCreateParams, - WorkspaceRenameParams, WorkspaceTarget, WorktreeCreateParams, WorktreeListParams, - WorktreeOpenParams, WorktreeRemoveParams, + EmptyParams, Method, PaneFocusDirectionParams, PaneInputSetParams, PaneMoveParams, + PaneRenameParams, PaneResizeParams, PaneSplitParams, PaneSwapParams, PaneTarget, + PaneZoomParams, Request, TabCreateParams, TabListParams, TabRenameParams, TabTarget, + WorkspaceCreateParams, WorkspaceRenameParams, WorkspaceTarget, WorktreeCreateParams, + WorktreeListParams, WorktreeOpenParams, WorktreeRemoveParams, }; fn print_method_response(id: &'static str, method: Method) -> std::io::Result<i32> { @@ -105,6 +105,10 @@ pub(super) fn pane_rename(params: PaneRenameParams) -> std::io::Result<i32> { print_method_response("cli:pane:rename", Method::PaneRename(params)) } +pub(super) fn pane_input_set(params: PaneInputSetParams) -> std::io::Result<i32> { + print_method_response("cli:pane:input:set", Method::PaneInputSet(params)) +} + pub(super) fn pane_split(params: PaneSplitParams) -> std::io::Result<i32> { print_method_response("cli:pane:split", Method::PaneSplit(params)) } diff --git a/src/cli/spec.rs b/src/cli/spec.rs index 3be83328..a1efdac4 100644 --- a/src/cli/spec.rs +++ b/src/cli/spec.rs @@ -525,6 +525,17 @@ fn pane_command() -> Command { .arg(flag("clear")), ) .subcommand( + Command::new("input") + .about("Set pane input routing") + .arg(Arg::new("pane_id").value_name("PANE_ID")) + .args(current_pane_args()) + .arg( + option("right-click", "TARGET") + .value_parser(["herdr", "pane"]) + .required(true), + ), + ) + .subcommand( Command::new("split") .about("Split a pane") .arg(Arg::new("pane_id").value_name("PANE_ID")) @@ -533,6 +544,7 @@ fn pane_command() -> Command { .arg(option("ratio", "FLOAT")) .arg(path_option("cwd", "PATH")) .arg(env_option()) + .arg(option("right-click", "TARGET").value_parser(["herdr", "pane"])) .arg(flag("focus")) .arg(flag("no-focus")), ) |
