Skip to content

Commit 8edeadc

Browse files
committed
add new interactive interpreter subclass with pretty pygments traceback coloring
1 parent 0533c80 commit 8edeadc

File tree

2 files changed

+170
-1
lines changed

2 files changed

+170
-1
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import code
2+
import traceback
3+
import sys
4+
from pygments.style import Style
5+
from pygments.token import *
6+
from pygments.formatter import Formatter
7+
from curtsies.bpythonparse import parse
8+
from codeop import CommandCompiler, compile_command
9+
from pygments.lexers import get_lexer_by_name
10+
from pygments.styles import get_style_by_name
11+
12+
default_colors = {
13+
Generic.Error:'R',
14+
Keyword:'d',
15+
Name:'c',
16+
Name.Builtin:'g',
17+
Comment:'b',
18+
String:'m',
19+
Error:'r',
20+
Literal:'d',
21+
Number:'M',
22+
Number.Integer:'d',
23+
Operator:'d',
24+
Punctuation:'d',
25+
Token:'d',
26+
Whitespace:'d',
27+
Token.Punctuation.Parenthesis:'R',
28+
Name.Function:'d',
29+
Name.Class:'d',
30+
}
31+
32+
33+
class BPythonFormatter(Formatter):
34+
"""This is subclassed from the custom formatter for bpython.
35+
Its format() method receives the tokensource
36+
and outfile params passed to it from the
37+
Pygments highlight() method and slops
38+
them into the appropriate format string
39+
as defined above, then writes to the outfile
40+
object the final formatted string.
41+
42+
See the Pygments source for more info; it's pretty
43+
straightforward."""
44+
45+
def __init__(self, color_scheme, **options):
46+
self.f_strings = {}
47+
for k, v in color_scheme.iteritems():
48+
self.f_strings[k] = '\x01%s' % (v,)
49+
Formatter.__init__(self, **options)
50+
51+
def format(self, tokensource, outfile):
52+
o = ''
53+
54+
for token, text in tokensource:
55+
while token not in self.f_strings:
56+
token = token.parent
57+
o += "%s\x03%s\x04" % (self.f_strings[token], text)
58+
outfile.write(str(parse(o.rstrip())))
59+
60+
class Interp(code.InteractiveInterpreter):
61+
def __init__(self, locals=None, outfile=sys.__stderr__):
62+
"""Constructor.
63+
64+
The optional 'locals' argument specifies the dictionary in
65+
which code will be executed; it defaults to a newly created
66+
dictionary with key "__name__" set to "__console__" and key
67+
"__doc__" set to None.
68+
69+
We include an argument for the outfile to pass to the formatter for it to write to.
70+
71+
"""
72+
if locals is None:
73+
locals = {"__name__": "__console__", "__doc__": None}
74+
self.locals = locals
75+
self.compile = CommandCompiler()
76+
self.outfile = outfile
77+
78+
def showsyntaxerror(self, filename=None):
79+
"""Display the syntax error that just occurred.
80+
81+
This doesn't display a stack trace because there isn't one.
82+
83+
If a filename is given, it is stuffed in the exception instead
84+
of what was there before (because Python's parser always uses
85+
"<string>" when reading from a string).
86+
87+
The output is written by self.write(), below.
88+
89+
"""
90+
type, value, sys.last_traceback = sys.exc_info()
91+
sys.last_type = type
92+
sys.last_value = value
93+
if filename and type is SyntaxError:
94+
# Work hard to stuff the correct filename in the exception
95+
try:
96+
msg, (dummy_filename, lineno, offset, line) = value
97+
except:
98+
# Not the format we expect; leave it alone
99+
pass
100+
else:
101+
# Stuff in the right filename
102+
value = SyntaxError(msg, (filename, lineno, offset, line))
103+
sys.last_value = value
104+
l = traceback.format_exception_only(type, value)
105+
tbtext = ''.join(l)
106+
lexer = get_lexer_by_name("pytb")
107+
traceback_informative_formatter = BPythonFormatter(default_colors)
108+
traceback_code_formatter = BPythonFormatter({Token: ('d')})
109+
tokens= list(lexer.get_tokens(tbtext))
110+
no_format_mode = False
111+
cur_line = []
112+
for token, text in tokens:
113+
if text.endswith('\n'):
114+
cur_line.append((token,text))
115+
if no_format_mode:
116+
traceback_code_formatter.format(cur_line,self.outfile)
117+
no_format_mode = False
118+
else:
119+
traceback_informative_formatter.format(cur_line,self.outfile)
120+
cur_line = []
121+
elif text == ' ' and cur_line == []:
122+
no_format_mode = True
123+
cur_line.append((token,text))
124+
else:
125+
cur_line.append((token,text))
126+
assert cur_line == [], cur_line
127+
128+
def showtraceback(self):
129+
"""Display the exception that just occurred.
130+
131+
We remove the first stack item because it is our own code.
132+
133+
134+
"""
135+
type, value, tb = sys.exc_info()
136+
sys.last_type = type
137+
sys.last_value = value
138+
sys.last_traceback = tb
139+
tblist = traceback.extract_tb(tb)
140+
del tblist[:1]
141+
l = traceback.format_list(tblist)
142+
if l:
143+
l.insert(0, "Traceback (most recent call last):\n")
144+
l[len(l):] = traceback.format_exception_only(type, value)
145+
tbtext = ''.join(l)
146+
lexer = get_lexer_by_name("pytb", stripall=True)
147+
traceback_informative_formatter = BPythonFormatter(default_colors)
148+
traceback_code_formatter = BPythonFormatter({Token: ('d')})
149+
tokens= list(lexer.get_tokens(tbtext))
150+
151+
no_format_mode = False
152+
cur_line = []
153+
for token, text in tokens:
154+
if text.endswith('\n'):
155+
cur_line.append((token,text))
156+
if no_format_mode:
157+
traceback_code_formatter.format(cur_line,self.outfile)
158+
no_format_mode = False
159+
else:
160+
traceback_informative_formatter.format(cur_line,self.outfile)
161+
cur_line = []
162+
elif text == ' ' and cur_line == []:
163+
no_format_mode = True
164+
cur_line.append((token,text))
165+
else:
166+
cur_line.append((token,text))
167+
assert cur_line == []
168+

bpython/curtsiesfrontend/repl.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from pygments import format
1818
from pygments.lexers import PythonLexer
1919
from pygments.formatters import TerminalFormatter
20+
from interpreter import Interp
2021

2122
import blessings
2223

@@ -237,7 +238,7 @@ def __init__(self,
237238
# would be unsafe because initial
238239
# state was passed in
239240
if interp is None:
240-
interp = code.InteractiveInterpreter(locals=locals_)
241+
interp = Interp(locals=locals_)
241242
if banner is None:
242243
banner = _('Welcome to bpython! Press <%s> for help.') % config.help_key
243244
config.autocomplete_mode = autocomplete.SIMPLE # only one implemented currently

0 commit comments

Comments
 (0)