Skip to content

Commit 9e45750

Browse files
author
Bob Farrell
committed
No more autoindent after a SyntaxError
This had the side-effect of me discovering the showsyntaxerror method on code.InteractiveInterpreter, which I have now overridden and passed through my own routines, so these errors are now in the same colour as the other exceptions. Hooray. committer: Bob Farrell <bob@keys.(none)>
1 parent 9cc760b commit 9e45750

2 files changed

Lines changed: 101 additions & 39 deletions

File tree

‎CHANGELOG‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ break but didn't manage to fix the code to reflect this - thank you!
1010
Jørgen Tjernø has fixed up the autoindentation issues we encountered when
1111
bringing soft tabs in.
1212

13+
SyntaxError, ValueError and OverflowError are now caught properly
14+
(code.InteractiveInterpreter treats these as different to other exceptions as
15+
it doesn't print the whole traceback, so a different handler is called). This
16+
was discovered as I was trying to stop autoindentation from occurring on a
17+
SyntaxError, which has also been fixed.
18+
1319
v0.7.0
1420
======
1521
C-d behaviour changed so it no longer exits if the current line isn't empty.

‎bpython/cli.py‎

Lines changed: 95 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,10 @@
5959

6060
stdscr = None
6161

62-
6362
class Struct(object):
64-
pass # When we inherit, a __dict__ is added (object uses slots)
63+
"""Simple class for instantiating objects we can add arbitrary attributes
64+
to and use for various arbitrary things."""
65+
pass
6566

6667

6768
class FakeStdin(object):
@@ -158,6 +159,43 @@ def make_colours():
158159

159160

160161
class Interpreter(code.InteractiveInterpreter):
162+
163+
def __init__(self):
164+
"""The syntaxerror callback can be set at any time and will be called
165+
on a caught syntax error. The purpose for this in bpython is so that
166+
the repl can be instantiated after the interpreter (which it
167+
necessarily must be with the current factoring) and then an exception
168+
callback can be added to the Interpeter instance afterwards - more
169+
specifically, this is so that autoindentation does not occur after a
170+
traceback."""
171+
172+
self.syntaxerror_callback = None
173+
# Unfortunately code.InteractiveInterpreter is a classic class, so no super()
174+
code.InteractiveInterpreter.__init__(self)
175+
176+
def showsyntaxerror(self, filename=None):
177+
"""Override the regular handler, the code's copied and pasted from
178+
code.py, as per showtraceback, but with the syntaxerror callback called
179+
and the text in a pretty colour."""
180+
if self.syntaxerror_callback is not None:
181+
self.syntaxerror_callback()
182+
183+
type, value, sys.last_traceback = sys.exc_info()
184+
sys.last_type = type
185+
sys.last_value = value
186+
if filename and type is SyntaxError:
187+
# Work hard to stuff the correct filename in the exception
188+
try:
189+
msg, (dummy_filename, lineno, offset, line) = value
190+
except:
191+
# Not the format we expect; leave it alone
192+
pass
193+
else:
194+
# Stuff in the right filename
195+
value = SyntaxError(msg, (filename, lineno, offset, line))
196+
sys.last_value = value
197+
list = traceback.format_exception_only(type, value)
198+
map(self.writetb, list)
161199

162200
def showtraceback(self):
163201
"""This needs to override the default traceback thing
@@ -280,8 +318,8 @@ def cw(self):
280318
left of the cursor"""
281319

282320
if self.cpos:
283-
# I don't know if autocomplete should be disabled if the cursor
284-
# isn't at the end of the line, but that's what this does for now.
321+
# I don't know if autocomplete should be disabled if the cursor
322+
# isn't at the end of the line, but that's what this does for now.
285323
return
286324

287325
l = len(self.s)
@@ -318,7 +356,7 @@ def getpydocspec(f, func):
318356
return None
319357

320358
args = [i.strip() for i in s.groups()[1].split(',')]
321-
return [func, (args, None, None, None)]#None, None, None]
359+
return [func, (args, None, None, None)]
322360

323361
def getargspec(func):
324362
try:
@@ -330,7 +368,8 @@ def getargspec(func):
330368
else:
331369
try:
332370
f = eval(func, self.interp.locals)
333-
except Exception: # Same deal with the exceptions :(
371+
except Exception:
372+
# Same deal with the exceptions :(
334373
return None
335374

336375
try:
@@ -339,8 +378,6 @@ def getargspec(func):
339378
else:
340379
argspec = inspect.getargspec(f)
341380
self.argspec = [func, argspec]
342-
#[0]]#"Args for %s: " + ", ".join(argspec[0])
343-
#self.argspec = self.argspec % func
344381
return True
345382

346383
except (NameError, TypeError, KeyError):
@@ -349,7 +386,8 @@ def getargspec(func):
349386
return None
350387
self.argspec = t
351388
return True
352-
except AttributeError: # no __init__
389+
except AttributeError:
390+
# This happens if no __init__ is found
353391
return None
354392

355393
def parse_parens(s):
@@ -423,9 +461,10 @@ def _complete(self, unused_tab=False):
423461

424462
try:
425463
self.completer.complete(cw, 0)
426-
except Exception: # This sucks, but it's either that or list all the
427-
# exceptions that could possibly be raised here, so if anyone wants to do that,
428-
# feel free to send me a patch.
464+
except Exception:
465+
# This sucks, but it's either that or list all the exceptions that could
466+
# possibly be raised here, so if anyone wants to do that, feel free to send me
467+
# a patch.
429468
e = True
430469
else:
431470
e = False
@@ -435,7 +474,7 @@ def _complete(self, unused_tab=False):
435474
return False
436475

437476
if not e and self.completer.matches:
438-
# remove duplicates and restore order
477+
# remove duplicates and restore order
439478
self.matches = sorted(set(self.completer.matches))
440479

441480
if len(self.matches) == 1 and not OPTS.auto_display_list:
@@ -470,7 +509,6 @@ def show_list(self, items, topline=None):
470509
height_offset = 0
471510

472511
def lsize():
473-
# wl = longest word length (and a space)
474512
wl = max(len(i) for i in v_items) + 1
475513
if not wl:
476514
wl = 1
@@ -490,7 +528,7 @@ def lsize():
490528
return True
491529

492530
if items:
493-
# visible items (we'll append until we can't fit any more in)
531+
# visible items (we'll append until we can't fit any more in)
494532
v_items = [items[0][:max_w-3]]
495533
lsize()
496534
else:
@@ -527,7 +565,7 @@ def lsize():
527565
if height_offset and display_rows+5 >= max_h:
528566
del v_items[-(cols * (height_offset)):]
529567

530-
self.list_win.resize(rows+2, w)#(cols + 1) * wl + 3)
568+
self.list_win.resize(rows+2, w)
531569

532570
if down:
533571
self.list_win.mvwin(y+1, 0)
@@ -807,7 +845,7 @@ def reevaluate(self):
807845

808846
self.evaluating = False
809847
#map(self.push, self.history)
810-
#^-- That's how simple this function was at first :(
848+
#^-- That's how simple this method was at first :(
811849

812850
def prompt(self, more):
813851
"""Show the appropriate Python prompt"""
@@ -1004,7 +1042,8 @@ def bs(self, delete_tabs=True):
10041042
y -= 1
10051043
x = gethw()[1]
10061044

1007-
if not self.cpos: # I know the nested if blocks look nasty. :(
1045+
if not self.cpos:
1046+
# I know the nested if blocks look nasty. :(
10081047
if self.atbol() and delete_tabs:
10091048
n = len(self.s) % OPTS.tab_length
10101049
if not n:
@@ -1021,10 +1060,10 @@ def bs(self, delete_tabs=True):
10211060

10221061
def bs_word(self):
10231062
pos = len(self.s) - self.cpos - 1
1024-
# First we delete any space to the left of the cursor.
1063+
# First we delete any space to the left of the cursor.
10251064
while pos >= 0 and self.s[pos] == ' ':
10261065
pos -= self.bs()
1027-
# Then we delete a full word.
1066+
# Then we delete a full word.
10281067
while pos >= 0 and self.s[pos] != ' ':
10291068
pos -= self.bs()
10301069

@@ -1202,7 +1241,7 @@ def strbase(self, l):
12021241

12031242
sl = sorted(l, key=str.__len__)
12041243
for i, c in enumerate(l[-1]):
1205-
# I hate myself. Please email seamusmb@gmail.com to call him an dickhead for
1244+
# I hate myself. Please email seamusmb@gmail.com to call him a dickhead for
12061245
# insisting that I make bpython 2.4-compatible. I couldn't be bothered
12071246
# refactoring, so ghetto all() it is:
12081247
if not reduce(lambda x, y: (x and y) or False,
@@ -1284,12 +1323,20 @@ def get_line(self):
12841323
idiot)."""
12851324

12861325
self.ts = ''
1287-
n_indent = re.split('[^\t]', self.s, 1)[0].count('\t')
1288-
indent = self.s.endswith(':')
1326+
1327+
indent_spaces = 0
1328+
for c in self.s:
1329+
if c == ' ':
1330+
indent_spaces += 1
1331+
else:
1332+
break
1333+
1334+
indent = self.s.rstrip().endswith(':')
1335+
12891336
self.s = ''
12901337
self.iy, self.ix = self.scr.getyx()
12911338

1292-
for _ in range(n_indent):
1339+
for _ in range(indent_spaces // OPTS.tab_length):
12931340
self.c = '\t'
12941341
self.p_key()
12951342

@@ -1305,18 +1352,25 @@ def get_line(self):
13051352
if self.p_key() is None:
13061353
return self.s
13071354

1355+
def clear_current_line(self):
1356+
"""This is used as the exception callback for the Interpreter instance.
1357+
It prevents autoindentation from occuring after a traceback."""
1358+
1359+
self.s = ''
1360+
13081361
def get_key(self):
13091362
while True:
13101363
if self.idle:
13111364
self.idle(self)
13121365
try:
13131366
key = self.scr.getkey()
1314-
except curses.error: # I'm quite annoyed with the ambiguity of
1315-
# this exception handler. I previously caught "curses.error, x" and accessed
1316-
# x.message and checked that it was "no input", which seemed a crappy way of
1317-
# doing it. But then I ran it on a different computer and the exception
1318-
# seems to have entirely different attributes. So let's hope getkey() doesn't
1319-
# raise any other crazy curses exceptions. :)
1367+
except curses.error:
1368+
# I'm quite annoyed with the ambiguity of this exception handler. I previously
1369+
# caught "curses.error, x" and accessed x.message and checked that it was "no
1370+
# input", which seemed a crappy way of doing it. But then I ran it on a
1371+
# different computer and the exception seems to have entirely different
1372+
# attributes. So let's hope getkey() doesn't raise any other crazy curses
1373+
# exceptions. :)
13201374
continue
13211375
else:
13221376
return key
@@ -1476,9 +1530,9 @@ def init_wins(scr, cols):
14761530

14771531
main_win = curses.newwin(h-1, w, 0, 0)
14781532
main_win.scrollok(True)
1479-
main_win.keypad(1) # Thanks to Angus Gibson for pointing out
1480-
# this missing line which was causing problems that needed dirty
1481-
# hackery to fix. :)
1533+
main_win.keypad(1)
1534+
# Thanks to Angus Gibson for pointing out this missing line which was causing
1535+
# problems that needed dirty hackery to fix. :)
14821536

14831537
statusbar = Statusbar(scr, main_win,
14841538
".:: <C-d> Exit <C-r> Rewind <F2> Save <F8> Pastebin ::.",
@@ -1533,7 +1587,8 @@ def do_resize(caller):
15331587
global DO_RESIZE
15341588
h, w = gethw()
15351589
if not h:
1536-
return # Hopefully this shouldn't happen. :)
1590+
# Hopefully this shouldn't happen. :)
1591+
return
15371592

15381593
curses.endwin()
15391594
os.environ["LINES"] = str(h)
@@ -1543,8 +1598,7 @@ def do_resize(caller):
15431598

15441599
caller.resize()
15451600
caller.statusbar.resize()
1546-
# The list win resizes itself every time it appears so no need to do it
1547-
# here.
1601+
# The list win resizes itself every time it appears so no need to do it here.
15481602

15491603

15501604
def loadrc():
@@ -1629,6 +1683,8 @@ def main_curses(scr):
16291683
interpreter = Interpreter()
16301684

16311685
repl = Repl(main_win, interpreter, statusbar, idle)
1686+
interpreter.syntaxerror_callback = repl.clear_current_line
1687+
16321688
repl._C = cols
16331689

16341690
sys.stdout = repl
@@ -1650,8 +1706,8 @@ def main():
16501706
o = curses.wrapper(main_curses)
16511707
except:
16521708
tb = traceback.format_exc()
1653-
# I don't know why this is necessary; without it the wrapper doesn't always
1654-
# do its job.
1709+
# I don't know why this is necessary; without it the wrapper doesn't always do
1710+
# its job.
16551711
if stdscr is not None:
16561712
stdscr.keypad(0)
16571713
curses.echo()
@@ -1663,7 +1719,7 @@ def main():
16631719
print tb
16641720
sys.exit(1)
16651721

1666-
# Fake stdout data so everything's still visible after exiting
1722+
# Fake stdout data so everything's still visible after exiting
16671723
sys.stdout.write(o)
16681724
sys.stdout.flush()
16691725

0 commit comments

Comments
 (0)