Add tmux pane target for container commands
This commit is contained in:
@@ -3533,6 +3533,60 @@ def resolve_env_target(
|
||||
return series_name, card_name, task_name
|
||||
|
||||
|
||||
def split_pane_selector(values: list[str], explicit_pane: str | None = None) -> tuple[list[str], str | None]:
|
||||
if "pane" not in values:
|
||||
return values, explicit_pane
|
||||
if explicit_pane:
|
||||
raise SystemExit("Pass pane target either as '--pane TARGET' or as 'pane TARGET', not both.")
|
||||
|
||||
index = values.index("pane")
|
||||
pane_values = values[index + 1 :]
|
||||
if len(pane_values) != 1:
|
||||
raise SystemExit("Use: pane TARGET, for example 'pane 0' or 'pane node:0'.")
|
||||
return values[:index], pane_values[0]
|
||||
|
||||
|
||||
def tmux_pane_target(raw_target: str) -> str:
|
||||
target = raw_target.strip()
|
||||
if not target:
|
||||
raise SystemExit("Empty tmux pane target.")
|
||||
if re.fullmatch(r"\d+", target):
|
||||
return f":.{target}"
|
||||
if re.fullmatch(r"%\d+", target):
|
||||
return target
|
||||
window_pane_match = re.fullmatch(r"([^:.]+):(\d+)", target)
|
||||
if window_pane_match:
|
||||
window_name, pane_index = window_pane_match.groups()
|
||||
return f":{window_name}.{pane_index}"
|
||||
return target
|
||||
|
||||
|
||||
def send_command_to_tmux_pane(shell_command: str, raw_target: str, dry_run: bool = False) -> dict[str, str]:
|
||||
target = tmux_pane_target(raw_target)
|
||||
details = {
|
||||
"pane": raw_target,
|
||||
"tmux_target": target,
|
||||
"tmux_send": f"tmux send-keys -t {shlex.quote(target)} -l {shlex.quote(shell_command)}; tmux send-keys -t {shlex.quote(target)} C-m",
|
||||
}
|
||||
if dry_run:
|
||||
return details
|
||||
|
||||
resolved = subprocess.run(
|
||||
["tmux", "display-message", "-p", "-t", target, "#{session_name}:#{window_name}.#{pane_index}"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
if resolved.returncode != 0:
|
||||
raise SystemExit(f"Missing tmux pane target '{raw_target}' ({target}): {resolved.stderr.strip()}")
|
||||
|
||||
subprocess.run(["tmux", "send-keys", "-t", target, "-l", shell_command], check=True)
|
||||
subprocess.run(["tmux", "send-keys", "-t", target, "C-m"], check=True)
|
||||
details["tmux_resolved"] = resolved.stdout.strip()
|
||||
return details
|
||||
|
||||
|
||||
def default_env_instance(profile: str, series_name: str, card_name: str, task_name: str, action: str) -> str:
|
||||
task_part = task_branch_part(task_name).lower()
|
||||
return slug_value(f"{profile}-{series_name}-{card_name}-{task_part}-{action}", "environment instance")
|
||||
@@ -3619,8 +3673,23 @@ def env_action_command(
|
||||
|
||||
|
||||
def print_env_plan(details: dict[str, str]) -> None:
|
||||
for key in ["profile", "service", "selector", "task", "card_path", "tools_root", "instance", "command"]:
|
||||
print(f"{key}\t{details.get(key, '')}")
|
||||
keys = [
|
||||
"profile",
|
||||
"service",
|
||||
"selector",
|
||||
"task",
|
||||
"card_path",
|
||||
"tools_root",
|
||||
"instance",
|
||||
"pane",
|
||||
"tmux_target",
|
||||
"tmux_resolved",
|
||||
"tmux_send",
|
||||
"command",
|
||||
]
|
||||
for key in keys:
|
||||
if key == "command" or details.get(key):
|
||||
print(f"{key}\t{details.get(key, '')}")
|
||||
|
||||
|
||||
def run_env(config: WorkspaceConfig, args: argparse.Namespace) -> None:
|
||||
@@ -3650,7 +3719,8 @@ def run_env(config: WorkspaceConfig, args: argparse.Namespace) -> None:
|
||||
|
||||
def run_profile_action(config: WorkspaceConfig, args: argparse.Namespace, action: str) -> None:
|
||||
profile = normalize_env_profile(args.profile)
|
||||
series_name, card_name, task_name = resolve_env_target(config, args.selector, getattr(args, "task", None))
|
||||
selector_values, pane_target = split_pane_selector(args.selector, getattr(args, "pane", None))
|
||||
series_name, card_name, task_name = resolve_env_target(config, selector_values, getattr(args, "task", None))
|
||||
shell_command, details = env_action_command(
|
||||
config,
|
||||
profile,
|
||||
@@ -3661,6 +3731,14 @@ def run_profile_action(config: WorkspaceConfig, args: argparse.Namespace, action
|
||||
instance=getattr(args, "instance", None),
|
||||
editor=getattr(args, "editor", None),
|
||||
)
|
||||
if pane_target:
|
||||
pane_details = send_command_to_tmux_pane(shell_command, pane_target, dry_run=args.dry_run)
|
||||
details.update(pane_details)
|
||||
if args.dry_run:
|
||||
print_env_plan(details)
|
||||
return
|
||||
print_env_plan(details)
|
||||
return
|
||||
if args.dry_run:
|
||||
print_env_plan(details)
|
||||
return
|
||||
@@ -3975,6 +4053,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
debug_parser.add_argument("--task", help="Task override. Defaults to selector or defaults.task.")
|
||||
debug_parser.add_argument("--instance", help="Container/tmux instance name.")
|
||||
debug_parser.add_argument("--editor", choices=["nvim", "vim"], help="Editor inside the container.")
|
||||
debug_parser.add_argument("--pane", help="Send the command to a host tmux pane, for example 0, node:0, :node.0 or %3.")
|
||||
debug_parser.add_argument("--dry-run", action="store_true", help="Print command without starting Docker.")
|
||||
|
||||
run_parser = subparsers.add_parser("run", help="Build and run selected task inside a container profile.")
|
||||
@@ -3982,6 +4061,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
run_parser.add_argument("selector", nargs="*", help="Optional selector: [task], [card task] or [series card task].")
|
||||
run_parser.add_argument("--task", help="Task override. Defaults to selector or defaults.task.")
|
||||
run_parser.add_argument("--instance", help="Container instance name.")
|
||||
run_parser.add_argument("--pane", help="Send the command to a host tmux pane, for example 0, node:0, :node.0 or %3.")
|
||||
run_parser.add_argument("--dry-run", action="store_true", help="Print command without starting Docker.")
|
||||
|
||||
shell_parser = subparsers.add_parser("shell", help="Open a shell inside a container profile.")
|
||||
@@ -3989,6 +4069,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
shell_parser.add_argument("selector", nargs="*", help="Optional selector: [card] or [series card].")
|
||||
shell_parser.add_argument("--task", help="Task used only for instance naming.")
|
||||
shell_parser.add_argument("--instance", help="Container instance name.")
|
||||
shell_parser.add_argument("--pane", help="Send the command to a host tmux pane, for example 0, node:0, :node.0 or %3.")
|
||||
shell_parser.add_argument("--dry-run", action="store_true", help="Print command without starting Docker.")
|
||||
|
||||
tasks_parser = subparsers.add_parser("tasks", help="Shortcut for tasks in the default card.")
|
||||
|
||||
Reference in New Issue
Block a user