From cbe6001626984ece6a8c67c8b11b83e55cd0115e 2010-08-23 02:07:06
From: Fernando Perez <Fernando.Perez@berkeley.edu>
Date: 2010-08-23 02:07:06
Subject: [PATCH] Improvements to exception handling to transport structured tracebacks.

This code is still fairly hackish, but we're starting to get there.
We still need to improve the api, because right now runlines() does
way too much, and I had to set the exception state in a temporary
private variable.  But client-side things are working better, so we
can continue fixing the kernel without bottlenecking Evan.

---

diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py
index 711b2f9..edf5c9c 100644
--- a/IPython/core/interactiveshell.py
+++ b/IPython/core/interactiveshell.py
@@ -29,6 +29,7 @@ import sys
 import tempfile
 from contextlib import nested
 
+from IPython.config.configurable import Configurable
 from IPython.core import debugger, oinspect
 from IPython.core import history as ipcorehist
 from IPython.core import prefilter
@@ -36,8 +37,8 @@ from IPython.core import shadowns
 from IPython.core import ultratb
 from IPython.core.alias import AliasManager
 from IPython.core.builtin_trap import BuiltinTrap
-from IPython.config.configurable import Configurable
 from IPython.core.display_trap import DisplayTrap
+from IPython.core.displayhook import DisplayHook
 from IPython.core.error import UsageError
 from IPython.core.extensions import ExtensionManager
 from IPython.core.fakemodule import FakeModule, init_fakemod_dict
@@ -47,24 +48,22 @@ from IPython.core.magic import Magic
 from IPython.core.payload import PayloadManager
 from IPython.core.plugin import PluginManager
 from IPython.core.prefilter import PrefilterManager
-from IPython.core.displayhook import DisplayHook
-import IPython.core.hooks
 from IPython.external.Itpl import ItplNS
 from IPython.utils import PyColorize
+from IPython.utils import io
 from IPython.utils import pickleshare
 from IPython.utils.doctestreload import doctest_reload
+from IPython.utils.io import ask_yes_no, rprint
 from IPython.utils.ipstruct import Struct
-import IPython.utils.io
-from IPython.utils.io import ask_yes_no
 from IPython.utils.path import get_home_dir, get_ipython_dir, HomeDirError
 from IPython.utils.process import getoutput, getoutputerror
 from IPython.utils.strdispatch import StrDispatch
 from IPython.utils.syspathcontext import prepended_to_syspath
 from IPython.utils.text import num_ini_spaces
+from IPython.utils.traitlets import (Int, Str, CBool, CaselessStrEnum, Enum,
+                                     List, Unicode, Instance, Type)
 from IPython.utils.warn import warn, error, fatal
-from IPython.utils.traitlets import (
-    Int, Str, CBool, CaselessStrEnum, Enum, List, Unicode, Instance, Type
-)
+import IPython.core.hooks
 
 # from IPython.utils import growl
 # growl.start("IPython")
@@ -430,12 +429,12 @@ class InteractiveShell(Configurable, Magic):
     def init_io(self):
         import IPython.utils.io
         if sys.platform == 'win32' and self.has_readline:
