diff --git a/README.md b/README.md index c32dc66..f2b879f 100644 --- a/README.md +++ b/README.md @@ -460,6 +460,17 @@ Dla pobranej karty `inf bss` możesz uruchomić debugowanie w obu profilach: ./rvctl debug host 4 ``` +Jeżeli pracujesz już w tmuksie na hoście, możesz wysłać start kontenera do +konkretnego panelu: + +```bash +./rvctl debug rv32i 4 pane 0 +./rvctl debug rv32i 4 --pane node:0 +``` + +`pane 0` oznacza panel `0` w bieżącym oknie tmuxa. `node:0` oznacza okno +`node`, panel `0`. + Profil `host` ma też szybkie uruchomienie bez sesji debuggera: ```bash diff --git a/doc/containers.md b/doc/containers.md index a405efd..01c9d0c 100644 --- a/doc/containers.md +++ b/doc/containers.md @@ -109,9 +109,18 @@ Przydatne warianty: ./rvctl debug host bss 4 ./rvctl debug host 4 --editor vim ./rvctl debug host 4 --instance host-bss-t4 +./rvctl debug rv32i 4 --pane 0 +./rvctl debug rv32i 4 pane node:0 ./rvctl debug host 4 --dry-run ``` +`--pane TARGET` oraz forma `pane TARGET` wysyłają wygenerowaną komendę do +istniejącego panelu tmuxa na hoście przez `tmux send-keys`. Skróty targetów: + +- `0` - panel `0` w bieżącym oknie, +- `node:0` - okno `node`, panel `0`, +- `%3` albo `:node.0` - natywny target tmuxa. + ## `run` Na razie `run` jest wdrożone dla profilu `host`. Buduje wybrany task natywnie i diff --git a/doc/rvctl.md b/doc/rvctl.md index 62865e4..069e9b3 100644 --- a/doc/rvctl.md +++ b/doc/rvctl.md @@ -667,6 +667,8 @@ Uruchamia debugowanie zadania w kontenerze. Selektor może być pełny albo kró ./rvctl debug host inf bss 1 ./rvctl debug rv32i 4 ./rvctl debug host 4 +./rvctl debug rv32i 4 --pane 0 +./rvctl debug rv32i 4 pane node:0 ``` `rv32i` uruchamia symulator Hazard3, `gdb-multiarch` i `nvim`. `host` kompiluje @@ -679,8 +681,14 @@ Przełączniki: - `--task TASK` - nadpisuje zadanie z selektora albo z `workspace.json` - `--instance NAME` - ustawia nazwę instancji kontenera/tmuxa - `--editor nvim|vim` - wybiera edytor w kontenerze +- `--pane TARGET` - wysyła wygenerowaną komendę do istniejącego panelu tmuxa + na hoście - `--dry-run` - pokazuje komendę bez uruchamiania Dockera +`--pane 0` oznacza panel `0` w bieżącym oknie tmuxa. `pane node:0` oznacza +okno `node`, panel `0`. Natywne targety tmuxa, na przykład `%3` albo +`:node.0`, też są obsługiwane. + ## `run host` Buduje i uruchamia wybrane zadanie natywnie w kontenerze `host`. @@ -688,6 +696,7 @@ Buduje i uruchamia wybrane zadanie natywnie w kontenerze `host`. ```bash ./rvctl run host inf bss 1 ./rvctl run host 4 +./rvctl run host 4 --pane node:0 ``` Profil `rv32i` pracuje obecnie przez `debug rv32i`, bo wymaga symulatora @@ -701,6 +710,7 @@ Otwiera shell w wybranym profilu kontenera z podmontowaną kartą. ./rvctl shell rv32i inf bss ./rvctl shell host inf bss ./rvctl shell host --dry-run +./rvctl shell rv32i --pane 0 ``` ## `list-series` diff --git a/rvctl.py b/rvctl.py index 582cbed..e0dfe11 100755 --- a/rvctl.py +++ b/rvctl.py @@ -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.")