Skip to content

Commit 4850400

Browse files
committed
Add show source feature.
1 parent f1cc92f commit 4850400

File tree

3 files changed

+49
-14
lines changed

3 files changed

+49
-14
lines changed

bpython/cli.py

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555

5656
# These are used for syntax hilighting.
5757
from pygments import format
58+
from pygments.formatters import TerminalFormatter
5859
from pygments.lexers import PythonLexer
5960
from pygments.token import Token
6061
from bpython.formatter import BPythonFormatter, Parenthesis
@@ -422,6 +423,7 @@ def __init__(self, scr, interp, statusbar=None, idle=None):
422423
self.matches = []
423424
self.matches_iter = MatchesIterator()
424425
self.argspec = None
426+
self.current_func = None
425427
self.s = ''
426428
self.inside_string = False
427429
self.highlighted_paren = None
@@ -523,6 +525,12 @@ def cw(self):
523525
i += 1
524526
return self.s[-i +1:]
525527

528+
def get_object(self, name):
529+
if name in self.interp.locals:
530+
return self.interp.locals[name]
531+
else:
532+
return eval(name, self.interp.locals)
533+
526534
def get_args(self):
527535
"""Check if an unclosed parenthesis exists, then attempt to get the
528536
argspec() for it. On success, update self.argspec and return True,
@@ -533,7 +541,8 @@ def get_args(self):
533541
if not OPTS.arg_spec:
534542
return False
535543

536-
# Find the name of the current function
544+
# Get the name of the current function and where we are in
545+
# the arguments
537546
stack = [['', 0, '']]
538547
try:
539548
for (token, value) in PythonLexer().get_tokens(self.s):
@@ -560,19 +569,14 @@ def get_args(self):
560569
func, _, _ = stack.pop()
561570
except IndexError:
562571
return False
572+
if not func:
573+
return False
563574

564-
# We found a name, now get a function object
565575
try:
566-
if func in self.interp.locals:
567-
f = self.interp.locals[func]
568-
except TypeError:
569-
return None
570-
else:
571-
try:
572-
f = eval(func, self.interp.locals)
573-
except Exception:
574-
# Same deal with the exceptions :(
575-
return None
576+
f = self.get_object(func)
577+
except (AttributeError, NameError):
578+
return False
579+
576580
if inspect.isclass(f):
577581
try:
578582
f = f.__init__
@@ -1565,6 +1569,22 @@ def p_key(self, key):
15651569
page(self.stdout_hist[self.prev_block_finished:-4])
15661570
return ''
15671571

1572+
elif key in key_dispatch[OPTS.show_source_key]:
1573+
try:
1574+
obj = self.current_func
1575+
if obj is None and inspection.is_eval_safe_name(self.s):
1576+
obj = self.get_object(self.s)
1577+
source = inspect.getsource(obj)
1578+
except (AttributeError, NameError, TypeError):
1579+
self.statusbar.message("Cannot show source.")
1580+
return ''
1581+
else:
1582+
if OPTS.highlight_show_source:
1583+
source = format(PythonLexer().get_tokens(source),
1584+
TerminalFormatter())
1585+
page(source)
1586+
return ''
1587+
15681588
elif key == '\n':
15691589
self.lf()
15701590
return None

bpython/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def loadini(struct, configfile):
3636
'auto_display_list': True,
3737
'color_scheme': 'default',
3838
'flush_output': True,
39+
'highlight_show_source': True,
3940
'hist_file': '~/.pythonhist',
4041
'hist_length': 100,
4142
'paste_time': 0.02,
@@ -53,6 +54,7 @@ def loadini(struct, configfile):
5354
'last_output': 'F9',
5455
'pastebin': 'F8',
5556
'save': 'C-s',
57+
'show_source': 'F2',
5658
'undo': 'C-r',
5759
'up_one_line': 'C-p',
5860
'yank_from_buffer': 'C-y'
@@ -66,11 +68,14 @@ def loadini(struct, configfile):
6668
struct.syntax = config.getboolean('general', 'syntax')
6769
struct.arg_spec = config.getboolean('general', 'arg_spec')
6870
struct.paste_time = config.getfloat('general', 'paste_time')
71+
struct.highlight_show_source = config.getboolean('general',
72+
'highlight_show_source')
6973
struct.hist_file = config.get('general', 'hist_file')
7074
struct.hist_length = config.getint('general', 'hist_length')
7175
struct.flush_output = config.getboolean('general', 'flush_output')
7276
struct.pastebin_key = config.get('keyboard', 'pastebin')
7377
struct.save_key = config.get('keyboard', 'save')
78+
struct.show_source_key = config.get('keyboard', 'show_source')
7479
struct.undo_key = config.get('keyboard', 'undo')
7580
struct.up_one_line_key = config.get('keyboard', 'up_one_line')
7681
struct.down_one_line_key = config.get('keyboard', 'down_one_line')

bpython/inspection.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434

3535
py3 = sys.version_info[0] == 3
3636

37+
if not py3:
38+
_name = re.compile(r'[a-zA-Z_]\w*')
39+
3740

3841
class AttrCleaner(object):
3942
"""A context manager that tries to make an object not exhibit side-effects
@@ -43,7 +46,7 @@ def __init__(self, obj):
4346

4447
def __enter__(self):
4548
"""Try to make an object not exhibit side-effects on attribute
46-
lookup."""
49+
lookup."""
4750
type_ = type(self.obj)
4851
__getattribute__ = None
4952
__getattr__ = None
@@ -198,7 +201,7 @@ def getargspec(func, f):
198201
argspec = inspect.getfullargspec(f)
199202
else:
200203
argspec = inspect.getargspec(f)
201-
204+
202205
argspec = list(argspec)
203206
fixlongargs(f, argspec)
204207
argspec = [func, argspec, is_bound_method]
@@ -211,3 +214,10 @@ def getargspec(func, f):
211214
argspec[1][0].insert(0, 'obj')
212215
argspec.append(is_bound_method)
213216
return argspec
217+
218+
219+
def is_eval_safe_name(string):
220+
if py3:
221+
return all(part.isidentifier() for part in string.split('.'))
222+
else:
223+
return all(_name.match(part) for part in string.split('.'))

0 commit comments

Comments
 (0)