-            Term = IPython.utils.io.IOTerm(
+            Term = io.IOTerm(
                 cout=self.readline._outputfile,cerr=self.readline._outputfile
             )
         else:
-            Term = IPython.utils.io.IOTerm()
-        IPython.utils.io.Term = Term
+            Term = io.IOTerm()
+        io.Term = Term
 
     def init_prompts(self):
         # TODO: This is a pass for now because the prompts are managed inside
@@ -1181,7 +1180,7 @@ class InteractiveShell(Configurable, Magic):
         # Set the exception mode
         self.InteractiveTB.set_mode(mode=self.xmode)
 
-    def set_custom_exc(self,exc_tuple,handler):
+    def set_custom_exc(self, exc_tuple, handler):
         """set_custom_exc(exc_tuple,handler)
 
         Set a custom exception handler, which will be called if any of the
@@ -1198,7 +1197,12 @@ class InteractiveShell(Configurable, Magic):
             exc_tuple == (MyCustomException,)
 
           - handler: this must be defined as a function with the following
-          basic interface: def my_handler(self,etype,value,tb).
+          basic interface::
+
+            def my_handler(self, etype, value, tb, tb_offset=None)
+                ...
+                # The return value must be
+                return structured_traceback
 
           This will be made into an instance method (via new.instancemethod)
           of IPython itself, and it will be called if any of the exceptions
@@ -1272,7 +1276,7 @@ class InteractiveShell(Configurable, Magic):
                     etype, value, tb = sys.last_type, sys.last_value, \
                                        sys.last_traceback
                 else:
-                    self.write('No traceback available to show.\n')
+                    self.write_err('No traceback available to show.\n')
                     return
     
             if etype is SyntaxError:
@@ -1291,22 +1295,39 @@ class InteractiveShell(Configurable, Magic):
                 sys.last_traceback = tb
     
                 if etype in self.custom_exceptions:
-                    self.CustomTB(etype,value,tb)
+                    # FIXME: Old custom traceback objects may just return a
+                    # string, in that case we just put it into a list
+                    stb = self.CustomTB(etype, value, tb, tb_offset)
+                    if isinstance(ctb, basestring):
+                        stb = [stb]
                 else:
                     if exception_only:
-                        m = ('An exception has occurred, use %tb to see the '
-                             'full traceback.')
-                        print m
-                        self.InteractiveTB.show_exception_only(etype, value)
+                        stb = ['An exception has occurred, use %tb to see '
+                               'the full traceback.']
+                        stb.extend(self.InteractiveTB.get_exception_only(etype,
+                                                                         value))
                     else:
-                        self.InteractiveTB(etype,value,tb,tb_offset=tb_offset)
+                        stb = self.InteractiveTB.structured_traceback(etype,
+                                                value, tb, tb_offset=tb_offset)
+                        # FIXME: the pdb calling should be done by us, not by
+                        # the code computing the traceback.
                         if self.InteractiveTB.call_pdb:
                             # pdb mucks up readline, fix it back
                             self.set_completer()
-                        
+
+                # Actually show the traceback
+                self._showtraceback(etype, value, stb)
+                
         except KeyboardInterrupt:
-            self.write("\nKeyboardInterrupt\n")        
-        
+            self.write_err("\nKeyboardInterrupt\n")
+
+    def _showtraceback(self, etype, evalue, stb):
+        """Actually show a traceback.
+
+        Subclasses may override this method to put the traceback on a different
+        place, like a side channel.
+        """
+        self.write_err('\n'.join(stb))
 
     def showsyntaxerror(self, filename=None):
         """Display the syntax error that just occurred.
@@ -1339,7 +1360,8 @@ class InteractiveShell(Configurable, Magic):
                 except:
                     # If that failed, assume SyntaxError is a string
                     value = msg, (filename, lineno, offset, line)
-        self.SyntaxTB(etype,value,[])
+        stb = self.SyntaxTB.structured_traceback(etype, value, [])
+        self._showtraceback(etype, value, stb)
 
     #-------------------------------------------------------------------------
     # Things related to tab completion
@@ -1792,7 +1814,7 @@ class InteractiveShell(Configurable, Magic):
         exposes IPython's processing machinery, the given strings can contain
         magic calls (%magic), special shell access (!cmd), etc.
         """
-
+        
         if isinstance(lines, (list, tuple)):
             lines = '\n'.join(lines)
 
@@ -1912,6 +1934,7 @@ class InteractiveShell(Configurable, Magic):
         try:
             try:
                 self.hooks.pre_runcode_hook()
+                #rprint('Running code') # dbg
                 exec code_obj in self.user_global_ns, self.user_ns
             finally:
                 # Reset our crash handler in place
@@ -2080,12 +2103,12 @@ class InteractiveShell(Configurable, Magic):
     # TODO:  This should be removed when Term is refactored.
     def write(self,data):
         """Write a string to the default output"""
-        IPython.utils.io.Term.cout.write(data)
+        io.Term.cout.write(data)
 
     # TODO:  This should be removed when Term is refactored.
     def write_err(self,data):
         """Write a string to the default error output"""
-        IPython.utils.io.Term.cerr.write(data)
+        io.Term.cerr.write(data)
 
     def ask_yes_no(self,prompt,default=True):
         if self.quiet:
diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py
index 7606ab6..8a4cabd 100644
--- a/IPython/core/ultratb.py
+++ b/IPython/core/ultratb.py
@@ -90,12 +90,12 @@ from inspect import getsourcefile, getfile, getmodule,\
 
 # IPython's own modules
 # Modified pdb which doesn't damage IPython's readline handling
-from IPython.utils import PyColorize
 from IPython.core import debugger, ipapi
 from IPython.core.display_trap import DisplayTrap
 from IPython.core.excolors import exception_colors
+from IPython.utils import PyColorize
+from IPython.utils import io
 from IPython.utils.data import uniq_stable
-import IPython.utils.io
 from IPython.utils.warn import info, error
 
 # Globals
@@ -310,7 +310,7 @@ def _format_traceback_lines(lnum, index, lines, Colors, lvals=None,scheme=None):
 
 #---------------------------------------------------------------------------
 # Module classes
-class TBTools:
+class TBTools(object):
     """Basic tools used by all traceback printer classes."""
 
     # This attribute us used in globalipapp.py to have stdout used for
@@ -319,6 +319,9 @@ class TBTools:
     # the string 'stdout' which will cause the override to sys.stdout.
     out_stream = None
 
+    # Number of frames to skip when reporting tracebacks
+    tb_offset = 0
+
     def __init__(self,color_scheme = 'NoColor',call_pdb=False):
         # Whether to call the interactive pdb debugger after printing
         # tracebacks or not
@@ -357,6 +360,24 @@ class TBTools:
             self.color_scheme_table.set_active_scheme('NoColor')
             self.Colors = self.color_scheme_table.active_colors
 
+    def text(self, etype, value, tb, tb_offset=None, context=5):
+        """Return formatted traceback.
+
+        Subclasses may override this if they add extra arguments.
+        """
+        tb_list = self.structured_traceback(etype, value, tb,
+                                            tb_offset, context)
+        return '\n'.join(tb_list)
+
+    def structured_traceback(self, etype, evalue, tb, tb_offset=None,
+                             context=5, mode=None):
+        """Return a list of traceback frames.
+
+        Must be implemented by each class.
+        """
+        raise NotImplementedError()
+
+
 #---------------------------------------------------------------------------
 class ListTB(TBTools):
     """Print traceback information from a traceback list, with optional color.
@@ -381,11 +402,12 @@ class ListTB(TBTools):
         TBTools.__init__(self,color_scheme = color_scheme,call_pdb=0)
         
     def __call__(self, etype, value, elist):
-        IPython.utils.io.Term.cout.flush()
-        IPython.utils.io.Term.cerr.write(self.text(etype,value,elist))
-        IPython.utils.io.Term.cerr.write('\n')
+        io.Term.cout.flush()
+        io.Term.cerr.write(self.text(etype, value, elist))
+        io.Term.cerr.write('\n')
 
-    def structured_traceback(self, etype, value, elist, context=5):
+    def structured_traceback(self, etype, value, elist, tb_offset=None,
+                             context=5):
         """Return a color formatted string with the traceback info.
 
         Parameters
@@ -399,28 +421,43 @@ class ListTB(TBTools):
         elist : list
           List of frames, see class docstring for details.
 
+        tb_offset : int, optional
+          Number of frames in the traceback to skip.  If not given, the
+          instance value is used (set in constructor).
+          
+        context : int, optional
+          Number of lines of context information to print.
+
         Returns
         -------
         String with formatted exception.
         """
-
+        tb_offset = self.tb_offset if tb_offset is None else tb_offset
         Colors = self.Colors
-        out_string = []
+        out_list = []
         if elist:
-            out_string.append('Traceback %s(most recent call last)%s:' %
+
+            if tb_offset and len(elist) > tb_offset:
+                elist = elist[tb_offset:]
+            
+            out_list.append('Traceback %s(most recent call last)%s:' %
                                 (Colors.normalEm, Colors.Normal) + '\n')
-            out_string.extend(self._format_list(elist))
-        lines = self._format_exception_only(etype, value)
-        for line in lines[:-1]:
-            out_string.append(" "+line)
-        out_string.append(lines[-1])
-        return out_string
-
-    def text(self, etype, value, elist, context=5):
-        out_string = ListTB.structured_traceback(
-            self, etype, value, elist, context
-        )
-        return ''.join(out_string)
+            out_list.extend(self._format_list(elist))
+        # The exception info should be a single entry in the list.
+        lines = ''.join(self._format_exception_only(etype, value))
+        out_list.append(lines)
+
+        # Note: this code originally read:
+        
+        ## for line in lines[:-1]:
+        ##     out_list.append(" "+line)
+        ## out_list.append(lines[-1])
+
+        # This means it was indenting everything but the last line by a little
+        # bit.  I've disabled this for now, but if we see ugliness somewhre we
+        # can restore it.
+        
+        return out_list
 
     def _format_list(self, extracted_list):
         """Format a list of traceback entry tuples for printing.
@@ -457,6 +494,7 @@ class ListTB(TBTools):
             item = item + '%s    %s%s\n' % (Colors.line, line.strip(),
                                             Colors.Normal)
         list.append(item)
+        #from pprint import pformat; print 'LISTTB', pformat(list) # dbg
         return list
         
     def _format_exception_only(self, etype, value):
@@ -528,6 +566,17 @@ class ListTB(TBTools):
 
         return list
 
+    def get_exception_only(self, etype, value):
+        """Only print the exception type and message, without a traceback.
+        
+        Parameters
+        ----------
+        etype : exception type
+        value : exception value
+        """
+        return ListTB.structured_traceback(self, etype, value, [])
+
+
     def show_exception_only(self, etype, value):
         """Only print the exception type and message, without a traceback.
         
@@ -541,9 +590,9 @@ class ListTB(TBTools):
         if self.out_stream == 'stdout':
             ostream = sys.stdout
         else:
-            ostream = IPython.utils.io.Term.cerr
+            ostream = io.Term.cerr
         ostream.flush()
-        ostream.write(ListTB.text(self, etype, value, []))
+        ostream.write('\n'.join(self.get_exception_only(etype, evalue)))
         ostream.flush()
 
     def _some_str(self, value):
@@ -575,9 +624,12 @@ class VerboseTB(TBTools):
         self.long_header = long_header
         self.include_vars = include_vars
 
-    def structured_traceback(self, etype, evalue, etb, context=5):
+    def structured_traceback(self, etype, evalue, etb, tb_offset=None,
+                             context=5):
         """Return a nice text document describing the traceback."""
 
+        tb_offset = self.tb_offset if tb_offset is None else tb_offset
+
         # some locals
         try:
             etype = etype.__name__
@@ -652,9 +704,9 @@ class VerboseTB(TBTools):
             # Try the default getinnerframes and Alex's: Alex's fixes some
             # problems, but it generates empty tracebacks for console errors
             # (5 blanks lines) where none should be returned.
-            #records = inspect.getinnerframes(etb, context)[self.tb_offset:]
+            #records = inspect.getinnerframes(etb, context)[tb_offset:]
             #print 'python records:', records # dbg
-            records = _fixed_getinnerframes(etb, context,self.tb_offset)
+            records = _fixed_getinnerframes(etb, context, tb_offset)
             #print 'alex   records:', records # dbg
         except:
 
@@ -665,7 +717,7 @@ class VerboseTB(TBTools):
             # So far, I haven't been able to find an isolated example to
             # reproduce the problem.
             inspect_error()
-            traceback.print_exc(file=IPython.utils.io.Term.cerr)
+            traceback.print_exc(file=io.Term.cerr)
             info('\nUnfortunately, your original traceback can not be constructed.\n')
             return ''
 
@@ -702,7 +754,7 @@ class VerboseTB(TBTools):
                 # able to remove this try/except when 2.4 becomes a
                 # requirement.  Bug details at http://python.org/sf/1005466
                 inspect_error()
-                traceback.print_exc(file=IPython.utils.io.Term.cerr)
+                traceback.print_exc(file=io.Term.cerr)
                 info("\nIPython's exception reporting continues...\n")
                 
             if func == '?':
@@ -723,7 +775,7 @@ class VerboseTB(TBTools):
                     # and barfs out. At some point I should dig into this one
                     # and file a bug report about it.
                     inspect_error()
-                    traceback.print_exc(file=IPython.utils.io.Term.cerr)
+                    traceback.print_exc(file=io.Term.cerr)
                     info("\nIPython's exception reporting continues...\n")
                     call = tpl_call_fail % func
 
@@ -869,13 +921,7 @@ class VerboseTB(TBTools):
         # return all our info assembled as a single string
         # return '%s\n\n%s\n%s' % (head,'\n'.join(frames),''.join(exception[0]) )
         return [head] + frames + [''.join(exception[0])]
-
-    def text(self, etype, evalue, etb, context=5):
-        tb_list = VerboseTB.structured_traceback(
-            self, etype, evalue, etb, context
-        )
-        return '\n'.join(tb_list)
-
+    
     def debugger(self,force=False):
         """Call up the pdb debugger if desired, always clean up the tb
         reference.
@@ -923,9 +969,9 @@ class VerboseTB(TBTools):
     def handler(self, info=None):
         (etype, evalue, etb) = info or sys.exc_info()
         self.tb = etb
-        IPython.utils.io.Term.cout.flush()
-        IPython.utils.io.Term.cerr.write(self.text(etype, evalue, etb))
-        IPython.utils.io.Term.cerr.write('\n')
+        io.Term.cout.flush()
+        io.Term.cerr.write(self.text(etype, evalue, etb))
+        io.Term.cerr.write('\n')
 
     # Changed so an instance can just be called as VerboseTB_inst() and print
     # out the right info on its own.
@@ -941,7 +987,7 @@ class VerboseTB(TBTools):
             print "\nKeyboardInterrupt"
 
 #----------------------------------------------------------------------------
-class FormattedTB(VerboseTB,ListTB):
+class FormattedTB(VerboseTB, ListTB):
     """Subclass ListTB but allow calling with a traceback.
 
     It can thus be used as a sys.excepthook for Python > 2.1.
@@ -953,8 +999,8 @@ class FormattedTB(VerboseTB,ListTB):
     occurs with python programs that themselves execute other python code,
     like Python shells).  """
     
-    def __init__(self, mode = 'Plain', color_scheme='Linux',
-                 tb_offset = 0,long_header=0,call_pdb=0,include_vars=0):
+    def __init__(self, mode='Plain', color_scheme='Linux',
+                 tb_offset=0, long_header=0, call_pdb=0, include_vars=0):
 
         # NEVER change the order of this list. Put new modes at the end:
         self.valid_modes = ['Plain','Context','Verbose']
@@ -970,12 +1016,14 @@ class FormattedTB(VerboseTB,ListTB):
         else:
             return None
 
-    def structured_traceback(self, etype, value, tb, context=5, mode=None):
+    def structured_traceback(self, etype, value, tb, tb_offset=None,
+                             context=5, mode=None):
+        tb_offset = self.tb_offset if tb_offset is None else tb_offset
         mode = self.mode if mode is None else mode
         if mode in self.verbose_modes:
             # Verbose modes need a full traceback
             return VerboseTB.structured_traceback(
-                self, etype, value, tb, context
+                self, etype, value, tb, tb_offset, context
             )
         else:
             # We must check the source cache because otherwise we can print
@@ -983,21 +1031,21 @@ class FormattedTB(VerboseTB,ListTB):
             linecache.checkcache()
             # Now we can extract and format the exception
             elist = self._extract_tb(tb)
-            if len(elist) > self.tb_offset:
-                del elist[:self.tb_offset]
             return ListTB.structured_traceback(
-                self, etype, value, elist, context
+                self, etype, value, elist, tb_offset, context
             )
 
-    def text(self, etype, value, tb, context=5, mode=None):
+    def text(self, etype, value, tb, tb_offset=None, context=5, mode=None):
         """Return formatted traceback.
 
         If the optional mode parameter is given, it overrides the current
         mode."""
-        tb_list = FormattedTB.structured_traceback(
-            self, etype, value, tb, context, mode
-        )
+
+        mode = self.mode if mode is None else mode
+        tb_list = self.structured_traceback(etype, value, tb, tb_offset,
+                                            context, mode)
         return '\n'.join(tb_list)
+        
 
     def set_mode(self,mode=None):
         """Switch to the desired mode.
@@ -1056,36 +1104,25 @@ class AutoFormattedTB(FormattedTB):
             if self.out_stream == 'stdout':
                 out = sys.stdout
             else:
-                out = IPython.utils.io.Term.cerr
+                out = io.Term.cerr
         out.flush()
-        if tb_offset is not None:
-            tb_offset, self.tb_offset = self.tb_offset, tb_offset
-            out.write(self.text(etype, evalue, etb))
-            out.write('\n')
-            self.tb_offset = tb_offset
-        else:
-            out.write(self.text(etype, evalue, etb))
-            out.write('\n')
+        out.write(self.text(etype, evalue, etb, tb_offset))
+        out.write('\n')
         out.flush()
+        # FIXME: we should remove the auto pdb behavior from here and leave
+        # that to the clients.
         try:
             self.debugger()
         except KeyboardInterrupt:
             print "\nKeyboardInterrupt"
 
     def structured_traceback(self, etype=None, value=None, tb=None,
-                             context=5, mode=None):
+                             tb_offset=None, context=5, mode=None):
         if etype is None:
             etype,value,tb = sys.exc_info()
         self.tb = tb
         return FormattedTB.structured_traceback(
-            self, etype, value, tb, context, mode
-        )
-
-    def text(self, etype=None, value=None, tb=None, context=5, mode=None):
-        tb_list = AutoFormattedTB.structured_traceback(
-            self, etype, value, tb, context, mode
-        )
-        return '\n'.join(tb_list)
+            self, etype, value, tb, tb_offset, context, mode )
 
 #---------------------------------------------------------------------------
 
