Skip to content

Commit 36f37ea

Browse files
committed
Automatically detect the best shell
1 parent 5e6c56f commit 36f37ea

File tree

3 files changed

+166
-14
lines changed

3 files changed

+166
-14
lines changed

tests/test_cli.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ def test_load_zsh_autotitle_warning(cli_args, tmpdir, monkeypatch):
407407
assert 'Please set' not in result.output
408408

409409

410-
@pytest.mark.parametrize("cli_cmd", ['shell', ('shell', '--use-pdb')])
410+
@pytest.mark.parametrize("cli_cmd", ['shell', ('shell', '--pdb')])
411411
@pytest.mark.parametrize(
412412
"cli_args,inputs,env,expected_output",
413413
[
@@ -516,7 +516,7 @@ def test_shell(
516516
assert expected_output.format(**template_ctx) in result.output
517517

518518

519-
@pytest.mark.parametrize("cli_cmd", ['shell', ('shell', '--use-pdb')])
519+
@pytest.mark.parametrize("cli_cmd", ['shell', ('shell', '--pdb')])
520520
@pytest.mark.parametrize(
521521
"cli_args,inputs,env,template_ctx,exception,message",
522522
[

tmuxp/cli.py

+11-8
Original file line numberDiff line numberDiff line change
@@ -671,20 +671,22 @@ def startup(config_dir):
671671
'command',
672672
help='Instead of opening shell, execute python code in libtmux and exit',
673673
)
674-
@click.option(
675-
'--use-pdb/--no-pdb',
676-
'use_pdb',
677-
help='Use pdb / breakpoint() instead of code.interact()',
678-
default=False,
679-
)
680674
@click.option(
681675
'--use-pythonrc/--no-startup',
682676
'use_pythonrc',
683677
help='Load the PYTHONSTARTUP environment variable and ~/.pythonrc.py script.',
684678
default=False,
685679
)
680+
@click.option('--pdb', 'shell', flag_value='pdb', help='Use plain pdb')
681+
@click.option(
682+
'--code', 'shell', flag_value='code', help='Use stdlib\'s code.interact()'
683+
)
684+
@click.option('--ptipython', 'shell', flag_value='pdb', help='Use ptpython + ipython')
685+
@click.option('--ptpython', 'shell', flag_value='pdb', help='Use ptpython')
686+
@click.option('--ipython', 'shell', flag_value='pdb', help='Use ipython')
687+
@click.option('--bpython', 'shell', flag_value='pdb', help='Use bpython')
686688
def command_shell(
687-
session_name, window_name, socket_name, socket_path, command, use_pdb, use_pythonrc
689+
session_name, window_name, socket_name, socket_path, command, shell, use_pythonrc
688690
):
689691
"""Launch python shell for tmux server, session, window and pane.
690692
@@ -713,7 +715,7 @@ def command_shell(
713715
if command is not None:
714716
exec(command)
715717
else:
716-
if use_pdb or (os.getenv('PYTHONBREAKPOINT') and PY3 and PYMINOR >= 7):
718+
if shell == 'pdb' or (os.getenv('PYTHONBREAKPOINT') and PY3 and PYMINOR >= 7):
717719
from ._compat import breakpoint as tmuxp_breakpoint
718720

719721
tmuxp_breakpoint()
@@ -727,6 +729,7 @@ def command_shell(
727729
window=window,
728730
pane=pane,
729731
use_pythonrc=use_pythonrc,
732+
shell=shell,
730733
)
731734

732735

tmuxp/shell.py

+153-4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import logging
1111
import os
12+
import traceback
1213

1314
logger = logging.getLogger(__name__)
1415

@@ -51,12 +52,132 @@ def has_ptipython():
5152
return True
5253

5354

54-
def launch(shell='best', use_pythonrc=False, **kwargs):
55-
import code
55+
def has_bpython():
56+
try:
57+
from bpython import embed # NOQA F841
58+
except ImportError:
59+
return False
60+
return True
61+
62+
63+
def detect_best_shell():
64+
if has_ptipython():
65+
return 'ptipython'
66+
elif has_ptpython():
67+
return 'ptpython'
68+
elif has_ipython():
69+
return 'ipython'
70+
elif has_bpython():
71+
return 'bpython'
72+
return 'code'
73+
74+
75+
def get_bpython(options, extra_args):
76+
try:
77+
from bpython import embed # NOQA F841
78+
except ImportError:
79+
return traceback.format_exc()
80+
81+
def launch_bpython():
82+
imported_objects = get_launch_args(**options)
83+
kwargs = {}
84+
if extra_args:
85+
kwargs['args'] = extra_args
86+
embed(imported_objects, **kwargs)
87+
88+
return launch_bpython
89+
90+
91+
def get_ipython_arguments():
92+
ipython_args = 'IPYTHON_ARGUMENTS'
93+
return os.environ.get(ipython_args, '').split()
94+
95+
96+
def get_ipython(options, **extra_args):
97+
try:
98+
from IPython import start_ipython
99+
100+
def launch_ipython():
101+
imported_objects = get_launch_args(**options)
102+
ipython_arguments = extra_args or get_ipython_arguments()
103+
start_ipython(argv=ipython_arguments, user_ns=imported_objects)
104+
105+
return launch_ipython
106+
except ImportError:
107+
str_exc = traceback.format_exc()
108+
# IPython < 0.11
109+
# Explicitly pass an empty list as arguments, because otherwise
110+
# IPython would use sys.argv from this script.
111+
# Notebook not supported for IPython < 0.11.
112+
try:
113+
from IPython.Shell import IPShell
114+
except ImportError:
115+
return str_exc + "\n" + traceback.format_exc()
116+
117+
def launch_ipython():
118+
imported_objects = get_launch_args(**options)
119+
shell = IPShell(argv=[], user_ns=imported_objects)
120+
shell.mainloop()
121+
122+
return launch_ipython
123+
56124

125+
def get_ptpython(self, options):
126+
try:
127+
from ptpython.repl import embed, run_config
128+
except ImportError:
129+
tb = traceback.format_exc()
130+
try: # prompt_toolkit < v0.27
131+
from prompt_toolkit.contrib.repl import embed, run_config
132+
except ImportError:
133+
return tb
134+
135+
def run_ptpython():
136+
imported_objects = get_launch_args(**options)
137+
history_filename = os.path.expanduser('~/.ptpython_history')
138+
embed(
139+
globals=imported_objects,
140+
history_filename=history_filename,
141+
vi_mode=options['vi_mode'],
142+
configure=run_config,
143+
)
144+
145+
return run_ptpython
146+
147+
148+
def get_ptipython(options):
149+
"""Based on django-extensions
150+
151+
Run renamed to launch, get_imported_objects renamed to get_launch_args
152+
"""
153+
try:
154+
from ptpython.ipython import embed
155+
from ptpython.repl import run_config
156+
except ImportError:
157+
tb = traceback.format_exc()
158+
try: # prompt_toolkit < v0.27
159+
from prompt_toolkit.contrib.ipython import embed
160+
from prompt_toolkit.contrib.repl import run_config
161+
except ImportError:
162+
return tb
163+
164+
def launch_ptipython():
165+
imported_objects = get_launch_args(**options)
166+
history_filename = os.path.expanduser('~/.ptpython_history')
167+
embed(
168+
user_ns=imported_objects,
169+
history_filename=history_filename,
170+
vi_mode=options['vi_mode'],
171+
configure=run_config,
172+
)
173+
174+
return launch_ptipython
175+
176+
177+
def get_launch_args(**kwargs):
57178
import libtmux
58179

59-
imported_objects = {
180+
return {
60181
'libtmux': libtmux,
61182
'Server': libtmux.Server,
62183
'Session': libtmux.Session,
@@ -68,6 +189,10 @@ def launch(shell='best', use_pythonrc=False, **kwargs):
68189
'pane': kwargs.get('pane'),
69190
}
70191

192+
193+
def get_code(use_pythonrc, imported_objects):
194+
import code
195+
71196
try:
72197
# Try activating rlcompleter, because it's handy.
73198
import readline
@@ -108,4 +233,28 @@ def launch(shell='best', use_pythonrc=False, **kwargs):
108233

109234
traceback.print_exc()
110235

111-
code.interact(local=imported_objects)
236+
def launch_code():
237+
code.interact(local=imported_objects)
238+
239+
return launch_code
240+
241+
242+
def launch(shell='best', use_pythonrc=False, **kwargs):
243+
# Also allowing passing shell='code' to force using code.interact
244+
imported_objects = get_launch_args(**kwargs)
245+
246+
if shell == 'best':
247+
shell = detect_best_shell()
248+
249+
if shell == 'ptipython':
250+
launch = get_ptipython(options=kwargs)
251+
elif shell == 'ptpython':
252+
launch = get_ptpython(options=kwargs)
253+
elif shell == 'ipython':
254+
launch = get_ipython(options=kwargs)
255+
elif shell == 'bpython':
256+
launch = get_bpython(options=kwargs)
257+
else:
258+
launch = get_code(use_pythonrc=use_pythonrc, imported_objects=imported_objects)
259+
260+
launch()

0 commit comments

Comments
 (0)