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