@@ -1114,6 +1151,15 @@ class SyntaxTB(ListTB):
         self.last_syntax_error = None
         return e
 
+    def text(self, etype, value, tb, tb_offset=None, context=5):
+        """Return formatted traceback.
+
+        Subclasses may override this if they add extra arguments.
+        """
+        tb_list = self.structured_traceback(etype, value, tb,
+                                            tb_offset, context)
+        return ''.join(tb_list)
+
 #----------------------------------------------------------------------------
 # module testing (minimal)
 if __name__ == "__main__":
diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py
index b140e7b..e6e5334 100644
--- a/IPython/frontend/qt/console/ipython_widget.py
+++ b/IPython/frontend/qt/console/ipython_widget.py
@@ -94,7 +94,7 @@ class IPythonWidget(FrontendWidget):
     def execute_file(self, path, hidden=False):
         """ Reimplemented to use the 'run' magic.
         """
-        self.execute('run %s' % path, hidden=hidden)
+        self.execute('%%run %s' % path, hidden=hidden)
 
     #---------------------------------------------------------------------------
     # 'FrontendWidget' protected interface
@@ -109,16 +109,24 @@ class IPythonWidget(FrontendWidget):
         """ Reimplemented for IPython-style traceback formatting.
         """
         content = msg['content']
