Skip to content

Commit 0ca376f

Browse files
authored
New command: tmuxp shell (#636)
'tmuxp shell' will open a pdb in python with the server, session, window, and pane object from the server's currently open (via env variable of TMUX_PANE) or attached pane via obj.attached_(sesson,window,pane). tmuxp shell -c 'cmd': direct python commands to the shell
2 parents 6ef9356 + 2cf24d5 commit 0ca376f

File tree

8 files changed

+401
-4
lines changed

8 files changed

+401
-4
lines changed

CHANGES

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,42 @@ Here you can find the recent changes to tmuxp
77
current
88
-------
99
- *Insert changes/features/fixes for next release here*
10+
- :issue:`636` New command: ``tmuxp shell``
11+
12+
Automatically preloads session, window, and pane via `libtmux`_
13+
api objects and makes them available in a python console.
14+
15+
.. image:: _static/tmuxp-shell.gif
16+
:width: 100%
17+
18+
In python 3.7+, supports ``PYTHONBREAKPOINT``:
19+
20+
.. code-block:: sh
21+
22+
$ pip install ipdb
23+
$ env PYTHONBREAKPOINT=ipdb.set_trace tmuxp shell
24+
25+
You can execute python directly via ``-c``:
26+
27+
.. code-block:: sh
28+
29+
$ tmuxp shell -c 'print(session.name); print(window.name)'
30+
my_server
31+
my_window
32+
33+
$ tmuxp shell my_server -c 'print(session.name); print(window.name)'
34+
my_server
35+
my_window
36+
37+
$ tmuxp shell my_server my_window -c 'print(session.name); print(window.name)'
38+
my_server
39+
my_window
40+
41+
$ tmuxp shell my_server my_window -c 'print(window.name.upper())'
42+
MY_WINDOW
1043
1144
tmuxp 1.5.8 (2020-10-31)
12-
-----------------------
45+
------------------------
1346
- :issue:`639` Passes start_directory through to new tmux session
1447
Fixes :issue:`631`, thank you @joseph-flinn!
1548

README.rst

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,50 @@ Load your tmuxp config from anywhere by using the filename, assuming
8282
8383
See `author's tmuxp configs`_ and the projects' `tmuxp.yaml`_.
8484

85+
Shell
86+
-----
87+
*New in 1.6.0*:
88+
89+
``tmuxp shell`` launches into a python console preloaded with the attached server,
90+
session, and window in `libtmux`_ objects.
91+
92+
.. code-block:: shell
93+
94+
$ tmuxp shell
95+
96+
(Pdb) server
97+
<libtmux.server.Server object at 0x7f7dc8e69d10>
98+
(Pdb) server.sessions
99+
[Session($1 your_project)]
100+
(Pdb) session
101+
Session($1 your_project)
102+
(Pdb) session.name
103+
'your_project'
104+
(Pdb) window
105+
Window(@3 1:your_window, Session($1 your_project))
106+
(Pdb) window.name
107+
'your_window'
108+
(Pdb) window.panes
109+
[Pane(%6 Window(@3 1:your_window, Session($1 your_project)))
110+
(Pdb) pane
111+
Pane(%6 Window(@3 1:your_window, Session($1 your_project))
112+
113+
Python 3.7+ supports `PEP 553`_ ``breakpoint()`` (including
114+
``PYTHONBREAKPOINT``). Also supports direct commands via ``-c``:
115+
116+
.. code-block:: shell
117+
118+
$ tmuxp shell -c 'print(window.name)'
119+
my_window
120+
121+
$ tmuxp shell -c 'print(window.name.upper())'
122+
MY_WINDOW
123+
124+
Read more on `tmuxp shell`_ in the CLI docs.
125+
126+
.. _PEP 553: https://www.python.org/dev/peps/pep-0553/
127+
.. _tmuxp shell: http://localhost:8031/cli.html#shell
128+
85129
Pre-load hook
86130
-------------
87131
Run custom startup scripts (such as installing project dependencies before

docs/_static/tmuxp-shell.gif

150 KB
Loading

docs/cli.rst

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,85 @@ In zsh (``~/.zshrc``):
2222
2323
eval "$(_TMUXP_COMPLETE=source_zsh tmuxp)"
2424
25+
.. _cli_shell:
26+
27+
Shell
28+
-----
29+
30+
::
31+
32+
tmuxp shell
33+
34+
tmuxp shell <session_name>
35+
36+
tmuxp shell <session_name> <window_name>
37+
38+
tmuxp shell -c 'python code'
39+
40+
Launch into a python console with `libtmux`_ objects. Compare to django's shell.
41+
42+
.. image:: _static/tmuxp-shell.gif
43+
:width: 100%
44+
45+
Automatically preloads current tmux :class:`server <libtmux.Server>`,
46+
:class:`session <libtmux.Session>`, :class:`window <libtmux.Window>`
47+
:class:`pane <libtmux.Pane>`. Pass additional arguments to select a
48+
specific one of your choice::
49+
50+
(Pdb) server
51+
<libtmux.server.Server object at 0x7f7dc8e69d10>
52+
(Pdb) server.sessions
53+
[Session($1 your_project)]
54+
(Pdb) session
55+
Session($1 your_project)
56+
(Pdb) session.name
57+
'your_project'
58+
(Pdb) window
59+
Window(@3 1:your_window, Session($1 your_project))
60+
(Pdb) window.name
61+
'your_window'
62+
(Pdb) window.panes
63+
[Pane(%6 Window(@3 1:your_window, Session($1 your_project)))
64+
(Pdb) pane
65+
Pane(%6 Window(@3 1:your_window, Session($1 your_project)))
66+
67+
Python 3.7 supports `PEP 553`_'s ``PYTHONBREAKPOINT`` and supports
68+
compatible debuggers, for instance `ipdb`_:
69+
70+
.. code-block:: sh
71+
72+
$ pip install ipdb
73+
$ env PYTHONBREAKPOINT=ipdb.set_trace tmuxp shell
74+
75+
You can also pass in python code directly, similar to ``python -c``, do
76+
this via ``tmuxp -c``:
77+
78+
.. code-block:: shell
79+
80+
$ tmuxp shell -c 'print(session.name); print(window.name)'
81+
my_server
82+
my_window
83+
84+
$ tmuxp shell my_server -c 'print(session.name); print(window.name)'
85+
my_server
86+
my_window
87+
88+
$ tmuxp shell my_server my_window -c 'print(session.name); print(window.name)'
89+
my_server
90+
my_window
91+
92+
$ tmuxp shell my_server my_window -c 'print(window.name.upper())'
93+
MY_WINDOW
94+
95+
# Assuming inside a tmux pane or one is attached on default server
96+
$ tmuxp shell -c 'print(pane.id); print(pane.window.name)'
97+
%2
98+
my_window
99+
100+
.. _PEP 553: https://www.python.org/dev/peps/pep-0553/
101+
.. _ipdb: https://pypi.org/project/ipdb/
102+
.. _libtmux: https://libtmux.git-pull.com
103+
25104
.. _cli_freeze:
26105

27106
Freeze sessions

tests/conftest.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,14 @@
1212

1313

1414
@pytest.fixture(scope='function')
15-
def server(request):
15+
def socket_name(request):
16+
return 'tmuxp_test%s' % next(namer)
17+
18+
19+
@pytest.fixture(scope='function')
20+
def server(request, socket_name):
1621
t = Server()
17-
t.socket_name = 'tmuxp_test%s' % next(namer)
22+
t.socket_name = socket_name
1823

1924
def fin():
2025
t.kill_server()

tests/test_cli.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import libtmux
1414
from libtmux.common import has_lt_version
15+
from libtmux.exc import LibTmuxException
1516
from tmuxp import cli, config
1617
from tmuxp.cli import (
1718
command_ls,
@@ -406,6 +407,142 @@ def test_load_zsh_autotitle_warning(cli_args, tmpdir, monkeypatch):
406407
assert 'Please set' not in result.output
407408

408409

410+
@pytest.mark.parametrize(
411+
"cli_args,inputs,env,expected_output",
412+
[
413+
(
414+
['shell', '-L{SOCKET_NAME}', '-c', 'print(str(server.socket_name))'],
415+
[],
416+
{},
417+
'{SERVER_SOCKET_NAME}',
418+
),
419+
(
420+
[
421+
'shell',
422+
'-L{SOCKET_NAME}',
423+
'{SESSION_NAME}',
424+
'-c',
425+
'print(session.name)',
426+
],
427+
[],
428+
{},
429+
'{SESSION_NAME}',
430+
),
431+
(
432+
[
433+
'shell',
434+
'-L{SOCKET_NAME}',
435+
'{SESSION_NAME}',
436+
'{WINDOW_NAME}',
437+
'-c',
438+
'print(server.has_session(session.name))',
439+
],
440+
[],
441+
{},
442+
'True',
443+
),
444+
(
445+
[
446+
'shell',
447+
'-L{SOCKET_NAME}',
448+
'{SESSION_NAME}',
449+
'{WINDOW_NAME}',
450+
'-c',
451+
'print(window.name)',
452+
],
453+
[],
454+
{},
455+
'{WINDOW_NAME}',
456+
),
457+
(
458+
[
459+
'shell',
460+
'-L{SOCKET_NAME}',
461+
'{SESSION_NAME}',
462+
'{WINDOW_NAME}',
463+
'-c',
464+
'print(pane.id)',
465+
],
466+
[],
467+
{},
468+
'{PANE_ID}',
469+
),
470+
(
471+
[
472+
'shell',
473+
'-L{SOCKET_NAME}',
474+
'-c',
475+
'print(pane.id)',
476+
],
477+
[],
478+
{'TMUX_PANE': '{PANE_ID}'},
479+
'{PANE_ID}',
480+
),
481+
],
482+
)
483+
def test_shell(
484+
cli_args, inputs, expected_output, env, tmpdir, monkeypatch, server, session
485+
):
486+
monkeypatch.setenv('HOME', str(tmpdir))
487+
window_name = 'my_window'
488+
window = session.new_window(window_name=window_name)
489+
window.split_window()
490+
491+
template_ctx = dict(
492+
SOCKET_NAME=server.socket_name,
493+
SOCKET_PATH=server.socket_path,
494+
SESSION_NAME=session.name,
495+
WINDOW_NAME=window_name,
496+
PANE_ID=window.attached_pane.id,
497+
SERVER_SOCKET_NAME=server.socket_name,
498+
)
499+
500+
cli_args[:] = [cli_arg.format(**template_ctx) for cli_arg in cli_args]
501+
for k, v in env.items():
502+
monkeypatch.setenv(k, v.format(**template_ctx))
503+
504+
with tmpdir.as_cwd():
505+
runner = CliRunner()
506+
507+
result = runner.invoke(
508+
cli.cli, cli_args, input=''.join(inputs), catch_exceptions=False
509+
)
510+
assert expected_output.format(**template_ctx) in result.output
511+
512+
513+
@pytest.mark.parametrize(
514+
"cli_args,inputs,env,exception, message",
515+
[
516+
(
517+
['shell', '-L{SOCKET_NAME}', '-c', 'print(str(server.socket_name))'],
518+
[],
519+
{},
520+
LibTmuxException,
521+
r'.*{SOCKET_NAME}\s\(No such file or directory\).*',
522+
),
523+
],
524+
)
525+
def test_shell_no_server(
526+
cli_args, inputs, env, exception, message, tmpdir, monkeypatch, socket_name
527+
):
528+
monkeypatch.setenv('HOME', str(tmpdir))
529+
template_ctx = dict(
530+
SOCKET_NAME=socket_name,
531+
)
532+
533+
cli_args[:] = [cli_arg.format(**template_ctx) for cli_arg in cli_args]
534+
for k, v in env.items():
535+
monkeypatch.setenv(k, v.format(**template_ctx))
536+
537+
with tmpdir.as_cwd():
538+
runner = CliRunner()
539+
540+
with pytest.raises(exception, match=message.format(**template_ctx)):
541+
runner.invoke(
542+
cli.cli, cli_args, input=''.join(inputs), catch_exceptions=False
543+
)
544+
545+
409546
@pytest.mark.parametrize(
410547
"cli_args",
411548
[

tmuxp/_compat.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,21 @@
33
import sys
44

55
PY2 = sys.version_info[0] == 2
6+
PY3 = sys.version_info[0] == 3
7+
PYMINOR = sys.version_info[1]
8+
PYPATCH = sys.version_info[2]
69

710
_identity = lambda x: x
811

912

13+
if PY3 and PYMINOR >= 7:
14+
breakpoint = breakpoint
15+
else:
16+
import pdb
17+
18+
breakpoint = pdb.set_trace
19+
20+
1021
if PY2:
1122
unichr = unichr
1223
text_type = unicode

0 commit comments

Comments
 (0)