From 556b16b2df7ea2c3e9b8ad13db4ad7370c1ad838 Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Fri, 5 Jun 2026 21:59:10 +0300 Subject: feat: add pane navigation controls refs #421 refs #330 related discussions: https://github.com/ogulcancelik/herdr/discussions/422 https://github.com/ogulcancelik/herdr/discussions/438 https://github.com/ogulcancelik/herdr/discussions/442 --- src/cli/pane.rs | 530 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 501 insertions(+), 29 deletions(-) (limited to 'src/cli/pane.rs') diff --git a/src/cli/pane.rs b/src/cli/pane.rs index d759fa0d..1a9b20e1 100644 --- a/src/cli/pane.rs +++ b/src/cli/pane.rs @@ -1,7 +1,9 @@ use crate::api::schema::{ - Method, PaneListParams, PaneReadParams, PaneRenameParams, PaneReportAgentParams, - PaneReportMetadataParams, PaneSendInputParams, PaneSendKeysParams, PaneSendTextParams, - PaneSplitParams, PaneTarget, ReadFormat, ReadSource, Request, + Method, PaneDirection, PaneEdgesParams, PaneFocusDirectionParams, PaneLayoutParams, + PaneListParams, PaneNeighborParams, PaneReadParams, PaneRenameParams, PaneReportAgentParams, + PaneReportMetadataParams, PaneResizeParams, PaneSendInputParams, PaneSendKeysParams, + PaneSendTextParams, PaneSplitParams, PaneSwapParams, PaneTarget, ReadFormat, ReadSource, + Request, }; pub(super) fn run_pane_command(args: &[String]) -> std::io::Result { @@ -13,9 +15,15 @@ pub(super) fn run_pane_command(args: &[String]) -> std::io::Result { match subcommand { "list" => pane_list(&args[1..]), "get" => pane_get(&args[1..]), + "layout" => pane_layout(&args[1..]), + "neighbor" => pane_neighbor(&args[1..]), + "edges" => pane_edges(&args[1..]), + "focus" => pane_focus(&args[1..]), + "resize" => pane_resize(&args[1..]), "read" => pane_read(&args[1..]), "rename" => pane_rename(&args[1..]), "split" => pane_split(&args[1..]), + "swap" => pane_swap(&args[1..]), "close" => pane_close(&args[1..]), "send-text" => pane_send_text(&args[1..]), "send-keys" => pane_send_keys(&args[1..]), @@ -78,6 +86,209 @@ fn pane_get(args: &[String]) -> std::io::Result { })?) } +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_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_rename(args: &[String]) -> std::io::Result { let Some(raw_pane_id) = args.first() else { eprintln!("usage: herdr pane rename