aboutsummaryrefslogtreecommitdiff
path: root/src/cli
diff options
context:
space:
mode:
Diffstat (limited to 'src/cli')
-rw-r--r--src/cli/pane.rs171
-rw-r--r--src/cli/runtime.rs14
-rw-r--r--src/cli/spec.rs12
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")),
)