-        traceback_lines = content['traceback'][:]
-        traceback = ''.join(traceback_lines)
-        traceback = traceback.replace(' ', '&nbsp;')
-        traceback = traceback.replace('\n', '<br/>')
 
-        ename = content['ename']
-        ename_styled = '<span class="error">%s</span>' % ename
-        traceback = traceback.replace(ename, ename_styled)
+        traceback = '\n'.join(content['traceback'])
 
-        self._append_html(traceback)
+        if 0:
+            # FIXME: for now, tracebacks come as plain text, so we can't use
+            # the html renderer yet.  Once we refactor ultratb to produce
+            # properly styled tracebacks, this branch should be the default
+            traceback = traceback.replace(' ', '&nbsp;')
+            traceback = traceback.replace('\n', '<br/>')
+
+            ename = content['ename']
+            ename_styled = '<span class="error">%s</span>' % ename
+            traceback = traceback.replace(ename, ename_styled)
+
+            self._append_html(traceback)
+        else:
+            # This is the fallback for now, using plain text with ansi escapes
+            self._append_plain_text(traceback)
 
     def _process_execute_payload(self, item):
         """ Reimplemented to handle %edit and paging payloads.
diff --git a/IPython/frontend/qt/console/scripts/ipythonqt.py b/IPython/frontend/qt/console/scripts/ipythonqt.py
index cdf50a2..1a8033c 100755
--- a/IPython/frontend/qt/console/scripts/ipythonqt.py
+++ b/IPython/frontend/qt/console/scripts/ipythonqt.py
@@ -61,6 +61,10 @@ def main():
             kernel_manager.start_kernel()
     kernel_manager.start_channels()
 
+    # FIXME: this is a hack, set colors to lightbg by default in qt terminal
+    # unconditionally, regardless of user settings in config files.
+    kernel_manager.xreq_channel.execute("%colors lightbg")
+
     # Launch the application.
     app = QtGui.QApplication([])
     if args.pure:
diff --git a/IPython/utils/io.py b/IPython/utils/io.py
index e38445b..e32c49f 100644
--- a/IPython/utils/io.py
+++ b/IPython/utils/io.py
@@ -13,7 +13,6 @@ IO related utilities.
 #-----------------------------------------------------------------------------
 # Imports
 #-----------------------------------------------------------------------------
-
 import sys
 import tempfile
 
@@ -278,4 +277,10 @@ def temp_pyfile(src, ext='.py'):
     return fname, f
 
 
-
+def rprint(*info):
+    """Raw print to sys.__stderr__"""
+    
+    for item in info:
+        print >> sys.__stderr__, item,
+    print >> sys.__stderr__
+    sys.__stderr__.flush()
diff --git a/IPython/zmq/ipkernel.py b/IPython/zmq/ipkernel.py
index bcdf342..4839785 100755
--- a/IPython/zmq/ipkernel.py
+++ b/IPython/zmq/ipkernel.py
@@ -66,6 +66,9 @@ class Kernel(Configurable):
         self.shell.displayhook.session = self.session
         self.shell.displayhook.pub_socket = self.pub_socket
 
+        # TMP - hack while developing
+        self.shell._reply_content = None
+
         # Build dict of handlers for message types
         msg_types = [ 'execute_request', 'complete_request', 
                       'object_info_request', 'prompt_request',
@@ -156,19 +159,20 @@ class Kernel(Configurable):
             sys.stdout.set_parent(parent)
             sys.stderr.set_parent(parent)
 
+            # FIXME: runlines calls the exception handler itself.  We should
+            # clean this up.
+            self.shell._reply_content = None
             self.shell.runlines(code)
         except:
+            # FIXME: this code right now isn't being used yet by default,
+            # because the runlines() call above directly fires off exception
+            # reporting.  This code, therefore, is only active in the scenario
+            # where runlines itself has an unhandled exception.  We need to
+            # uniformize this, for all exception construction to come from a
+            # single location in the codbase.
             etype, evalue, tb = sys.exc_info()
-            tb = traceback.format_exception(etype, evalue, tb)
-            exc_content = {
-                u'status' : u'error',
-                u'traceback' : tb,
-                u'ename' : unicode(etype.__name__),
-                u'evalue' : unicode(evalue)
-            }
-            exc_msg = self.session.msg(u'pyerr', exc_content, parent)
-            self.pub_socket.send_json(exc_msg)
-            reply_content = exc_content
+            tb_list = traceback.format_exception(etype, evalue, tb)
+            reply_content = self.shell._showtraceback(etype, evalue, tb_list)
         else:
             payload = self.shell.payload_manager.read_payload()
             # Be agressive about clearing the payload because we don't want
@@ -185,6 +189,11 @@ class Kernel(Configurable):
                        'input_sep'     : self.shell.displayhook.input_sep}
         reply_content['next_prompt'] = next_prompt
 
+        # TMP - fish exception info out of shell, possibly left there by
+        # runlines
+        if self.shell._reply_content is not None:
+            reply_content.update(self.shell._reply_content)
+
         # Flush output before sending the reply.
         sys.stderr.flush()
         sys.stdout.flush()
diff --git a/IPython/zmq/zmqshell.py b/IPython/zmq/zmqshell.py
index ecbf85e..0e2c1a8 100644
--- a/IPython/zmq/zmqshell.py
+++ b/IPython/zmq/zmqshell.py
@@ -8,6 +8,7 @@ from IPython.core.interactiveshell import (
 )
 from IPython.core.displayhook import DisplayHook
 from IPython.core.macro import Macro
+from IPython.utils.io import rprint
 from IPython.utils.path import get_py_filename
 from IPython.utils.text import StringTypes
 from IPython.utils.traitlets import Instance, Type, Dict
@@ -359,7 +360,30 @@ class ZMQInteractiveShell(InteractiveShell):
         self.payload_manager.write_payload(payload)
 
 
-InteractiveShellABC.register(ZMQInteractiveShell)
+    def _showtraceback(self, etype, evalue, stb):
 
+        exc_content = {
+            u'status' : u'error',
+            u'traceback' : stb,
+            u'ename' : unicode(etype.__name__),
+            u'evalue' : unicode(evalue)
+        }
 
+        dh = self.displayhook
+        exc_msg = dh.session.msg(u'pyerr', exc_content, dh.parent_header)
+        # Send exception info over pub socket for other clients than the caller
+        # to pick up
+        dh.pub_socket.send_json(exc_msg)
+
+        # FIXME - Hack: store exception info in shell object.  Right now, the
+        # caller is reading this info after the fact, we need to fix this logic
+        # to remove this hack.
+        self._reply_content = exc_content
+        # /FIXME
+        
+        return exc_content
 
+    def runlines(self, lines, clean=False):
+        return InteractiveShell.runlines(self, lines, clean)
+
+InteractiveShellABC.register(ZMQInteractiveShell)