1# -*- coding: utf-8 -*-
2"""Displayhook for IPython.
3
4This defines a callable class that IPython uses for `sys.displayhook`.
5"""
6
7# Copyright (c) IPython Development Team.
8# Distributed under the terms of the Modified BSD License.
9
10from __future__ import print_function
11
12import sys
13import io as _io
14import tokenize
15
16from traitlets.config.configurable import Configurable
17from IPython.utils.py3compat import builtin_mod, cast_unicode_py2
18from traitlets import Instance, Float
19from warnings import warn
20
21# TODO: Move the various attributes (cache_size, [others now moved]). Some
22# of these are also attributes of InteractiveShell. They should be on ONE object
23# only and the other objects should ask that one object for their values.
24
25class DisplayHook(Configurable):
26    """The custom IPython displayhook to replace sys.displayhook.
27
28    This class does many things, but the basic idea is that it is a callable
29    that gets called anytime user code returns a value.
30    """
31
32    shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
33                     allow_none=True)
34    exec_result = Instance('IPython.core.interactiveshell.ExecutionResult',
35                           allow_none=True)
36    cull_fraction = Float(0.2)
37
38    def __init__(self, shell=None, cache_size=1000, **kwargs):
39        super(DisplayHook, self).__init__(shell=shell, **kwargs)
40        cache_size_min = 3
41        if cache_size <= 0:
42            self.do_full_cache = 0
43            cache_size = 0
44        elif cache_size < cache_size_min:
45            self.do_full_cache = 0
46            cache_size = 0
47            warn('caching was disabled (min value for cache size is %s).' %
48                 cache_size_min,stacklevel=3)
49        else:
50            self.do_full_cache = 1
51
52        self.cache_size = cache_size
53
54        # we need a reference to the user-level namespace
55        self.shell = shell
56
57        self._,self.__,self.___ = '','',''
58
59        # these are deliberately global:
60        to_user_ns = {'_':self._,'__':self.__,'___':self.___}
61        self.shell.user_ns.update(to_user_ns)
62
63    @property
64    def prompt_count(self):
65        return self.shell.execution_count
66
67    #-------------------------------------------------------------------------
68    # Methods used in __call__. Override these methods to modify the behavior
69    # of the displayhook.
70    #-------------------------------------------------------------------------
71
72    def check_for_underscore(self):
73        """Check if the user has set the '_' variable by hand."""
74        # If something injected a '_' variable in __builtin__, delete
75        # ipython's automatic one so we don't clobber that.  gettext() in
76        # particular uses _, so we need to stay away from it.
77        if '_' in builtin_mod.__dict__:
78            try:
79                del self.shell.user_ns['_']
80            except KeyError:
81                pass
82
83    def quiet(self):
84        """Should we silence the display hook because of ';'?"""
85        # do not print output if input ends in ';'
86
87        try:
88            cell = cast_unicode_py2(self.shell.history_manager.input_hist_parsed[-1])
89        except IndexError:
90            # some uses of ipshellembed may fail here
91            return False
92
93        sio = _io.StringIO(cell)
94        tokens = list(tokenize.generate_tokens(sio.readline))
95
96        for token in reversed(tokens):
97            if token[0] in (tokenize.ENDMARKER, tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT):
98                continue
99            if (token[0] == tokenize.OP) and (token[1] == ';'):
100                return True
101            else:
102                return False
103
104    def start_displayhook(self):
105        """Start the displayhook, initializing resources."""
106        pass
107
108    def write_output_prompt(self):
109        """Write the output prompt.
110
111        The default implementation simply writes the prompt to
112        ``sys.stdout``.
113        """
114        # Use write, not print which adds an extra space.
115        sys.stdout.write(self.shell.separate_out)
116        outprompt = 'Out[{}]: '.format(self.shell.execution_count)
117        if self.do_full_cache:
118            sys.stdout.write(outprompt)
119
120    def compute_format_data(self, result):
121        """Compute format data of the object to be displayed.
122
123        The format data is a generalization of the :func:`repr` of an object.
124        In the default implementation the format data is a :class:`dict` of
125        key value pair where the keys are valid MIME types and the values
126        are JSON'able data structure containing the raw data for that MIME
127        type. It is up to frontends to determine pick a MIME to to use and
128        display that data in an appropriate manner.
129
130        This method only computes the format data for the object and should
131        NOT actually print or write that to a stream.
132
133        Parameters
134        ----------
135        result : object
136            The Python object passed to the display hook, whose format will be
137            computed.
138
139        Returns
140        -------
141        (format_dict, md_dict) : dict
142            format_dict is a :class:`dict` whose keys are valid MIME types and values are
143            JSON'able raw data for that MIME type. It is recommended that
144            all return values of this should always include the "text/plain"
145            MIME type representation of the object.
146            md_dict is a :class:`dict` with the same MIME type keys
147            of metadata associated with each output.
148
149        """
150        return self.shell.display_formatter.format(result)
151
152    # This can be set to True by the write_output_prompt method in a subclass
153    prompt_end_newline = False
154
155    def write_format_data(self, format_dict, md_dict=None):
156        """Write the format data dict to the frontend.
157
158        This default version of this method simply writes the plain text
159        representation of the object to ``sys.stdout``. Subclasses should
160        override this method to send the entire `format_dict` to the
161        frontends.
162
163        Parameters
164        ----------
165        format_dict : dict
166            The format dict for the object passed to `sys.displayhook`.
167        md_dict : dict (optional)
168            The metadata dict to be associated with the display data.
169        """
170        if 'text/plain' not in format_dict:
171            # nothing to do
172            return
173        # We want to print because we want to always make sure we have a
174        # newline, even if all the prompt separators are ''. This is the
175        # standard IPython behavior.
176        result_repr = format_dict['text/plain']
177        if '\n' in result_repr:
178            # So that multi-line strings line up with the left column of
179            # the screen, instead of having the output prompt mess up
180            # their first line.
181            # We use the prompt template instead of the expanded prompt
182            # because the expansion may add ANSI escapes that will interfere
183            # with our ability to determine whether or not we should add
184            # a newline.
185            if not self.prompt_end_newline:
186                # But avoid extraneous empty lines.
187                result_repr = '\n' + result_repr
188
189        print(result_repr)
190
191    def update_user_ns(self, result):
192        """Update user_ns with various things like _, __, _1, etc."""
193
194        # Avoid recursive reference when displaying _oh/Out
195        if result is not self.shell.user_ns['_oh']:
196            if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache:
197                self.cull_cache()
198            # Don't overwrite '_' and friends if '_' is in __builtin__ (otherwise
199            # we cause buggy behavior for things like gettext).
200
201            if '_' not in builtin_mod.__dict__:
202                self.___ = self.__
203                self.__ = self._
204                self._ = result
205                self.shell.push({'_':self._,
206                                 '__':self.__,
207                                '___':self.___}, interactive=False)
208
209            # hackish access to top-level  namespace to create _1,_2... dynamically
210            to_main = {}
211            if self.do_full_cache:
212                new_result = '_'+repr(self.prompt_count)
213                to_main[new_result] = result
214                self.shell.push(to_main, interactive=False)
215                self.shell.user_ns['_oh'][self.prompt_count] = result
216
217    def fill_exec_result(self, result):
218        if self.exec_result is not None:
219            self.exec_result.result = result
220
221    def log_output(self, format_dict):
222        """Log the output."""
223        if 'text/plain' not in format_dict:
224            # nothing to do
225            return
226        if self.shell.logger.log_output:
227            self.shell.logger.log_write(format_dict['text/plain'], 'output')
228        self.shell.history_manager.output_hist_reprs[self.prompt_count] = \
229                                                    format_dict['text/plain']
230
231    def finish_displayhook(self):
232        """Finish up all displayhook activities."""
233        sys.stdout.write(self.shell.separate_out2)
234        sys.stdout.flush()
235
236    def __call__(self, result=None):
237        """Printing with history cache management.
238
239        This is invoked everytime the interpreter needs to print, and is
240        activated by setting the variable sys.displayhook to it.
241        """
242        self.check_for_underscore()
243        if result is not None and not self.quiet():
244            self.start_displayhook()
245            self.write_output_prompt()
246            format_dict, md_dict = self.compute_format_data(result)
247            self.update_user_ns(result)
248            self.fill_exec_result(result)
249            if format_dict:
250                self.write_format_data(format_dict, md_dict)
251                self.log_output(format_dict)
252            self.finish_displayhook()
253
254    def cull_cache(self):
255        """Output cache is full, cull the oldest entries"""
256        oh = self.shell.user_ns.get('_oh', {})
257        sz = len(oh)
258        cull_count = max(int(sz * self.cull_fraction), 2)
259        warn('Output cache limit (currently {sz} entries) hit.\n'
260             'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count))
261
262        for i, n in enumerate(sorted(oh)):
263            if i >= cull_count:
264                break
265            self.shell.user_ns.pop('_%i' % n, None)
266            oh.pop(n, None)
267
268
269    def flush(self):
270        if not self.do_full_cache:
271            raise ValueError("You shouldn't have reached the cache flush "
272                             "if full caching is not enabled!")
273        # delete auto-generated vars from global namespace
274
275        for n in range(1,self.prompt_count + 1):
276            key = '_'+repr(n)
277            try:
278                del self.shell.user_ns[key]
279            except: pass
280        # In some embedded circumstances, the user_ns doesn't have the
281        # '_oh' key set up.
282        oh = self.shell.user_ns.get('_oh', None)
283        if oh is not None:
284            oh.clear()
285
286        # Release our own references to objects:
287        self._, self.__, self.___ = '', '', ''
288
289        if '_' not in builtin_mod.__dict__:
290            self.shell.user_ns.update({'_':None,'__':None, '___':None})
291        import gc
292        # TODO: Is this really needed?
293        # IronPython blocks here forever
294        if sys.platform != "cli":
295            gc.collect()
296
297
298class CapturingDisplayHook(object):
299    def __init__(self, shell, outputs=None):
300        self.shell = shell
301        if outputs is None:
302            outputs = []
303        self.outputs = outputs
304
305    def __call__(self, result=None):
306        if result is None:
307            return
308        format_dict, md_dict = self.shell.display_formatter.format(result)
309        self.outputs.append({ 'data': format_dict, 'metadata': md_dict })
310