diff --git a/CHANGES b/CHANGES index 03d66a7bd20..be470b649de 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,27 @@ enter: false ``` +- #750: Pause execution via `sleep_before: [int]` and `sleep_after: [int]` + + ```yaml + session_name: Pause / skip command execution (command-level) + windows: + - panes: + - shell_command: + # Executes immediately + - echo "___$((11 + 1))___" + # Delays before sending 2 seconds + - cmd: echo "___$((1 + 3))___" + sleep_before: 2 + # Executes immediately + - cmd: echo "___$((1 + 3))___" + # Pauses 2 seconds after + - cmd: echo "Stuff rendering here!" + sleep_after: 2 + # Executes after earlier commands (after 2 sec) + - cmd: echo "2 seconds later" + ``` + - #701: `tmuxp freeze` now accepts `--quiet` and `--yes` along with the `--config-format` and filename (`--save-to`). This means you can do it all in one command: diff --git a/docs/examples.md b/docs/examples.md index 70c8ba3c4a4..e84376744fb 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -332,6 +332,8 @@ This will add the `shell_command` to the bash history in the pane. ```{versionadded} 1.10.0b1 `enter: false` option. Pane-level support. +``` + ```{versionadded} 1.10.0b3 Support command-level skipping. ``` @@ -375,6 +377,64 @@ Omit sending {kbd}`enter` to key commands. Equivalent to ```` +## Pausing commands + +```{versionadded} 1.10.0b4 +`sleep_before` and `sleep_after` options added. Pane and command-level support. +``` + +```{warning} +This will delay loading as it runs synchronously for each pane. In future version asynchronous support, (i.e. [`asyncio`](asyncio)) will speed up this up. +``` + +Omit sending {kbd}`enter` to key commands. Equivalent to having +a [`time.sleep`](time.sleep) before and after [`send_keys`](libtmux.Pane.send_keys). + +This is especially useful for expensive commands where the terminal needs some breathing room (virtualenv, poetry, pipenv, sourcing a configuration, launching a tui app, etc). + +````{tab} Virtualenv + +```{literalinclude} ../examples/sleep-virtualenv.yaml +:language: yaml + +``` +```` + +````{tab} YAML + +```{literalinclude} ../examples/sleep.yaml +:language: yaml + +``` + +```` + +````{tab} JSON + +```{literalinclude} ../examples/sleep.json +:language: json + +``` + +```` + +````{tab} YAML (pane-level) + +```{literalinclude} ../examples/sleep-pane-level.yaml +:language: yaml + +``` + +```` + +````{tab} JSON (pane-level) + +```{literalinclude} ../examples/sleep-pane-level.json +:language: json + +``` + +```` ## Window Index diff --git a/examples/skip-send-pane-level.json b/examples/skip-send-pane-level.json index 43a88fa2999..2fa8a2535ce 100644 --- a/examples/skip-send-pane-level.json +++ b/examples/skip-send-pane-level.json @@ -1,5 +1,5 @@ { - "session_name": "Skip all pane commands", + "session_name": "Skip command execution (pane-level)", "windows": [ { "panes": [ @@ -17,4 +17,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/examples/skip-send-pane-level.yaml b/examples/skip-send-pane-level.yaml index cbc9d77ebbf..03628f76918 100644 --- a/examples/skip-send-pane-level.yaml +++ b/examples/skip-send-pane-level.yaml @@ -1,4 +1,4 @@ -session_name: Skip all pane commands +session_name: Skip command execution (pane-level) windows: - panes: - shell_command: echo "___$((1 + 3))___" diff --git a/examples/sleep-pane-level.json b/examples/sleep-pane-level.json new file mode 100644 index 00000000000..92daeeab77c --- /dev/null +++ b/examples/sleep-pane-level.json @@ -0,0 +1,27 @@ +{ + "session_name": "Pause / skip command execution (pane-level)", + "windows": [ + { + "panes": [ + { + "sleep_before": 2, + "shell_command": [ + "echo \"___$((11 + 1))___\"", + { + "cmd": "echo \"___$((1 + 3))___\"" + }, + { + "cmd": "echo \"___$((1 + 3))___\"" + }, + { + "cmd": "echo \"Stuff rendering here!\"" + }, + { + "cmd": "echo \"2 seconds later\"" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/sleep-pane-level.yaml b/examples/sleep-pane-level.yaml new file mode 100644 index 00000000000..eb64e0fd7ee --- /dev/null +++ b/examples/sleep-pane-level.yaml @@ -0,0 +1,13 @@ +session_name: Pause / skip command execution (pane-level) +windows: +- panes: + - + # Wait 2 seconds before sending all commands in this pane + sleep_before: 2 + shell_command: + - echo "___$((11 + 1))___" + - cmd: echo "___$((1 + 3))___" + - cmd: echo "___$((1 + 3))___" + - cmd: echo "Stuff rendering here!" + - cmd: echo "2 seconds later" + diff --git a/examples/sleep-virtualenv.yaml b/examples/sleep-virtualenv.yaml new file mode 100644 index 00000000000..5370fa34c70 --- /dev/null +++ b/examples/sleep-virtualenv.yaml @@ -0,0 +1,11 @@ +session_name: virtualenv +shell_command_before: +# - cmd: source $(poetry env info --path)/bin/activate +# - cmd: source `pipenv --venv`/bin/activate +- cmd: source .venv/bin/activate + sleep_before: 1 + sleep_after: 1 +windows: +- panes: + - shell_command: + - ./manage.py runserver diff --git a/examples/sleep.json b/examples/sleep.json new file mode 100644 index 00000000000..2136cab086f --- /dev/null +++ b/examples/sleep.json @@ -0,0 +1,28 @@ +{ + "session_name": "Pause / skip command execution (command-level)", + "windows": [ + { + "panes": [ + { + "shell_command": [ + "echo \"___$((11 + 1))___\"", + { + "cmd": "echo \"___$((1 + 3))___\"", + "sleep_before": 2 + }, + { + "cmd": "echo \"___$((1 + 3))___\"" + }, + { + "cmd": "echo \"Stuff rendering here!\"", + "sleep_after": 2 + }, + { + "cmd": "echo \"2 seconds later\"" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/sleep.yaml b/examples/sleep.yaml new file mode 100644 index 00000000000..ae90586b7ae --- /dev/null +++ b/examples/sleep.yaml @@ -0,0 +1,16 @@ +session_name: Pause / skip command execution (command-level) +windows: +- panes: + - shell_command: + # Executes immediately + - echo "___$((11 + 1))___" + # Delays before sending 2 seconds + - cmd: echo "___$((1 + 3))___" + sleep_before: 2 + # Executes immediately + - cmd: echo "___$((1 + 3))___" + # Pauses 2 seconds after + - cmd: echo "Stuff rendering here!" + sleep_after: 2 + # Executes after earlier commands (after 2 sec) + - cmd: echo "2 seconds later" diff --git a/tests/test_workspacebuilder.py b/tests/test_workspacebuilder.py index 158f9f68751..fdcf63c3e9f 100644 --- a/tests/test_workspacebuilder.py +++ b/tests/test_workspacebuilder.py @@ -1021,3 +1021,126 @@ def test_load_workspace_enter( assert output in captured_pane else: assert output not in captured_pane + + +@pytest.mark.parametrize( + "yaml,sleep,output", + [ + [ + textwrap.dedent( + """ +session_name: Should not execute +windows: +- panes: + - shell_command: + - cmd: echo "___$((1 + 5))___" + sleep_before: 2 + - cmd: echo "___$((1 + 3))___" + sleep_before: 1 + """ + ), + 1.5, + "___4___", + ], + [ + textwrap.dedent( + """ +session_name: Should not execute +windows: +- panes: + - shell_command: + - cmd: echo "___$((1 + 5))___" + sleep_before: 2 + - cmd: echo "___$((1 + 3))___" + sleep_before: 1 + """ + ), + 3, + "___4___", + ], + [ + textwrap.dedent( + """ +session_name: Should not execute +windows: +- panes: + - shell_command: + - cmd: echo "___$((1 + 3))___" + sleep_before: 2 + """ + ), + 2, + "___4___", + ], + [ + textwrap.dedent( + """ +session_name: Should not execute +windows: +- panes: + - shell_command: + - cmd: echo "___$((1 + 3))___" + sleep_before: 2 + """ + ), + 2, + "___4___", + ], + [ + textwrap.dedent( + """ +session_name: Should not execute +shell_command_before: + - cmd: echo "sleeping before" + sleep_before: 2 +windows: +- panes: + - echo "___$((1 + 3))___" + """ + ), + 2, + "___4___", + ], + ], + ids=[ + "command_level_sleep_3_shortform", + "command_level_pane_sleep_3_longform", + "pane_sleep_2_shortform", + "pane_sleep_2_longform", + "shell_before_before_command_level", + ], +) +@pytest.mark.flaky(reruns=3) +def test_load_workspace_sleep( + tmp_path: pathlib.Path, + server: libtmux.Server, + monkeypatch: pytest.MonkeyPatch, + yaml, + sleep: int, + output, +): + yaml_config = tmp_path / "simple.yaml" + yaml_config.write_text( + yaml, + encoding="utf-8", + ) + sconfig = kaptan.Kaptan(handler="yaml") + sconfig = sconfig.import_config(str(yaml_config)).get() + sconfig = config.expand(sconfig) + sconfig = config.trickle(sconfig) + builder = WorkspaceBuilder(sconf=sconfig, server=server) + builder.build() + + t = time.process_time() + + time.sleep(1) + session = builder.session + pane = session.attached_pane + + while (time.process_time() - t) * 1000 < sleep: + captured_pane = "\n".join(pane.capture_pane()) + + assert output not in captured_pane + time.sleep(0.1) + captured_pane = "\n".join(pane.capture_pane()) + assert output in captured_pane diff --git a/tmuxp/workspacebuilder.py b/tmuxp/workspacebuilder.py index 3bb8f499702..4f085b1e15a 100644 --- a/tmuxp/workspacebuilder.py +++ b/tmuxp/workspacebuilder.py @@ -5,6 +5,7 @@ """ import logging +import time from libtmux.exc import TmuxSessionExists from libtmux.pane import Pane @@ -363,11 +364,21 @@ def get_pane_shell(): suppress = True enter = pconf.get("enter", True) + sleep_before = pconf.get("sleep_before", None) + sleep_after = pconf.get("sleep_after", None) for cmd in pconf["shell_command"]: enter = cmd.get("enter", enter) + sleep_before = cmd.get("sleep_before", sleep_before) + sleep_after = cmd.get("sleep_after", sleep_after) + + if sleep_before is not None: + time.sleep(sleep_before) p.send_keys(cmd["cmd"], suppress_history=suppress, enter=enter) + if sleep_after is not None: + time.sleep(sleep_after) + if "focus" in pconf and pconf["focus"]: w.select_pane(p["pane_id"])