From 775b3992fbb7d28b5be2985bf4f1807c824ae056 2010-08-18 01:38:39 From: Brian Granger Date: 2010-08-18 01:38:39 Subject: [PATCH] Refactor of prompts and the displayhook. * Renamed CachedOutput to displayhook.DisplayHook. * Added methods that DisplayHook.__call__ uses to do its work. These methods can now be overridden for the ZMQ kernel. * Removed all hooks (result_display and generate_output_prompt) that the outputcache was using. We need to add these back in once we figure out what to do about hooks in general. --- diff --git a/IPython/core/displayhook.py b/IPython/core/displayhook.py new file mode 100644 index 0000000..2f052fa --- /dev/null +++ b/IPython/core/displayhook.py @@ -0,0 +1,284 @@ +# -*- coding: utf-8 -*- +"""Displayhook for IPython. + +Authors: + +* Fernando Perez +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2010 The IPython Development Team +# Copyright (C) 2001-2007 Fernando Perez +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import __builtin__ +from pprint import PrettyPrinter +pformat = PrettyPrinter().pformat + +from IPython.config.configurable import Configurable +from IPython.core import prompts +import IPython.utils.generics +import IPython.utils.io +from IPython.utils.traitlets import Instance +from IPython.utils.warn import warn + +#----------------------------------------------------------------------------- +# Main displayhook class +#----------------------------------------------------------------------------- + +# TODO: The DisplayHook class should be split into two classes, one that +# manages the prompts and their synchronization and another that just does the +# displayhook logic and calls into the prompt manager. + +# TODO: Move the various attributes (cache_size, colors, input_sep, +# output_sep, output_sep2, ps1, ps2, ps_out, pad_left). Some of these are also +# attributes of InteractiveShell. They should be on ONE object only and the +# other objects should ask that one object for their values. + +class DisplayHook(Configurable): + """The custom IPython displayhook to replace sys.displayhook. + + This class does many things, but the basic idea is that it is a callable + that gets called anytime user code returns a value. + + Currently this class does more than just the displayhook logic and that + extra logic should eventually be moved out of here. + """ + + shell = Instance('IPython.core.interactiveshell.InteractiveShellABC') + + def __init__(self, shell=None, cache_size=1000, + colors='NoColor', input_sep='\n', + output_sep='\n', output_sep2='', + ps1 = None, ps2 = None, ps_out = None, pad_left=True, + config=None): + super(DisplayHook, self).__init__(shell=shell, config=config) + + cache_size_min = 3 + if cache_size <= 0: + self.do_full_cache = 0 + cache_size = 0 + elif cache_size < cache_size_min: + self.do_full_cache = 0 + cache_size = 0 + warn('caching was disabled (min value for cache size is %s).' % + cache_size_min,level=3) + else: + self.do_full_cache = 1 + + self.cache_size = cache_size + self.input_sep = input_sep + + # we need a reference to the user-level namespace + self.shell = shell + + # Set input prompt strings and colors + if cache_size == 0: + if ps1.find('%n') > -1 or ps1.find(r'\#') > -1 \ + or ps1.find(r'\N') > -1: + ps1 = '>>> ' + if ps2.find('%n') > -1 or ps2.find(r'\#') > -1 \ + or ps2.find(r'\N') > -1: + ps2 = '... ' + self.ps1_str = self._set_prompt_str(ps1,'In [\\#]: ','>>> ') + self.ps2_str = self._set_prompt_str(ps2,' .\\D.: ','... ') + self.ps_out_str = self._set_prompt_str(ps_out,'Out[\\#]: ','') + + self.color_table = prompts.PromptColors + self.prompt1 = prompts.Prompt1(self,sep=input_sep,prompt=self.ps1_str, + pad_left=pad_left) + self.prompt2 = prompts.Prompt2(self,prompt=self.ps2_str,pad_left=pad_left) + self.prompt_out = prompts.PromptOut(self,sep='',prompt=self.ps_out_str, + pad_left=pad_left) + self.set_colors(colors) + + # other more normal stuff + # b/c each call to the In[] prompt raises it by 1, even the first. + self.prompt_count = 0 + # Store the last prompt string each time, we need it for aligning + # continuation and auto-rewrite prompts + self.last_prompt = '' + self.output_sep = output_sep + self.output_sep2 = output_sep2 + self._,self.__,self.___ = '','','' + self.pprint_types = map(type,[(),[],{}]) + + # these are deliberately global: + to_user_ns = {'_':self._,'__':self.__,'___':self.___} + self.shell.user_ns.update(to_user_ns) + + def _set_prompt_str(self,p_str,cache_def,no_cache_def): + if p_str is None: + if self.do_full_cache: + return cache_def + else: + return no_cache_def + else: + return p_str + + def set_colors(self, colors): + """Set the active color scheme and configure colors for the three + prompt subsystems.""" + + # FIXME: This modifying of the global prompts.prompt_specials needs + # to be fixed. We need to refactor all of the prompts stuff to use + # proper configuration and traits notifications. + if colors.lower()=='nocolor': + prompts.prompt_specials = prompts.prompt_specials_nocolor + else: + prompts.prompt_specials = prompts.prompt_specials_color + + self.color_table.set_active_scheme(colors) + self.prompt1.set_colors() + self.prompt2.set_colors() + self.prompt_out.set_colors() + + #------------------------------------------------------------------------- + # Methods used in __call__. Override these methods to modify the behavior + # of the displayhook. + #------------------------------------------------------------------------- + + def check_for_underscore(self): + """Check if the user has set the '_' variable by hand.""" + # If something injected a '_' variable in __builtin__, delete + # ipython's automatic one so we don't clobber that. gettext() in + # particular uses _, so we need to stay away from it. + if '_' in __builtin__.__dict__: + try: + del self.shell.user_ns['_'] + except KeyError: + pass + + def quite(self): + """Should we silence the display hook because of ';'?""" + # do not print output if input ends in ';' + try: + if self.shell.input_hist[self.prompt_count].endswith(';\n'): + return True + except IndexError: + # some uses of ipshellembed may fail here + pass + return False + + def write_output_prompt(self): + """Write the output prompt.""" + # Use write, not print which adds an extra space. + IPython.utils.io.Term.cout.write(self.output_sep) + outprompt = str(self.prompt_out) + if self.do_full_cache: + IPython.utils.io.Term.cout.write(outprompt) + + # TODO: Make this method an extension point. The previous implementation + # has both a result_display hook as well as a result_display generic + # function to customize the repr on a per class basis. We need to rethink + # the hooks mechanism before doing this though. + def compute_result_repr(self, result): + """Compute and return the repr of the object to be displayed. + + This method only compute the string form of the repr and should NOT + actual print or write that to a stream. This method may also transform + the result itself, but the default implementation passes the original + through. + """ + try: + if self.shell.pprint: + result_repr = pformat(result) + if '\n' in result_repr: + # So that multi-line strings line up with the left column of + # the screen, instead of having the output prompt mess up + # their first line. + result_repr = '\n' + result_repr + else: + result_repr = repr(result) + except TypeError: + # This happens when result.__repr__ doesn't return a string, + # such as when it returns None. + result_repr = '\n' + return result, result_repr + + def write_result_repr(self, result_repr): + # We want to print because we want to always make sure we have a + # newline, even if all the prompt separators are ''. This is the + # standard IPython behavior. + print >>IPython.utils.io.Term.cout, result_repr + + def update_user_ns(self, result): + """Update user_ns with various things like _, __, _1, etc.""" + + # Avoid recursive reference when displaying _oh/Out + if result is not self.shell.user_ns['_oh']: + if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache: + warn('Output cache limit (currently '+ + `self.cache_size`+' entries) hit.\n' + 'Flushing cache and resetting history counter...\n' + 'The only history variables available will be _,__,___ and _1\n' + 'with the current result.') + + self.flush() + # Don't overwrite '_' and friends if '_' is in __builtin__ (otherwise + # we cause buggy behavior for things like gettext). + if '_' not in __builtin__.__dict__: + self.___ = self.__ + self.__ = self._ + self._ = result + self.shell.user_ns.update({'_':self._,'__':self.__,'___':self.___}) + + # hackish access to top-level namespace to create _1,_2... dynamically + to_main = {} + if self.do_full_cache: + new_result = '_'+`self.prompt_count` + to_main[new_result] = result + self.shell.user_ns.update(to_main) + self.shell.user_ns['_oh'][self.prompt_count] = result + + def log_output(self, result): + """Log the output.""" + if self.shell.logger.log_output: + self.shell.logger.log_write(repr(result),'output') + + def finish_displayhook(self): + """Finish up all displayhook activities.""" + IPython.utils.io.Term.cout.write(self.output_sep2) + IPython.utils.io.Term.cout.flush() + + def __call__(self, result=None): + """Printing with history cache management. + + This is invoked everytime the interpreter needs to print, and is + activated by setting the variable sys.displayhook to it. + """ + self.check_for_underscore() + if result is not None and not self.quite(): + self.write_output_prompt() + result, result_repr = self.compute_result_repr(result) + self.write_result_repr(result_repr) + self.update_user_ns(result) + self.log_output(result) + self.finish_displayhook() + + def flush(self): + if not self.do_full_cache: + raise ValueError,"You shouldn't have reached the cache flush "\ + "if full caching is not enabled!" + # delete auto-generated vars from global namespace + + for n in range(1,self.prompt_count + 1): + key = '_'+`n` + try: + del self.shell.user_ns[key] + except: pass + self.shell.user_ns['_oh'].clear() + + if '_' not in __builtin__.__dict__: + self.shell.user_ns.update({'_':None,'__':None, '___':None}) + import gc + gc.collect() # xxx needed? + diff --git a/IPython/core/history.py b/IPython/core/history.py index 14ad06e..d24e880 100644 --- a/IPython/core/history.py +++ b/IPython/core/history.py @@ -54,7 +54,7 @@ def magic_history(self, parameter_s = ''): confirmation first if it already exists. """ - if not self.outputcache.do_full_cache: + if not self.displayhook.do_full_cache: print 'This feature is only available if numbered prompts are in use.' return opts,args = self.parse_options(parameter_s,'gnoptsrf:',mode='list') diff --git a/IPython/core/hooks.py b/IPython/core/hooks.py index 8639388..5d8a2ac 100644 --- a/IPython/core/hooks.py +++ b/IPython/core/hooks.py @@ -44,24 +44,19 @@ somewhere in your configuration files or ipython command line. import os, bisect import sys -from pprint import PrettyPrinter - +from IPython.core.error import TryNext import IPython.utils.io from IPython.utils.process import shell -from IPython.core.error import TryNext - # List here all the default hooks. For now it's just the editor functions # but over time we'll move here all the public API for user-accessible things. -__all__ = ['editor', 'fix_error_editor', 'synchronize_with_editor', 'result_display', +__all__ = ['editor', 'fix_error_editor', 'synchronize_with_editor', 'input_prefilter', 'shutdown_hook', 'late_startup_hook', - 'generate_prompt', 'generate_output_prompt','shell_hook', + 'generate_prompt','shell_hook', 'show_in_pager','pre_prompt_hook', 'pre_runcode_hook', 'clipboard_get'] -pformat = PrettyPrinter().pformat - def editor(self,filename, linenum=None): """Open the default editor at the given filename and linenumber. @@ -221,12 +216,8 @@ def late_startup_hook(self): def generate_prompt(self, is_continuation): """ calculate and return a string with the prompt to display """ if is_continuation: - return str(self.outputcache.prompt2) - return str(self.outputcache.prompt1) - - -def generate_output_prompt(self): - return str(self.outputcache.prompt_out) + return str(self.displayhook.prompt2) + return str(self.displayhook.prompt1) def shell_hook(self,cmd): diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 94a0fdf..7e0cc92 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -46,7 +46,7 @@ from IPython.core.logger import Logger from IPython.core.magic import Magic from IPython.core.plugin import PluginManager from IPython.core.prefilter import PrefilterManager -from IPython.core.prompts import CachedOutput +from IPython.core.displayhook import DisplayHook import IPython.core.hooks from IPython.external.Itpl import ItplNS from IPython.utils import PyColorize @@ -206,8 +206,8 @@ class InteractiveShell(Configurable, Magic): # TODO: this part of prompt management should be moved to the frontends. # Use custom TraitTypes that convert '0'->'' and '\\n'->'\n' separate_in = SeparateStr('\n', config=True) - separate_out = SeparateStr('', config=True) - separate_out2 = SeparateStr('', config=True) + separate_out = SeparateStr('\n', config=True) + separate_out2 = SeparateStr('\n', config=True) system_header = Str('IPython system call: ', config=True) system_verbose = CBool(False, config=True) wildcards_case_sensitive = CBool(True, config=True) @@ -428,10 +428,15 @@ class InteractiveShell(Configurable, Magic): IPython.utils.io.Term = Term def init_prompts(self): - # Initialize cache, set in/out prompts and printing system - self.outputcache = CachedOutput(self, - self.cache_size, - self.pprint, + # TODO: This is a pass for now because the prompts are managed inside + # the DisplayHook. Once there is a separate prompt manager, this + # will initialize that object and all prompt related information. + pass + + def init_displayhook(self): + # Initialize displayhook, set in/out prompts and printing system + self.displayhook = DisplayHook( shell=self, + cache_size=self.cache_size, input_sep = self.separate_in, output_sep = self.separate_out, output_sep2 = self.separate_out2, @@ -439,15 +444,9 @@ class InteractiveShell(Configurable, Magic): ps2 = self.prompt_in2, ps_out = self.prompt_out, pad_left = self.prompts_pad_left) - - # user may have over-ridden the default print hook: - try: - self.outputcache.__class__.display = self.hooks.display - except AttributeError: - pass - - def init_displayhook(self): - self.display_trap = DisplayTrap(hook=self.outputcache) + # This is a context manager that installs/revmoes the displayhook at + # the appropriate time. + self.display_trap = DisplayTrap(hook=self.displayhook) def init_reload_doctest(self): # Do a proper resetting of doctest, including the necessary displayhook @@ -1482,8 +1481,9 @@ class InteractiveShell(Configurable, Magic): #------------------------------------------------------------------------- def init_magics(self): - # Set user colors (don't do it in the constructor above so that it - # doesn't crash if colors option is invalid) + # FIXME: Move the color initialization to the DisplayHook, which + # should be split into a prompt manager and displayhook. We probably + # even need a centralize colors management object. self.magic_colors(self.colors) # History was moved to a separate module from . import history diff --git a/IPython/core/logger.py b/IPython/core/logger.py index be7bd7b..c177877 100644 --- a/IPython/core/logger.py +++ b/IPython/core/logger.py @@ -182,14 +182,14 @@ which already exists. But you must first start the logging process with # update the auto _i tables #print '***logging line',line_mod # dbg - #print '***cache_count', self.shell.outputcache.prompt_count # dbg + #print '***cache_count', self.shell.displayhook.prompt_count # dbg try: input_hist = self.shell.user_ns['_ih'] except: #print 'userns:',self.shell.user_ns.keys() # dbg return - out_cache = self.shell.outputcache + out_cache = self.shell.displayhook # add blank lines if the input cache fell out of sync. if out_cache.do_full_cache and \ @@ -208,15 +208,15 @@ which already exists. But you must first start the logging process with # hackish access to top-level namespace to create _i1,_i2... dynamically to_main = {'_i':self._i,'_ii':self._ii,'_iii':self._iii} - if self.shell.outputcache.do_full_cache: - in_num = self.shell.outputcache.prompt_count + if self.shell.displayhook.do_full_cache: + in_num = self.shell.displayhook.prompt_count # but if the opposite is true (a macro can produce multiple inputs # with no output display called), then bring the output counter in # sync: last_num = len(input_hist)-1 if in_num != last_num: - in_num = self.shell.outputcache.prompt_count = last_num + in_num = self.shell.displayhook.prompt_count = last_num new_i = '_i%s' % in_num if continuation: self._i00 = '%s%s\n' % (self.shell.user_ns[new_i],line_mod) diff --git a/IPython/core/magic.py b/IPython/core/magic.py index 5cd7429..d5ef5d4 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -2378,7 +2378,7 @@ Currently the magic system has the following functions:\n""" # use last_call to remember the state of the previous call, but don't # let it be clobbered by successive '-p' calls. try: - last_call[0] = self.shell.outputcache.prompt_count + last_call[0] = self.shell.displayhook.prompt_count if not opts_p: last_call[1] = parameter_s except: @@ -2566,12 +2566,12 @@ Defaulting color scheme to 'NoColor'""" # Set prompt colors try: - shell.outputcache.set_colors(new_scheme) + shell.displayhook.set_colors(new_scheme) except: color_switch_err('prompt') else: shell.colors = \ - shell.outputcache.color_table.active_scheme_name + shell.displayhook.color_table.active_scheme_name # Set exception colors try: shell.InteractiveTB.set_colors(scheme = new_scheme) @@ -2889,7 +2889,7 @@ Defaulting color scheme to 'NoColor'""" if ps: try: os.chdir(os.path.expanduser(ps)) - if self.shell.term_title: + if hasattr(self.shell, 'term_title') and self.shell.term_title: set_term_title('IPython: ' + abbrev_cwd()) except OSError: print sys.exc_info()[1] @@ -2902,7 +2902,7 @@ Defaulting color scheme to 'NoColor'""" else: os.chdir(self.shell.home_dir) - if self.shell.term_title: + if hasattr(self.shell, 'term_title') and self.shell.term_title: set_term_title('IPython: ' + '~') cwd = os.getcwd() dhist = self.shell.user_ns['_dh'] @@ -3439,7 +3439,7 @@ Defaulting color scheme to 'NoColor'""" # Shorthands shell = self.shell - oc = shell.outputcache + oc = shell.displayhook meta = shell.meta # dstore is a data store kept in the instance metadata bag to track any # changes we make, so we can undo them later. diff --git a/IPython/core/prefilter.py b/IPython/core/prefilter.py index a7bd9c6..3c11767 100755 --- a/IPython/core/prefilter.py +++ b/IPython/core/prefilter.py @@ -405,7 +405,7 @@ class PrefilterManager(Configurable): normal_handler = self.get_handler_by_name('normal') if not stripped: if not continue_prompt: - self.shell.outputcache.prompt_count -= 1 + self.shell.displayhook.prompt_count -= 1 return normal_handler.handle(line_info) @@ -916,7 +916,7 @@ class AutoHandler(PrefilterHandler): newcmd = '%s(%s)' % (ifun.rstrip(), the_rest) if auto_rewrite: - rw = self.shell.outputcache.prompt1.auto_rewrite() + newcmd + rw = self.shell.displayhook.prompt1.auto_rewrite() + newcmd try: # plain ascii works better w/ pyreadline, on some machines, so diff --git a/IPython/core/prompts.py b/IPython/core/prompts.py index 14231c8..3cfba55 100644 --- a/IPython/core/prompts.py +++ b/IPython/core/prompts.py @@ -1,19 +1,24 @@ # -*- coding: utf-8 -*- -""" -Classes for handling input/output prompts. +"""Classes for handling input/output prompts. + +Authors: + +* Fernando Perez +* Brian Granger """ -#***************************************************************************** -# Copyright (C) 2008-2009 The IPython Development Team +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2010 The IPython Development Team # Copyright (C) 2001-2007 Fernando Perez # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. -#***************************************************************************** +#----------------------------------------------------------------------------- -#**************************************************************************** +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- -import __builtin__ import os import re import socket @@ -21,14 +26,11 @@ import sys from IPython.core import release from IPython.external.Itpl import ItplNS -from IPython.core.error import TryNext from IPython.utils import coloransi -import IPython.utils.generics -from IPython.utils.warn import warn -import IPython.utils.io -#**************************************************************************** -#Color schemes for Prompts. +#----------------------------------------------------------------------------- +# Color schemes for prompts +#----------------------------------------------------------------------------- PromptColors = coloransi.ColorSchemeTable() InputColors = coloransi.InputTermColors # just a shorthand @@ -76,6 +78,9 @@ PromptColors.add_scheme(__PColLightBG) del Colors,InputColors #----------------------------------------------------------------------------- +# Utilities +#----------------------------------------------------------------------------- + def multiple_replace(dict, text): """ Replace in 'text' all occurences of any key in the given dictionary by its corresponding value. Returns the new string.""" @@ -90,6 +95,7 @@ def multiple_replace(dict, text): #----------------------------------------------------------------------------- # Special characters that can be used in prompt templates, mainly bash-like +#----------------------------------------------------------------------------- # If $HOME isn't defined (Windows), make it an absurd string so that it can # never be expanded out into '~'. Basically anything which can never be a @@ -201,6 +207,9 @@ for _color in dir(input_colors): prompt_specials = prompt_specials_nocolor #----------------------------------------------------------------------------- +# More utilities +#----------------------------------------------------------------------------- + def str_safe(arg): """Convert to a string, without ever raising an exception. @@ -221,6 +230,10 @@ def str_safe(arg): #raise # dbg return out +#----------------------------------------------------------------------------- +# Prompt classes +#----------------------------------------------------------------------------- + class BasePrompt(object): """Interactive prompt similar to Mathematica's.""" @@ -234,7 +247,7 @@ class BasePrompt(object): p_template = property(_get_p_template,_set_p_template, doc='Template for prompt string creation') - def __init__(self,cache,sep,prompt,pad_left=False): + def __init__(self, cache, sep, prompt, pad_left=False): # Hack: we access information about the primary prompt through the # cache argument. We need this, because we want the secondary prompt @@ -267,17 +280,17 @@ class BasePrompt(object): self.p_str = ItplNS('%s%s%s' % ('${self.sep}${self.col_p}', multiple_replace(prompt_specials, self.p_template), - '${self.col_norm}'),self.cache.user_ns,loc) + '${self.col_norm}'),self.cache.shell.user_ns,loc) self.p_str_nocolor = ItplNS(multiple_replace(prompt_specials_nocolor, self.p_template), - self.cache.user_ns,loc) + self.cache.shell.user_ns,loc) except: print "Illegal prompt template (check $ usage!):",self.p_template self.p_str = self.p_template self.p_str_nocolor = self.p_template - def write(self,msg): # dbg + def write(self, msg): sys.stdout.write(msg) return '' @@ -301,7 +314,7 @@ class BasePrompt(object): # these path filters are put in as methods so that we can control the # namespace where the prompt strings get evaluated - def cwd_filt(self,depth): + def cwd_filt(self, depth): """Return the last depth elements of the current working directory. $HOME is always replaced with '~'. @@ -314,7 +327,7 @@ class BasePrompt(object): else: return os.sep - def cwd_filt2(self,depth): + def cwd_filt2(self, depth): """Return the last depth elements of the current working directory. $HOME is always replaced with '~'. @@ -341,11 +354,12 @@ class BasePrompt(object): return bool(self.p_template) + class Prompt1(BasePrompt): """Input interactive prompt similar to Mathematica's.""" - def __init__(self,cache,sep='\n',prompt='In [\\#]: ',pad_left=True): - BasePrompt.__init__(self,cache,sep,prompt,pad_left) + def __init__(self, cache, sep='\n', prompt='In [\\#]: ', pad_left=True): + BasePrompt.__init__(self, cache, sep, prompt, pad_left) def set_colors(self): self.set_p_str() @@ -373,11 +387,12 @@ class Prompt1(BasePrompt): return '%s%s>%s%s' % (self.col_p_ni,'-'*(len(curr)-nrspaces-1), ' '*nrspaces,self.col_norm_ni) + class PromptOut(BasePrompt): """Output interactive prompt similar to Mathematica's.""" - def __init__(self,cache,sep='',prompt='Out[\\#]: ',pad_left=True): - BasePrompt.__init__(self,cache,sep,prompt,pad_left) + def __init__(self, cache, sep='', prompt='Out[\\#]: ', pad_left=True): + BasePrompt.__init__(self, cache, sep, prompt, pad_left) if not self.p_template: self.__str__ = lambda: '' @@ -388,10 +403,11 @@ class PromptOut(BasePrompt): self.col_num = Colors.out_number self.col_norm = Colors.normal + class Prompt2(BasePrompt): """Interactive continuation prompt.""" - def __init__(self,cache,prompt=' .\\D.: ',pad_left=True): + def __init__(self, cache, prompt=' .\\D.: ', pad_left=True): self.cache = cache self.p_template = prompt self.pad_left = pad_left @@ -404,10 +420,10 @@ class Prompt2(BasePrompt): ('${self.col_p2}', multiple_replace(prompt_specials, self.p_template), '$self.col_norm'), - self.cache.user_ns,loc) + self.cache.shell.user_ns,loc) self.p_str_nocolor = ItplNS(multiple_replace(prompt_specials_nocolor, self.p_template), - self.cache.user_ns,loc) + self.cache.shell.user_ns,loc) def set_colors(self): self.set_p_str() @@ -419,221 +435,3 @@ class Prompt2(BasePrompt): self.col_p = Colors.out_prompt self.col_num = Colors.out_number - -#----------------------------------------------------------------------------- -class CachedOutput: - """Class for printing output from calculations while keeping a cache of - reults. It dynamically creates global variables prefixed with _ which - contain these results. - - Meant to be used as a sys.displayhook replacement, providing numbered - prompts and cache services. - - Initialize with initial and final values for cache counter (this defines - the maximum size of the cache.""" - - def __init__(self,shell,cache_size,Pprint, - colors='NoColor',input_sep='\n', - output_sep='\n',output_sep2='', - ps1 = None, ps2 = None,ps_out = None,pad_left=True): - - cache_size_min = 3 - if cache_size <= 0: - self.do_full_cache = 0 - cache_size = 0 - elif cache_size < cache_size_min: - self.do_full_cache = 0 - cache_size = 0 - warn('caching was disabled (min value for cache size is %s).' % - cache_size_min,level=3) - else: - self.do_full_cache = 1 - - self.cache_size = cache_size - self.input_sep = input_sep - - # we need a reference to the user-level namespace - self.shell = shell - self.user_ns = shell.user_ns - # and to the user's input - self.input_hist = shell.input_hist - # and to the user's logger, for logging output - self.logger = shell.logger - - # Set input prompt strings and colors - if cache_size == 0: - if ps1.find('%n') > -1 or ps1.find(r'\#') > -1 \ - or ps1.find(r'\N') > -1: - ps1 = '>>> ' - if ps2.find('%n') > -1 or ps2.find(r'\#') > -1 \ - or ps2.find(r'\N') > -1: - ps2 = '... ' - self.ps1_str = self._set_prompt_str(ps1,'In [\\#]: ','>>> ') - self.ps2_str = self._set_prompt_str(ps2,' .\\D.: ','... ') - self.ps_out_str = self._set_prompt_str(ps_out,'Out[\\#]: ','') - - self.color_table = PromptColors - self.prompt1 = Prompt1(self,sep=input_sep,prompt=self.ps1_str, - pad_left=pad_left) - self.prompt2 = Prompt2(self,prompt=self.ps2_str,pad_left=pad_left) - self.prompt_out = PromptOut(self,sep='',prompt=self.ps_out_str, - pad_left=pad_left) - self.set_colors(colors) - - # other more normal stuff - # b/c each call to the In[] prompt raises it by 1, even the first. - self.prompt_count = 0 - # Store the last prompt string each time, we need it for aligning - # continuation and auto-rewrite prompts - self.last_prompt = '' - self.Pprint = Pprint - self.output_sep = output_sep - self.output_sep2 = output_sep2 - self._,self.__,self.___ = '','','' - self.pprint_types = map(type,[(),[],{}]) - - # these are deliberately global: - to_user_ns = {'_':self._,'__':self.__,'___':self.___} - self.user_ns.update(to_user_ns) - - def _set_prompt_str(self,p_str,cache_def,no_cache_def): - if p_str is None: - if self.do_full_cache: - return cache_def - else: - return no_cache_def - else: - return p_str - - def set_colors(self,colors): - """Set the active color scheme and configure colors for the three - prompt subsystems.""" - - # FIXME: the prompt_specials global should be gobbled inside this - # class instead. Do it when cleaning up the whole 3-prompt system. - global prompt_specials - if colors.lower()=='nocolor': - prompt_specials = prompt_specials_nocolor - else: - prompt_specials = prompt_specials_color - - self.color_table.set_active_scheme(colors) - self.prompt1.set_colors() - self.prompt2.set_colors() - self.prompt_out.set_colors() - - def __call__(self,arg=None): - """Printing with history cache management. - - This is invoked everytime the interpreter needs to print, and is - activated by setting the variable sys.displayhook to it.""" - - # If something injected a '_' variable in __builtin__, delete - # ipython's automatic one so we don't clobber that. gettext() in - # particular uses _, so we need to stay away from it. - if '_' in __builtin__.__dict__: - try: - del self.user_ns['_'] - except KeyError: - pass - if arg is not None: - cout_write = IPython.utils.io.Term.cout.write # fast lookup - # first handle the cache and counters - - # do not print output if input ends in ';' - try: - if self.input_hist[self.prompt_count].endswith(';\n'): - return - except IndexError: - # some uses of ipshellembed may fail here - pass - # don't use print, puts an extra space - cout_write(self.output_sep) - outprompt = self.shell.hooks.generate_output_prompt() - # print "Got prompt: ", outprompt - if self.do_full_cache: - cout_write(outprompt) - - # and now call a possibly user-defined print mechanism. Note that - # self.display typically prints as a side-effect, we don't do any - # printing to stdout here. - try: - manipulated_val = self.display(arg) - except TypeError: - # If the user's display hook didn't return a string we can - # print, we're done. Happens commonly if they return None - cout_write('\n') - return - - # user display hooks can change the variable to be stored in - # output history - if manipulated_val is not None: - arg = manipulated_val - - # avoid recursive reference when displaying _oh/Out - if arg is not self.user_ns['_oh']: - self.update(arg) - - if self.logger.log_output: - self.logger.log_write(repr(arg),'output') - cout_write(self.output_sep2) - IPython.utils.io.Term.cout.flush() - - def _display(self,arg): - """Default printer method, uses pprint. - - Do ip.set_hook("result_display", my_displayhook) for custom result - display, e.g. when your own objects need special formatting. - """ - try: - return IPython.utils.generics.result_display(arg) - except TryNext: - return self.shell.hooks.result_display(arg) - - # Assign the default display method: - display = _display - - def update(self,arg): - #print '***cache_count', self.cache_count # dbg - if len(self.user_ns['_oh']) >= self.cache_size and self.do_full_cache: - warn('Output cache limit (currently '+ - `self.cache_size`+' entries) hit.\n' - 'Flushing cache and resetting history counter...\n' - 'The only history variables available will be _,__,___ and _1\n' - 'with the current result.') - - self.flush() - # Don't overwrite '_' and friends if '_' is in __builtin__ (otherwise - # we cause buggy behavior for things like gettext). - if '_' not in __builtin__.__dict__: - self.___ = self.__ - self.__ = self._ - self._ = arg - self.user_ns.update({'_':self._,'__':self.__,'___':self.___}) - - # hackish access to top-level namespace to create _1,_2... dynamically - to_main = {} - if self.do_full_cache: - new_result = '_'+`self.prompt_count` - to_main[new_result] = arg - self.user_ns.update(to_main) - self.user_ns['_oh'][self.prompt_count] = arg - - def flush(self): - if not self.do_full_cache: - raise ValueError,"You shouldn't have reached the cache flush "\ - "if full caching is not enabled!" - # delete auto-generated vars from global namespace - - for n in range(1,self.prompt_count + 1): - key = '_'+`n` - try: - del self.user_ns[key] - except: pass - self.user_ns['_oh'].clear() - - if '_' not in __builtin__.__dict__: - self.user_ns.update({'_':None,'__':None, '___':None}) - import gc - gc.collect() # xxx needed? - diff --git a/IPython/core/tests/test_run.py b/IPython/core/tests/test_run.py index 35d49ac..12f634f 100644 --- a/IPython/core/tests/test_run.py +++ b/IPython/core/tests/test_run.py @@ -122,7 +122,7 @@ class TestMagicRunPass(tt.TempFileMixin): """Test that prompts correctly generate after %run""" self.run_tmpfile() _ip = get_ipython() - p2 = str(_ip.outputcache.prompt2).strip() + p2 = str(_ip.displayhook.prompt2).strip() nt.assert_equals(p2[:3], '...') diff --git a/IPython/frontend/terminal/interactiveshell.py b/IPython/frontend/terminal/interactiveshell.py index ec10d8c..50b57ac 100644 --- a/IPython/frontend/terminal/interactiveshell.py +++ b/IPython/frontend/terminal/interactiveshell.py @@ -236,7 +236,7 @@ class TerminalInteractiveShell(InteractiveShell): self.write('\nKeyboardInterrupt\n') self.resetbuffer() # keep cache in sync with the prompt counter: - self.outputcache.prompt_count -= 1 + self.displayhook.prompt_count -= 1 if self.autoindent: self.indent_current_nsp = 0 diff --git a/IPython/testing/iptest.py b/IPython/testing/iptest.py index a39e53b..db139b1 100644 --- a/IPython/testing/iptest.py +++ b/IPython/testing/iptest.py @@ -250,7 +250,7 @@ class IPTester(object): return os.system(' '.join(self.call_args)) else: def _run_cmd(self): - #print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg + # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg subp = subprocess.Popen(self.call_args) self.pids.append(subp.pid) # If this fails, the pid will be left in self.pids and cleaned up diff --git a/IPython/utils/generics.py b/IPython/utils/generics.py index 2087b50..4fcc32b 100644 --- a/IPython/utils/generics.py +++ b/IPython/utils/generics.py @@ -33,12 +33,6 @@ from IPython.external.simplegeneric import generic @generic -def result_display(result): - """Print the result of computation.""" - raise TryNext - - -@generic def inspect_object(obj): """Called when you do obj?""" raise TryNext diff --git a/IPython/utils/text.py b/IPython/utils/text.py index 169a0e4..45ec602 100644 --- a/IPython/utils/text.py +++ b/IPython/utils/text.py @@ -23,7 +23,6 @@ import types from IPython.external.path import path -from IPython.utils.generics import result_display from IPython.utils.io import nlprint from IPython.utils.data import flatten @@ -94,14 +93,17 @@ class LSString(str): p = paths = property(get_paths) +# FIXME: We need to reimplement type specific displayhook and then add this +# back as a custom printer. This should also be moved outside utils into the +# core. -def print_lsstring(arg): - """ Prettier (non-repr-like) and more informative printer for LSString """ - print "LSString (.p, .n, .l, .s available). Value:" - print arg - - -print_lsstring = result_display.when_type(LSString)(print_lsstring) +# def print_lsstring(arg): +# """ Prettier (non-repr-like) and more informative printer for LSString """ +# print "LSString (.p, .n, .l, .s available). Value:" +# print arg +# +# +# print_lsstring = result_display.when_type(LSString)(print_lsstring) class SList(list): @@ -248,17 +250,20 @@ class SList(list): return SList([t[1] for t in dsu]) -def print_slist(arg): - """ Prettier (non-repr-like) and more informative printer for SList """ - print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):" - if hasattr(arg, 'hideonce') and arg.hideonce: - arg.hideonce = False - return - - nlprint(arg) - - -print_slist = result_display.when_type(SList)(print_slist) +# FIXME: We need to reimplement type specific displayhook and then add this +# back as a custom printer. This should also be moved outside utils into the +# core. + +# def print_slist(arg): +# """ Prettier (non-repr-like) and more informative printer for SList """ +# print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):" +# if hasattr(arg, 'hideonce') and arg.hideonce: +# arg.hideonce = False +# return +# +# nlprint(arg) +# +# print_slist = result_display.when_type(SList)(print_slist) def esc_quotes(strng): diff --git a/IPython/zmq/zmqshell.py b/IPython/zmq/zmqshell.py index 62ba5d0..061bc2a 100644 --- a/IPython/zmq/zmqshell.py +++ b/IPython/zmq/zmqshell.py @@ -17,7 +17,7 @@ class ZMQInteractiveShell(InteractiveShell): for line in p.stderr.read().split('\n'): if len(line) > 0: print line - return p.wait() + p.wait() def init_io(self): # This will just use sys.stdout and sys.stderr. If you want to