##// END OF EJS Templates
Provide hooks for arbitrary mimetypes handling....
Matthias Bussonnier -
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,55 b''
1 Arbitrary Mimetypes Handing in Terminal
2 =======================================
3
4 When using IPython terminal it is now possible to register function to handle
5 arbitrary mimetypes (``TerminalInteractiveShell.mime_renderers`` ``Dict``
6 configurable). While rendering non-text based representation was possible in
7 many jupyter frontend; it was not possible in terminal IPython, as usually
8 terminal are limited to displaying text. As many terminal these days provide
9 escape sequences to display non-text; bringing this loved feature to IPython CLI
10 made a lot of sens. This functionality will not only allow inline images; but
11 allow opening of external program; for example ``fmplayer`` to "display" sound
12 files.
13
14 Here is a complete IPython tension to display images inline and convert math to
15 png, before displaying it inline ::
16
17
18 from base64 import encodebytes
19 from IPython.lib.latextools import latex_to_png
20
21
22 def mathcat(data, meta):
23 png = latex_to_png(f'$${data}$$'.replace('\displaystyle', '').replace('$$$', '$$'))
24 imcat(png, meta)
25
26 IMAGE_CODE = '\033]1337;File=name=name;inline=true;:{}\a'
27
28 def imcat(image_data, metadata):
29 try:
30 print(IMAGE_CODE.format(encodebytes(image_data).decode()))
31 # bug workaround
32 except:
33 print(IMAGE_CODE.format(image_data))
34
35 def register_mimerenderer(ipython, mime, handler):
36 ipython.display_formatter.active_types.append(mime)
37 ipython.display_formatter.formatters[mime].enabled = True
38 ipython.mime_renderers[mime] = handler
39
40 def load_ipython_extension(ipython):
41 register_mimerenderer(ipython, 'image/png', imcat)
42 register_mimerenderer(ipython, 'image/jpeg', imcat)
43 register_mimerenderer(ipython, 'text/latex', mathcat)
44
45 This example only work for iterm2 on mac os and skip error handling for brevity.
46 One could also invoke an external viewer with ``subporcess.run()`` and a
47 tempfile, which is left as an exercise.
48
49 So far only the hooks necessary for this are in place, but no default mime
50 renderers added; so inline images will only be available via extensions. We will
51 progressively enable these features by default in the next few releases, and
52 contribution is welcomed.
53
54
55
@@ -1,325 +1,325 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Displayhook for IPython.
2 """Displayhook for IPython.
3
3
4 This defines a callable class that IPython uses for `sys.displayhook`.
4 This defines a callable class that IPython uses for `sys.displayhook`.
5 """
5 """
6
6
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 import builtins as builtin_mod
10 import builtins as builtin_mod
11 import sys
11 import sys
12 import io as _io
12 import io as _io
13 import tokenize
13 import tokenize
14
14
15 from traitlets.config.configurable import Configurable
15 from traitlets.config.configurable import Configurable
16 from traitlets import Instance, Float
16 from traitlets import Instance, Float
17 from warnings import warn
17 from warnings import warn
18
18
19 # TODO: Move the various attributes (cache_size, [others now moved]). Some
19 # TODO: Move the various attributes (cache_size, [others now moved]). Some
20 # of these are also attributes of InteractiveShell. They should be on ONE object
20 # of these are also attributes of InteractiveShell. They should be on ONE object
21 # only and the other objects should ask that one object for their values.
21 # only and the other objects should ask that one object for their values.
22
22
23 class DisplayHook(Configurable):
23 class DisplayHook(Configurable):
24 """The custom IPython displayhook to replace sys.displayhook.
24 """The custom IPython displayhook to replace sys.displayhook.
25
25
26 This class does many things, but the basic idea is that it is a callable
26 This class does many things, but the basic idea is that it is a callable
27 that gets called anytime user code returns a value.
27 that gets called anytime user code returns a value.
28 """
28 """
29
29
30 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
30 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
31 allow_none=True)
31 allow_none=True)
32 exec_result = Instance('IPython.core.interactiveshell.ExecutionResult',
32 exec_result = Instance('IPython.core.interactiveshell.ExecutionResult',
33 allow_none=True)
33 allow_none=True)
34 cull_fraction = Float(0.2)
34 cull_fraction = Float(0.2)
35
35
36 def __init__(self, shell=None, cache_size=1000, **kwargs):
36 def __init__(self, shell=None, cache_size=1000, **kwargs):
37 super(DisplayHook, self).__init__(shell=shell, **kwargs)
37 super(DisplayHook, self).__init__(shell=shell, **kwargs)
38 cache_size_min = 3
38 cache_size_min = 3
39 if cache_size <= 0:
39 if cache_size <= 0:
40 self.do_full_cache = 0
40 self.do_full_cache = 0
41 cache_size = 0
41 cache_size = 0
42 elif cache_size < cache_size_min:
42 elif cache_size < cache_size_min:
43 self.do_full_cache = 0
43 self.do_full_cache = 0
44 cache_size = 0
44 cache_size = 0
45 warn('caching was disabled (min value for cache size is %s).' %
45 warn('caching was disabled (min value for cache size is %s).' %
46 cache_size_min,stacklevel=3)
46 cache_size_min,stacklevel=3)
47 else:
47 else:
48 self.do_full_cache = 1
48 self.do_full_cache = 1
49
49
50 self.cache_size = cache_size
50 self.cache_size = cache_size
51
51
52 # we need a reference to the user-level namespace
52 # we need a reference to the user-level namespace
53 self.shell = shell
53 self.shell = shell
54
54
55 self._,self.__,self.___ = '','',''
55 self._,self.__,self.___ = '','',''
56
56
57 # these are deliberately global:
57 # these are deliberately global:
58 to_user_ns = {'_':self._,'__':self.__,'___':self.___}
58 to_user_ns = {'_':self._,'__':self.__,'___':self.___}
59 self.shell.user_ns.update(to_user_ns)
59 self.shell.user_ns.update(to_user_ns)
60
60
61 @property
61 @property
62 def prompt_count(self):
62 def prompt_count(self):
63 return self.shell.execution_count
63 return self.shell.execution_count
64
64
65 #-------------------------------------------------------------------------
65 #-------------------------------------------------------------------------
66 # Methods used in __call__. Override these methods to modify the behavior
66 # Methods used in __call__. Override these methods to modify the behavior
67 # of the displayhook.
67 # of the displayhook.
68 #-------------------------------------------------------------------------
68 #-------------------------------------------------------------------------
69
69
70 def check_for_underscore(self):
70 def check_for_underscore(self):
71 """Check if the user has set the '_' variable by hand."""
71 """Check if the user has set the '_' variable by hand."""
72 # If something injected a '_' variable in __builtin__, delete
72 # If something injected a '_' variable in __builtin__, delete
73 # ipython's automatic one so we don't clobber that. gettext() in
73 # ipython's automatic one so we don't clobber that. gettext() in
74 # particular uses _, so we need to stay away from it.
74 # particular uses _, so we need to stay away from it.
75 if '_' in builtin_mod.__dict__:
75 if '_' in builtin_mod.__dict__:
76 try:
76 try:
77 user_value = self.shell.user_ns['_']
77 user_value = self.shell.user_ns['_']
78 if user_value is not self._:
78 if user_value is not self._:
79 return
79 return
80 del self.shell.user_ns['_']
80 del self.shell.user_ns['_']
81 except KeyError:
81 except KeyError:
82 pass
82 pass
83
83
84 def quiet(self):
84 def quiet(self):
85 """Should we silence the display hook because of ';'?"""
85 """Should we silence the display hook because of ';'?"""
86 # do not print output if input ends in ';'
86 # do not print output if input ends in ';'
87
87
88 try:
88 try:
89 cell = self.shell.history_manager.input_hist_parsed[-1]
89 cell = self.shell.history_manager.input_hist_parsed[-1]
90 except IndexError:
90 except IndexError:
91 # some uses of ipshellembed may fail here
91 # some uses of ipshellembed may fail here
92 return False
92 return False
93
93
94 sio = _io.StringIO(cell)
94 sio = _io.StringIO(cell)
95 tokens = list(tokenize.generate_tokens(sio.readline))
95 tokens = list(tokenize.generate_tokens(sio.readline))
96
96
97 for token in reversed(tokens):
97 for token in reversed(tokens):
98 if token[0] in (tokenize.ENDMARKER, tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT):
98 if token[0] in (tokenize.ENDMARKER, tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT):
99 continue
99 continue
100 if (token[0] == tokenize.OP) and (token[1] == ';'):
100 if (token[0] == tokenize.OP) and (token[1] == ';'):
101 return True
101 return True
102 else:
102 else:
103 return False
103 return False
104
104
105 def start_displayhook(self):
105 def start_displayhook(self):
106 """Start the displayhook, initializing resources."""
106 """Start the displayhook, initializing resources."""
107 pass
107 pass
108
108
109 def write_output_prompt(self):
109 def write_output_prompt(self):
110 """Write the output prompt.
110 """Write the output prompt.
111
111
112 The default implementation simply writes the prompt to
112 The default implementation simply writes the prompt to
113 ``sys.stdout``.
113 ``sys.stdout``.
114 """
114 """
115 # Use write, not print which adds an extra space.
115 # Use write, not print which adds an extra space.
116 sys.stdout.write(self.shell.separate_out)
116 sys.stdout.write(self.shell.separate_out)
117 outprompt = 'Out[{}]: '.format(self.shell.execution_count)
117 outprompt = 'Out[{}]: '.format(self.shell.execution_count)
118 if self.do_full_cache:
118 if self.do_full_cache:
119 sys.stdout.write(outprompt)
119 sys.stdout.write(outprompt)
120
120
121 def compute_format_data(self, result):
121 def compute_format_data(self, result):
122 """Compute format data of the object to be displayed.
122 """Compute format data of the object to be displayed.
123
123
124 The format data is a generalization of the :func:`repr` of an object.
124 The format data is a generalization of the :func:`repr` of an object.
125 In the default implementation the format data is a :class:`dict` of
125 In the default implementation the format data is a :class:`dict` of
126 key value pair where the keys are valid MIME types and the values
126 key value pair where the keys are valid MIME types and the values
127 are JSON'able data structure containing the raw data for that MIME
127 are JSON'able data structure containing the raw data for that MIME
128 type. It is up to frontends to determine pick a MIME to to use and
128 type. It is up to frontends to determine pick a MIME to to use and
129 display that data in an appropriate manner.
129 display that data in an appropriate manner.
130
130
131 This method only computes the format data for the object and should
131 This method only computes the format data for the object and should
132 NOT actually print or write that to a stream.
132 NOT actually print or write that to a stream.
133
133
134 Parameters
134 Parameters
135 ----------
135 ----------
136 result : object
136 result : object
137 The Python object passed to the display hook, whose format will be
137 The Python object passed to the display hook, whose format will be
138 computed.
138 computed.
139
139
140 Returns
140 Returns
141 -------
141 -------
142 (format_dict, md_dict) : dict
142 (format_dict, md_dict) : dict
143 format_dict is a :class:`dict` whose keys are valid MIME types and values are
143 format_dict is a :class:`dict` whose keys are valid MIME types and values are
144 JSON'able raw data for that MIME type. It is recommended that
144 JSON'able raw data for that MIME type. It is recommended that
145 all return values of this should always include the "text/plain"
145 all return values of this should always include the "text/plain"
146 MIME type representation of the object.
146 MIME type representation of the object.
147 md_dict is a :class:`dict` with the same MIME type keys
147 md_dict is a :class:`dict` with the same MIME type keys
148 of metadata associated with each output.
148 of metadata associated with each output.
149
149
150 """
150 """
151 return self.shell.display_formatter.format(result)
151 return self.shell.display_formatter.format(result)
152
152
153 # This can be set to True by the write_output_prompt method in a subclass
153 # This can be set to True by the write_output_prompt method in a subclass
154 prompt_end_newline = False
154 prompt_end_newline = False
155
155
156 def write_format_data(self, format_dict, md_dict=None):
156 def write_format_data(self, format_dict, md_dict=None) -> None:
157 """Write the format data dict to the frontend.
157 """Write the format data dict to the frontend.
158
158
159 This default version of this method simply writes the plain text
159 This default version of this method simply writes the plain text
160 representation of the object to ``sys.stdout``. Subclasses should
160 representation of the object to ``sys.stdout``. Subclasses should
161 override this method to send the entire `format_dict` to the
161 override this method to send the entire `format_dict` to the
162 frontends.
162 frontends.
163
163
164 Parameters
164 Parameters
165 ----------
165 ----------
166 format_dict : dict
166 format_dict : dict
167 The format dict for the object passed to `sys.displayhook`.
167 The format dict for the object passed to `sys.displayhook`.
168 md_dict : dict (optional)
168 md_dict : dict (optional)
169 The metadata dict to be associated with the display data.
169 The metadata dict to be associated with the display data.
170 """
170 """
171 if 'text/plain' not in format_dict:
171 if 'text/plain' not in format_dict:
172 # nothing to do
172 # nothing to do
173 return
173 return
174 # We want to print because we want to always make sure we have a
174 # We want to print because we want to always make sure we have a
175 # newline, even if all the prompt separators are ''. This is the
175 # newline, even if all the prompt separators are ''. This is the
176 # standard IPython behavior.
176 # standard IPython behavior.
177 result_repr = format_dict['text/plain']
177 result_repr = format_dict['text/plain']
178 if '\n' in result_repr:
178 if '\n' in result_repr:
179 # So that multi-line strings line up with the left column of
179 # So that multi-line strings line up with the left column of
180 # the screen, instead of having the output prompt mess up
180 # the screen, instead of having the output prompt mess up
181 # their first line.
181 # their first line.
182 # We use the prompt template instead of the expanded prompt
182 # We use the prompt template instead of the expanded prompt
183 # because the expansion may add ANSI escapes that will interfere
183 # because the expansion may add ANSI escapes that will interfere
184 # with our ability to determine whether or not we should add
184 # with our ability to determine whether or not we should add
185 # a newline.
185 # a newline.
186 if not self.prompt_end_newline:
186 if not self.prompt_end_newline:
187 # But avoid extraneous empty lines.
187 # But avoid extraneous empty lines.
188 result_repr = '\n' + result_repr
188 result_repr = '\n' + result_repr
189
189
190 try:
190 try:
191 print(result_repr)
191 print(result_repr)
192 except UnicodeEncodeError:
192 except UnicodeEncodeError:
193 # If a character is not supported by the terminal encoding replace
193 # If a character is not supported by the terminal encoding replace
194 # it with its \u or \x representation
194 # it with its \u or \x representation
195 print(result_repr.encode(sys.stdout.encoding,'backslashreplace').decode(sys.stdout.encoding))
195 print(result_repr.encode(sys.stdout.encoding,'backslashreplace').decode(sys.stdout.encoding))
196
196
197 def update_user_ns(self, result):
197 def update_user_ns(self, result):
198 """Update user_ns with various things like _, __, _1, etc."""
198 """Update user_ns with various things like _, __, _1, etc."""
199
199
200 # Avoid recursive reference when displaying _oh/Out
200 # Avoid recursive reference when displaying _oh/Out
201 if result is not self.shell.user_ns['_oh']:
201 if result is not self.shell.user_ns['_oh']:
202 if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache:
202 if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache:
203 self.cull_cache()
203 self.cull_cache()
204
204
205 # Don't overwrite '_' and friends if '_' is in __builtin__
205 # Don't overwrite '_' and friends if '_' is in __builtin__
206 # (otherwise we cause buggy behavior for things like gettext). and
206 # (otherwise we cause buggy behavior for things like gettext). and
207 # do not overwrite _, __ or ___ if one of these has been assigned
207 # do not overwrite _, __ or ___ if one of these has been assigned
208 # by the user.
208 # by the user.
209 update_unders = True
209 update_unders = True
210 for unders in ['_'*i for i in range(1,4)]:
210 for unders in ['_'*i for i in range(1,4)]:
211 if not unders in self.shell.user_ns:
211 if not unders in self.shell.user_ns:
212 continue
212 continue
213 if getattr(self, unders) is not self.shell.user_ns.get(unders):
213 if getattr(self, unders) is not self.shell.user_ns.get(unders):
214 update_unders = False
214 update_unders = False
215
215
216 self.___ = self.__
216 self.___ = self.__
217 self.__ = self._
217 self.__ = self._
218 self._ = result
218 self._ = result
219
219
220 if ('_' not in builtin_mod.__dict__) and (update_unders):
220 if ('_' not in builtin_mod.__dict__) and (update_unders):
221 self.shell.push({'_':self._,
221 self.shell.push({'_':self._,
222 '__':self.__,
222 '__':self.__,
223 '___':self.___}, interactive=False)
223 '___':self.___}, interactive=False)
224
224
225 # hackish access to top-level namespace to create _1,_2... dynamically
225 # hackish access to top-level namespace to create _1,_2... dynamically
226 to_main = {}
226 to_main = {}
227 if self.do_full_cache:
227 if self.do_full_cache:
228 new_result = '_%s' % self.prompt_count
228 new_result = '_%s' % self.prompt_count
229 to_main[new_result] = result
229 to_main[new_result] = result
230 self.shell.push(to_main, interactive=False)
230 self.shell.push(to_main, interactive=False)
231 self.shell.user_ns['_oh'][self.prompt_count] = result
231 self.shell.user_ns['_oh'][self.prompt_count] = result
232
232
233 def fill_exec_result(self, result):
233 def fill_exec_result(self, result):
234 if self.exec_result is not None:
234 if self.exec_result is not None:
235 self.exec_result.result = result
235 self.exec_result.result = result
236
236
237 def log_output(self, format_dict):
237 def log_output(self, format_dict):
238 """Log the output."""
238 """Log the output."""
239 if 'text/plain' not in format_dict:
239 if 'text/plain' not in format_dict:
240 # nothing to do
240 # nothing to do
241 return
241 return
242 if self.shell.logger.log_output:
242 if self.shell.logger.log_output:
243 self.shell.logger.log_write(format_dict['text/plain'], 'output')
243 self.shell.logger.log_write(format_dict['text/plain'], 'output')
244 self.shell.history_manager.output_hist_reprs[self.prompt_count] = \
244 self.shell.history_manager.output_hist_reprs[self.prompt_count] = \
245 format_dict['text/plain']
245 format_dict['text/plain']
246
246
247 def finish_displayhook(self):
247 def finish_displayhook(self):
248 """Finish up all displayhook activities."""
248 """Finish up all displayhook activities."""
249 sys.stdout.write(self.shell.separate_out2)
249 sys.stdout.write(self.shell.separate_out2)
250 sys.stdout.flush()
250 sys.stdout.flush()
251
251
252 def __call__(self, result=None):
252 def __call__(self, result=None):
253 """Printing with history cache management.
253 """Printing with history cache management.
254
254
255 This is invoked every time the interpreter needs to print, and is
255 This is invoked every time the interpreter needs to print, and is
256 activated by setting the variable sys.displayhook to it.
256 activated by setting the variable sys.displayhook to it.
257 """
257 """
258 self.check_for_underscore()
258 self.check_for_underscore()
259 if result is not None and not self.quiet():
259 if result is not None and not self.quiet():
260 self.start_displayhook()
260 self.start_displayhook()
261 self.write_output_prompt()
261 self.write_output_prompt()
262 format_dict, md_dict = self.compute_format_data(result)
262 format_dict, md_dict = self.compute_format_data(result)
263 self.update_user_ns(result)
263 self.update_user_ns(result)
264 self.fill_exec_result(result)
264 self.fill_exec_result(result)
265 if format_dict:
265 if format_dict:
266 self.write_format_data(format_dict, md_dict)
266 self.write_format_data(format_dict, md_dict)
267 self.log_output(format_dict)
267 self.log_output(format_dict)
268 self.finish_displayhook()
268 self.finish_displayhook()
269
269
270 def cull_cache(self):
270 def cull_cache(self):
271 """Output cache is full, cull the oldest entries"""
271 """Output cache is full, cull the oldest entries"""
272 oh = self.shell.user_ns.get('_oh', {})
272 oh = self.shell.user_ns.get('_oh', {})
273 sz = len(oh)
273 sz = len(oh)
274 cull_count = max(int(sz * self.cull_fraction), 2)
274 cull_count = max(int(sz * self.cull_fraction), 2)
275 warn('Output cache limit (currently {sz} entries) hit.\n'
275 warn('Output cache limit (currently {sz} entries) hit.\n'
276 'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count))
276 'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count))
277
277
278 for i, n in enumerate(sorted(oh)):
278 for i, n in enumerate(sorted(oh)):
279 if i >= cull_count:
279 if i >= cull_count:
280 break
280 break
281 self.shell.user_ns.pop('_%i' % n, None)
281 self.shell.user_ns.pop('_%i' % n, None)
282 oh.pop(n, None)
282 oh.pop(n, None)
283
283
284
284
285 def flush(self):
285 def flush(self):
286 if not self.do_full_cache:
286 if not self.do_full_cache:
287 raise ValueError("You shouldn't have reached the cache flush "
287 raise ValueError("You shouldn't have reached the cache flush "
288 "if full caching is not enabled!")
288 "if full caching is not enabled!")
289 # delete auto-generated vars from global namespace
289 # delete auto-generated vars from global namespace
290
290
291 for n in range(1,self.prompt_count + 1):
291 for n in range(1,self.prompt_count + 1):
292 key = '_'+repr(n)
292 key = '_'+repr(n)
293 try:
293 try:
294 del self.shell.user_ns[key]
294 del self.shell.user_ns[key]
295 except: pass
295 except: pass
296 # In some embedded circumstances, the user_ns doesn't have the
296 # In some embedded circumstances, the user_ns doesn't have the
297 # '_oh' key set up.
297 # '_oh' key set up.
298 oh = self.shell.user_ns.get('_oh', None)
298 oh = self.shell.user_ns.get('_oh', None)
299 if oh is not None:
299 if oh is not None:
300 oh.clear()
300 oh.clear()
301
301
302 # Release our own references to objects:
302 # Release our own references to objects:
303 self._, self.__, self.___ = '', '', ''
303 self._, self.__, self.___ = '', '', ''
304
304
305 if '_' not in builtin_mod.__dict__:
305 if '_' not in builtin_mod.__dict__:
306 self.shell.user_ns.update({'_':self._,'__':self.__,'___':self.___})
306 self.shell.user_ns.update({'_':self._,'__':self.__,'___':self.___})
307 import gc
307 import gc
308 # TODO: Is this really needed?
308 # TODO: Is this really needed?
309 # IronPython blocks here forever
309 # IronPython blocks here forever
310 if sys.platform != "cli":
310 if sys.platform != "cli":
311 gc.collect()
311 gc.collect()
312
312
313
313
314 class CapturingDisplayHook(object):
314 class CapturingDisplayHook(object):
315 def __init__(self, shell, outputs=None):
315 def __init__(self, shell, outputs=None):
316 self.shell = shell
316 self.shell = shell
317 if outputs is None:
317 if outputs is None:
318 outputs = []
318 outputs = []
319 self.outputs = outputs
319 self.outputs = outputs
320
320
321 def __call__(self, result=None):
321 def __call__(self, result=None):
322 if result is None:
322 if result is None:
323 return
323 return
324 format_dict, md_dict = self.shell.display_formatter.format(result)
324 format_dict, md_dict = self.shell.display_formatter.format(result)
325 self.outputs.append({ 'data': format_dict, 'metadata': md_dict })
325 self.outputs.append({ 'data': format_dict, 'metadata': md_dict })
@@ -1,125 +1,138 b''
1 """An interface for publishing rich data to frontends.
1 """An interface for publishing rich data to frontends.
2
2
3 There are two components of the display system:
3 There are two components of the display system:
4
4
5 * Display formatters, which take a Python object and compute the
5 * Display formatters, which take a Python object and compute the
6 representation of the object in various formats (text, HTML, SVG, etc.).
6 representation of the object in various formats (text, HTML, SVG, etc.).
7 * The display publisher that is used to send the representation data to the
7 * The display publisher that is used to send the representation data to the
8 various frontends.
8 various frontends.
9
9
10 This module defines the logic display publishing. The display publisher uses
10 This module defines the logic display publishing. The display publisher uses
11 the ``display_data`` message type that is defined in the IPython messaging
11 the ``display_data`` message type that is defined in the IPython messaging
12 spec.
12 spec.
13 """
13 """
14
14
15 # Copyright (c) IPython Development Team.
15 # Copyright (c) IPython Development Team.
16 # Distributed under the terms of the Modified BSD License.
16 # Distributed under the terms of the Modified BSD License.
17
17
18
18
19 import sys
19 import sys
20
20
21 from traitlets.config.configurable import Configurable
21 from traitlets.config.configurable import Configurable
22 from traitlets import List
22 from traitlets import List, Dict
23
23
24 # This used to be defined here - it is imported for backwards compatibility
24 # This used to be defined here - it is imported for backwards compatibility
25 from .display import publish_display_data
25 from .display import publish_display_data
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Main payload class
28 # Main payload class
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31
31 class DisplayPublisher(Configurable):
32 class DisplayPublisher(Configurable):
32 """A traited class that publishes display data to frontends.
33 """A traited class that publishes display data to frontends.
33
34
34 Instances of this class are created by the main IPython object and should
35 Instances of this class are created by the main IPython object and should
35 be accessed there.
36 be accessed there.
36 """
37 """
37
38
39 def __init__(self, shell=None, *args, **kwargs):
40 self.shell = shell
41 super().__init__(*args, **kwargs)
42
38 def _validate_data(self, data, metadata=None):
43 def _validate_data(self, data, metadata=None):
39 """Validate the display data.
44 """Validate the display data.
40
45
41 Parameters
46 Parameters
42 ----------
47 ----------
43 data : dict
48 data : dict
44 The formata data dictionary.
49 The formata data dictionary.
45 metadata : dict
50 metadata : dict
46 Any metadata for the data.
51 Any metadata for the data.
47 """
52 """
48
53
49 if not isinstance(data, dict):
54 if not isinstance(data, dict):
50 raise TypeError('data must be a dict, got: %r' % data)
55 raise TypeError('data must be a dict, got: %r' % data)
51 if metadata is not None:
56 if metadata is not None:
52 if not isinstance(metadata, dict):
57 if not isinstance(metadata, dict):
53 raise TypeError('metadata must be a dict, got: %r' % data)
58 raise TypeError('metadata must be a dict, got: %r' % data)
54
59
55 # use * to indicate transient, update are keyword-only
60 # use * to indicate transient, update are keyword-only
56 def publish(self, data, metadata=None, source=None, *, transient=None, update=False, **kwargs):
61 def publish(self, data, metadata=None, source=None, *, transient=None, update=False, **kwargs) -> None:
57 """Publish data and metadata to all frontends.
62 """Publish data and metadata to all frontends.
58
63
59 See the ``display_data`` message in the messaging documentation for
64 See the ``display_data`` message in the messaging documentation for
60 more details about this message type.
65 more details about this message type.
61
66
62 The following MIME types are currently implemented:
67 The following MIME types are currently implemented:
63
68
64 * text/plain
69 * text/plain
65 * text/html
70 * text/html
66 * text/markdown
71 * text/markdown
67 * text/latex
72 * text/latex
68 * application/json
73 * application/json
69 * application/javascript
74 * application/javascript
70 * image/png
75 * image/png
71 * image/jpeg
76 * image/jpeg
72 * image/svg+xml
77 * image/svg+xml
73
78
74 Parameters
79 Parameters
75 ----------
80 ----------
76 data : dict
81 data : dict
77 A dictionary having keys that are valid MIME types (like
82 A dictionary having keys that are valid MIME types (like
78 'text/plain' or 'image/svg+xml') and values that are the data for
83 'text/plain' or 'image/svg+xml') and values that are the data for
79 that MIME type. The data itself must be a JSON'able data
84 that MIME type. The data itself must be a JSON'able data
80 structure. Minimally all data should have the 'text/plain' data,
85 structure. Minimally all data should have the 'text/plain' data,
81 which can be displayed by all frontends. If more than the plain
86 which can be displayed by all frontends. If more than the plain
82 text is given, it is up to the frontend to decide which
87 text is given, it is up to the frontend to decide which
83 representation to use.
88 representation to use.
84 metadata : dict
89 metadata : dict
85 A dictionary for metadata related to the data. This can contain
90 A dictionary for metadata related to the data. This can contain
86 arbitrary key, value pairs that frontends can use to interpret
91 arbitrary key, value pairs that frontends can use to interpret
87 the data. Metadata specific to each mime-type can be specified
92 the data. Metadata specific to each mime-type can be specified
88 in the metadata dict with the same mime-type keys as
93 in the metadata dict with the same mime-type keys as
89 the data itself.
94 the data itself.
90 source : str, deprecated
95 source : str, deprecated
91 Unused.
96 Unused.
92 transient: dict, keyword-only
97 transient: dict, keyword-only
93 A dictionary for transient data.
98 A dictionary for transient data.
94 Data in this dictionary should not be persisted as part of saving this output.
99 Data in this dictionary should not be persisted as part of saving this output.
95 Examples include 'display_id'.
100 Examples include 'display_id'.
96 update: bool, keyword-only, default: False
101 update: bool, keyword-only, default: False
97 If True, only update existing outputs with the same display_id,
102 If True, only update existing outputs with the same display_id,
98 rather than creating a new output.
103 rather than creating a new output.
99 """
104 """
100
105
101 # The default is to simply write the plain text data using sys.stdout.
106 handlers = {}
107 if self.shell is not None:
108 handlers = self.shell.mime_renderers
109
110 for mime, handler in handlers.items():
111 if mime in data:
112 handler(data[mime], metadata.get(mime, None))
113 return
114
102 if 'text/plain' in data:
115 if 'text/plain' in data:
103 print(data['text/plain'])
116 print(data['text/plain'])
104
117
105 def clear_output(self, wait=False):
118 def clear_output(self, wait=False):
106 """Clear the output of the cell receiving output."""
119 """Clear the output of the cell receiving output."""
107 print('\033[2K\r', end='')
120 print('\033[2K\r', end='')
108 sys.stdout.flush()
121 sys.stdout.flush()
109 print('\033[2K\r', end='')
122 print('\033[2K\r', end='')
110 sys.stderr.flush()
123 sys.stderr.flush()
111
124
112
125
113 class CapturingDisplayPublisher(DisplayPublisher):
126 class CapturingDisplayPublisher(DisplayPublisher):
114 """A DisplayPublisher that stores"""
127 """A DisplayPublisher that stores"""
115 outputs = List()
128 outputs = List()
116
129
117 def publish(self, data, metadata=None, source=None, *, transient=None, update=False):
130 def publish(self, data, metadata=None, source=None, *, transient=None, update=False):
118 self.outputs.append({'data':data, 'metadata':metadata,
131 self.outputs.append({'data':data, 'metadata':metadata,
119 'transient':transient, 'update':update})
132 'transient':transient, 'update':update})
120
133
121 def clear_output(self, wait=False):
134 def clear_output(self, wait=False):
122 super(CapturingDisplayPublisher, self).clear_output(wait)
135 super(CapturingDisplayPublisher, self).clear_output(wait)
123
136
124 # empty the list, *do not* reassign a new list
137 # empty the list, *do not* reassign a new list
125 self.outputs.clear()
138 self.outputs.clear()
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,556 +1,558 b''
1 """IPython terminal interface using prompt_toolkit"""
1 """IPython terminal interface using prompt_toolkit"""
2
2
3 import os
3 import os
4 import sys
4 import sys
5 import warnings
5 import warnings
6 from warnings import warn
6 from warnings import warn
7
7
8 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
8 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
9 from IPython.utils import io
9 from IPython.utils import io
10 from IPython.utils.py3compat import input
10 from IPython.utils.py3compat import input
11 from IPython.utils.terminal import toggle_set_term_title, set_term_title
11 from IPython.utils.terminal import toggle_set_term_title, set_term_title
12 from IPython.utils.process import abbrev_cwd
12 from IPython.utils.process import abbrev_cwd
13 from traitlets import (
13 from traitlets import (
14 Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union,
14 Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union,
15 Any, validate
15 Any, validate
16 )
16 )
17
17
18 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
18 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
19 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
19 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
20 from prompt_toolkit.formatted_text import PygmentsTokens
20 from prompt_toolkit.formatted_text import PygmentsTokens
21 from prompt_toolkit.history import InMemoryHistory
21 from prompt_toolkit.history import InMemoryHistory
22 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
22 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
23 from prompt_toolkit.output import ColorDepth
23 from prompt_toolkit.output import ColorDepth
24 from prompt_toolkit.patch_stdout import patch_stdout
24 from prompt_toolkit.patch_stdout import patch_stdout
25 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
25 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
26 from prompt_toolkit.styles import DynamicStyle, merge_styles
26 from prompt_toolkit.styles import DynamicStyle, merge_styles
27 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
27 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
28
28
29 from pygments.styles import get_style_by_name
29 from pygments.styles import get_style_by_name
30 from pygments.style import Style
30 from pygments.style import Style
31 from pygments.token import Token
31 from pygments.token import Token
32
32
33 from .debugger import TerminalPdb, Pdb
33 from .debugger import TerminalPdb, Pdb
34 from .magics import TerminalMagics
34 from .magics import TerminalMagics
35 from .pt_inputhooks import get_inputhook_name_and_func
35 from .pt_inputhooks import get_inputhook_name_and_func
36 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
36 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
37 from .ptutils import IPythonPTCompleter, IPythonPTLexer
37 from .ptutils import IPythonPTCompleter, IPythonPTLexer
38 from .shortcuts import create_ipython_shortcuts
38 from .shortcuts import create_ipython_shortcuts
39
39
40 DISPLAY_BANNER_DEPRECATED = object()
40 DISPLAY_BANNER_DEPRECATED = object()
41
41
42
42
43 class _NoStyle(Style): pass
43 class _NoStyle(Style): pass
44
44
45
45
46
46
47 _style_overrides_light_bg = {
47 _style_overrides_light_bg = {
48 Token.Prompt: '#0000ff',
48 Token.Prompt: '#0000ff',
49 Token.PromptNum: '#0000ee bold',
49 Token.PromptNum: '#0000ee bold',
50 Token.OutPrompt: '#cc0000',
50 Token.OutPrompt: '#cc0000',
51 Token.OutPromptNum: '#bb0000 bold',
51 Token.OutPromptNum: '#bb0000 bold',
52 }
52 }
53
53
54 _style_overrides_linux = {
54 _style_overrides_linux = {
55 Token.Prompt: '#00cc00',
55 Token.Prompt: '#00cc00',
56 Token.PromptNum: '#00bb00 bold',
56 Token.PromptNum: '#00bb00 bold',
57 Token.OutPrompt: '#cc0000',
57 Token.OutPrompt: '#cc0000',
58 Token.OutPromptNum: '#bb0000 bold',
58 Token.OutPromptNum: '#bb0000 bold',
59 }
59 }
60
60
61 def get_default_editor():
61 def get_default_editor():
62 try:
62 try:
63 return os.environ['EDITOR']
63 return os.environ['EDITOR']
64 except KeyError:
64 except KeyError:
65 pass
65 pass
66 except UnicodeError:
66 except UnicodeError:
67 warn("$EDITOR environment variable is not pure ASCII. Using platform "
67 warn("$EDITOR environment variable is not pure ASCII. Using platform "
68 "default editor.")
68 "default editor.")
69
69
70 if os.name == 'posix':
70 if os.name == 'posix':
71 return 'vi' # the only one guaranteed to be there!
71 return 'vi' # the only one guaranteed to be there!
72 else:
72 else:
73 return 'notepad' # same in Windows!
73 return 'notepad' # same in Windows!
74
74
75 # conservatively check for tty
75 # conservatively check for tty
76 # overridden streams can result in things like:
76 # overridden streams can result in things like:
77 # - sys.stdin = None
77 # - sys.stdin = None
78 # - no isatty method
78 # - no isatty method
79 for _name in ('stdin', 'stdout', 'stderr'):
79 for _name in ('stdin', 'stdout', 'stderr'):
80 _stream = getattr(sys, _name)
80 _stream = getattr(sys, _name)
81 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
81 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
82 _is_tty = False
82 _is_tty = False
83 break
83 break
84 else:
84 else:
85 _is_tty = True
85 _is_tty = True
86
86
87
87
88 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
88 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
89
89
90 class TerminalInteractiveShell(InteractiveShell):
90 class TerminalInteractiveShell(InteractiveShell):
91 mime_renderers = Dict().tag(config=True)
92
91 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
93 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
92 'to reserve for the completion menu'
94 'to reserve for the completion menu'
93 ).tag(config=True)
95 ).tag(config=True)
94
96
95 pt_app = None
97 pt_app = None
96 debugger_history = None
98 debugger_history = None
97
99
98 simple_prompt = Bool(_use_simple_prompt,
100 simple_prompt = Bool(_use_simple_prompt,
99 help="""Use `raw_input` for the REPL, without completion and prompt colors.
101 help="""Use `raw_input` for the REPL, without completion and prompt colors.
100
102
101 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
103 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
102 IPython own testing machinery, and emacs inferior-shell integration through elpy.
104 IPython own testing machinery, and emacs inferior-shell integration through elpy.
103
105
104 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
106 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
105 environment variable is set, or the current terminal is not a tty."""
107 environment variable is set, or the current terminal is not a tty."""
106 ).tag(config=True)
108 ).tag(config=True)
107
109
108 @property
110 @property
109 def debugger_cls(self):
111 def debugger_cls(self):
110 return Pdb if self.simple_prompt else TerminalPdb
112 return Pdb if self.simple_prompt else TerminalPdb
111
113
112 confirm_exit = Bool(True,
114 confirm_exit = Bool(True,
113 help="""
115 help="""
114 Set to confirm when you try to exit IPython with an EOF (Control-D
116 Set to confirm when you try to exit IPython with an EOF (Control-D
115 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
117 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
116 you can force a direct exit without any confirmation.""",
118 you can force a direct exit without any confirmation.""",
117 ).tag(config=True)
119 ).tag(config=True)
118
120
119 editing_mode = Unicode('emacs',
121 editing_mode = Unicode('emacs',
120 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
122 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
121 ).tag(config=True)
123 ).tag(config=True)
122
124
123 mouse_support = Bool(False,
125 mouse_support = Bool(False,
124 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
126 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
125 ).tag(config=True)
127 ).tag(config=True)
126
128
127 # We don't load the list of styles for the help string, because loading
129 # We don't load the list of styles for the help string, because loading
128 # Pygments plugins takes time and can cause unexpected errors.
130 # Pygments plugins takes time and can cause unexpected errors.
129 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
131 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
130 help="""The name or class of a Pygments style to use for syntax
132 help="""The name or class of a Pygments style to use for syntax
131 highlighting. To see available styles, run `pygmentize -L styles`."""
133 highlighting. To see available styles, run `pygmentize -L styles`."""
132 ).tag(config=True)
134 ).tag(config=True)
133
135
134 @validate('editing_mode')
136 @validate('editing_mode')
135 def _validate_editing_mode(self, proposal):
137 def _validate_editing_mode(self, proposal):
136 if proposal['value'].lower() == 'vim':
138 if proposal['value'].lower() == 'vim':
137 proposal['value']= 'vi'
139 proposal['value']= 'vi'
138 elif proposal['value'].lower() == 'default':
140 elif proposal['value'].lower() == 'default':
139 proposal['value']= 'emacs'
141 proposal['value']= 'emacs'
140
142
141 if hasattr(EditingMode, proposal['value'].upper()):
143 if hasattr(EditingMode, proposal['value'].upper()):
142 return proposal['value'].lower()
144 return proposal['value'].lower()
143
145
144 return self.editing_mode
146 return self.editing_mode
145
147
146
148
147 @observe('editing_mode')
149 @observe('editing_mode')
148 def _editing_mode(self, change):
150 def _editing_mode(self, change):
149 u_mode = change.new.upper()
151 u_mode = change.new.upper()
150 if self.pt_app:
152 if self.pt_app:
151 self.pt_app.editing_mode = u_mode
153 self.pt_app.editing_mode = u_mode
152
154
153 @observe('highlighting_style')
155 @observe('highlighting_style')
154 @observe('colors')
156 @observe('colors')
155 def _highlighting_style_changed(self, change):
157 def _highlighting_style_changed(self, change):
156 self.refresh_style()
158 self.refresh_style()
157
159
158 def refresh_style(self):
160 def refresh_style(self):
159 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
161 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
160
162
161
163
162 highlighting_style_overrides = Dict(
164 highlighting_style_overrides = Dict(
163 help="Override highlighting format for specific tokens"
165 help="Override highlighting format for specific tokens"
164 ).tag(config=True)
166 ).tag(config=True)
165
167
166 true_color = Bool(False,
168 true_color = Bool(False,
167 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
169 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
168 "If your terminal supports true color, the following command "
170 "If your terminal supports true color, the following command "
169 "should print 'TRUECOLOR' in orange: "
171 "should print 'TRUECOLOR' in orange: "
170 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
172 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
171 ).tag(config=True)
173 ).tag(config=True)
172
174
173 editor = Unicode(get_default_editor(),
175 editor = Unicode(get_default_editor(),
174 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
176 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
175 ).tag(config=True)
177 ).tag(config=True)
176
178
177 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
179 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
178
180
179 prompts = Instance(Prompts)
181 prompts = Instance(Prompts)
180
182
181 @default('prompts')
183 @default('prompts')
182 def _prompts_default(self):
184 def _prompts_default(self):
183 return self.prompts_class(self)
185 return self.prompts_class(self)
184
186
185 # @observe('prompts')
187 # @observe('prompts')
186 # def _(self, change):
188 # def _(self, change):
187 # self._update_layout()
189 # self._update_layout()
188
190
189 @default('displayhook_class')
191 @default('displayhook_class')
190 def _displayhook_class_default(self):
192 def _displayhook_class_default(self):
191 return RichPromptDisplayHook
193 return RichPromptDisplayHook
192
194
193 term_title = Bool(True,
195 term_title = Bool(True,
194 help="Automatically set the terminal title"
196 help="Automatically set the terminal title"
195 ).tag(config=True)
197 ).tag(config=True)
196
198
197 term_title_format = Unicode("IPython: {cwd}",
199 term_title_format = Unicode("IPython: {cwd}",
198 help="Customize the terminal title format. This is a python format string. " +
200 help="Customize the terminal title format. This is a python format string. " +
199 "Available substitutions are: {cwd}."
201 "Available substitutions are: {cwd}."
200 ).tag(config=True)
202 ).tag(config=True)
201
203
202 display_completions = Enum(('column', 'multicolumn','readlinelike'),
204 display_completions = Enum(('column', 'multicolumn','readlinelike'),
203 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
205 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
204 "'readlinelike'. These options are for `prompt_toolkit`, see "
206 "'readlinelike'. These options are for `prompt_toolkit`, see "
205 "`prompt_toolkit` documentation for more information."
207 "`prompt_toolkit` documentation for more information."
206 ),
208 ),
207 default_value='multicolumn').tag(config=True)
209 default_value='multicolumn').tag(config=True)
208
210
209 highlight_matching_brackets = Bool(True,
211 highlight_matching_brackets = Bool(True,
210 help="Highlight matching brackets.",
212 help="Highlight matching brackets.",
211 ).tag(config=True)
213 ).tag(config=True)
212
214
213 extra_open_editor_shortcuts = Bool(False,
215 extra_open_editor_shortcuts = Bool(False,
214 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
216 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
215 "This is in addition to the F2 binding, which is always enabled."
217 "This is in addition to the F2 binding, which is always enabled."
216 ).tag(config=True)
218 ).tag(config=True)
217
219
218 handle_return = Any(None,
220 handle_return = Any(None,
219 help="Provide an alternative handler to be called when the user presses "
221 help="Provide an alternative handler to be called when the user presses "
220 "Return. This is an advanced option intended for debugging, which "
222 "Return. This is an advanced option intended for debugging, which "
221 "may be changed or removed in later releases."
223 "may be changed or removed in later releases."
222 ).tag(config=True)
224 ).tag(config=True)
223
225
224 enable_history_search = Bool(True,
226 enable_history_search = Bool(True,
225 help="Allows to enable/disable the prompt toolkit history search"
227 help="Allows to enable/disable the prompt toolkit history search"
226 ).tag(config=True)
228 ).tag(config=True)
227
229
228 prompt_includes_vi_mode = Bool(True,
230 prompt_includes_vi_mode = Bool(True,
229 help="Display the current vi mode (when using vi editing mode)."
231 help="Display the current vi mode (when using vi editing mode)."
230 ).tag(config=True)
232 ).tag(config=True)
231
233
232 @observe('term_title')
234 @observe('term_title')
233 def init_term_title(self, change=None):
235 def init_term_title(self, change=None):
234 # Enable or disable the terminal title.
236 # Enable or disable the terminal title.
235 if self.term_title:
237 if self.term_title:
236 toggle_set_term_title(True)
238 toggle_set_term_title(True)
237 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
239 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
238 else:
240 else:
239 toggle_set_term_title(False)
241 toggle_set_term_title(False)
240
242
241 def init_display_formatter(self):
243 def init_display_formatter(self):
242 super(TerminalInteractiveShell, self).init_display_formatter()
244 super(TerminalInteractiveShell, self).init_display_formatter()
243 # terminal only supports plain text
245 # terminal only supports plain text
244 self.display_formatter.active_types = ['text/plain']
246 self.display_formatter.active_types = ['text/plain']
245 # disable `_ipython_display_`
247 # disable `_ipython_display_`
246 self.display_formatter.ipython_display_formatter.enabled = False
248 self.display_formatter.ipython_display_formatter.enabled = False
247
249
248 def init_prompt_toolkit_cli(self):
250 def init_prompt_toolkit_cli(self):
249 if self.simple_prompt:
251 if self.simple_prompt:
250 # Fall back to plain non-interactive output for tests.
252 # Fall back to plain non-interactive output for tests.
251 # This is very limited.
253 # This is very limited.
252 def prompt():
254 def prompt():
253 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
255 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
254 lines = [input(prompt_text)]
256 lines = [input(prompt_text)]
255 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
257 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
256 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
258 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
257 lines.append( input(prompt_continuation) )
259 lines.append( input(prompt_continuation) )
258 return '\n'.join(lines)
260 return '\n'.join(lines)
259 self.prompt_for_code = prompt
261 self.prompt_for_code = prompt
260 return
262 return
261
263
262 # Set up keyboard shortcuts
264 # Set up keyboard shortcuts
263 key_bindings = create_ipython_shortcuts(self)
265 key_bindings = create_ipython_shortcuts(self)
264
266
265 # Pre-populate history from IPython's history database
267 # Pre-populate history from IPython's history database
266 history = InMemoryHistory()
268 history = InMemoryHistory()
267 last_cell = u""
269 last_cell = u""
268 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
270 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
269 include_latest=True):
271 include_latest=True):
270 # Ignore blank lines and consecutive duplicates
272 # Ignore blank lines and consecutive duplicates
271 cell = cell.rstrip()
273 cell = cell.rstrip()
272 if cell and (cell != last_cell):
274 if cell and (cell != last_cell):
273 history.append_string(cell)
275 history.append_string(cell)
274 last_cell = cell
276 last_cell = cell
275
277
276 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
278 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
277 self.style = DynamicStyle(lambda: self._style)
279 self.style = DynamicStyle(lambda: self._style)
278
280
279 editing_mode = getattr(EditingMode, self.editing_mode.upper())
281 editing_mode = getattr(EditingMode, self.editing_mode.upper())
280
282
281 self.pt_app = PromptSession(
283 self.pt_app = PromptSession(
282 editing_mode=editing_mode,
284 editing_mode=editing_mode,
283 key_bindings=key_bindings,
285 key_bindings=key_bindings,
284 history=history,
286 history=history,
285 completer=IPythonPTCompleter(shell=self),
287 completer=IPythonPTCompleter(shell=self),
286 enable_history_search = self.enable_history_search,
288 enable_history_search = self.enable_history_search,
287 style=self.style,
289 style=self.style,
288 include_default_pygments_style=False,
290 include_default_pygments_style=False,
289 mouse_support=self.mouse_support,
291 mouse_support=self.mouse_support,
290 enable_open_in_editor=self.extra_open_editor_shortcuts,
292 enable_open_in_editor=self.extra_open_editor_shortcuts,
291 color_depth=self.color_depth,
293 color_depth=self.color_depth,
292 **self._extra_prompt_options())
294 **self._extra_prompt_options())
293
295
294 def _make_style_from_name_or_cls(self, name_or_cls):
296 def _make_style_from_name_or_cls(self, name_or_cls):
295 """
297 """
296 Small wrapper that make an IPython compatible style from a style name
298 Small wrapper that make an IPython compatible style from a style name
297
299
298 We need that to add style for prompt ... etc.
300 We need that to add style for prompt ... etc.
299 """
301 """
300 style_overrides = {}
302 style_overrides = {}
301 if name_or_cls == 'legacy':
303 if name_or_cls == 'legacy':
302 legacy = self.colors.lower()
304 legacy = self.colors.lower()
303 if legacy == 'linux':
305 if legacy == 'linux':
304 style_cls = get_style_by_name('monokai')
306 style_cls = get_style_by_name('monokai')
305 style_overrides = _style_overrides_linux
307 style_overrides = _style_overrides_linux
306 elif legacy == 'lightbg':
308 elif legacy == 'lightbg':
307 style_overrides = _style_overrides_light_bg
309 style_overrides = _style_overrides_light_bg
308 style_cls = get_style_by_name('pastie')
310 style_cls = get_style_by_name('pastie')
309 elif legacy == 'neutral':
311 elif legacy == 'neutral':
310 # The default theme needs to be visible on both a dark background
312 # The default theme needs to be visible on both a dark background
311 # and a light background, because we can't tell what the terminal
313 # and a light background, because we can't tell what the terminal
312 # looks like. These tweaks to the default theme help with that.
314 # looks like. These tweaks to the default theme help with that.
313 style_cls = get_style_by_name('default')
315 style_cls = get_style_by_name('default')
314 style_overrides.update({
316 style_overrides.update({
315 Token.Number: '#007700',
317 Token.Number: '#007700',
316 Token.Operator: 'noinherit',
318 Token.Operator: 'noinherit',
317 Token.String: '#BB6622',
319 Token.String: '#BB6622',
318 Token.Name.Function: '#2080D0',
320 Token.Name.Function: '#2080D0',
319 Token.Name.Class: 'bold #2080D0',
321 Token.Name.Class: 'bold #2080D0',
320 Token.Name.Namespace: 'bold #2080D0',
322 Token.Name.Namespace: 'bold #2080D0',
321 Token.Prompt: '#009900',
323 Token.Prompt: '#009900',
322 Token.PromptNum: '#ansibrightgreen bold',
324 Token.PromptNum: '#ansibrightgreen bold',
323 Token.OutPrompt: '#990000',
325 Token.OutPrompt: '#990000',
324 Token.OutPromptNum: '#ansibrightred bold',
326 Token.OutPromptNum: '#ansibrightred bold',
325 })
327 })
326
328
327 # Hack: Due to limited color support on the Windows console
329 # Hack: Due to limited color support on the Windows console
328 # the prompt colors will be wrong without this
330 # the prompt colors will be wrong without this
329 if os.name == 'nt':
331 if os.name == 'nt':
330 style_overrides.update({
332 style_overrides.update({
331 Token.Prompt: '#ansidarkgreen',
333 Token.Prompt: '#ansidarkgreen',
332 Token.PromptNum: '#ansigreen bold',
334 Token.PromptNum: '#ansigreen bold',
333 Token.OutPrompt: '#ansidarkred',
335 Token.OutPrompt: '#ansidarkred',
334 Token.OutPromptNum: '#ansired bold',
336 Token.OutPromptNum: '#ansired bold',
335 })
337 })
336 elif legacy =='nocolor':
338 elif legacy =='nocolor':
337 style_cls=_NoStyle
339 style_cls=_NoStyle
338 style_overrides = {}
340 style_overrides = {}
339 else :
341 else :
340 raise ValueError('Got unknown colors: ', legacy)
342 raise ValueError('Got unknown colors: ', legacy)
341 else :
343 else :
342 if isinstance(name_or_cls, str):
344 if isinstance(name_or_cls, str):
343 style_cls = get_style_by_name(name_or_cls)
345 style_cls = get_style_by_name(name_or_cls)
344 else:
346 else:
345 style_cls = name_or_cls
347 style_cls = name_or_cls
346 style_overrides = {
348 style_overrides = {
347 Token.Prompt: '#009900',
349 Token.Prompt: '#009900',
348 Token.PromptNum: '#ansibrightgreen bold',
350 Token.PromptNum: '#ansibrightgreen bold',
349 Token.OutPrompt: '#990000',
351 Token.OutPrompt: '#990000',
350 Token.OutPromptNum: '#ansibrightred bold',
352 Token.OutPromptNum: '#ansibrightred bold',
351 }
353 }
352 style_overrides.update(self.highlighting_style_overrides)
354 style_overrides.update(self.highlighting_style_overrides)
353 style = merge_styles([
355 style = merge_styles([
354 style_from_pygments_cls(style_cls),
356 style_from_pygments_cls(style_cls),
355 style_from_pygments_dict(style_overrides),
357 style_from_pygments_dict(style_overrides),
356 ])
358 ])
357
359
358 return style
360 return style
359
361
360 @property
362 @property
361 def pt_complete_style(self):
363 def pt_complete_style(self):
362 return {
364 return {
363 'multicolumn': CompleteStyle.MULTI_COLUMN,
365 'multicolumn': CompleteStyle.MULTI_COLUMN,
364 'column': CompleteStyle.COLUMN,
366 'column': CompleteStyle.COLUMN,
365 'readlinelike': CompleteStyle.READLINE_LIKE,
367 'readlinelike': CompleteStyle.READLINE_LIKE,
366 }[self.display_completions]
368 }[self.display_completions]
367
369
368 @property
370 @property
369 def color_depth(self):
371 def color_depth(self):
370 return (ColorDepth.TRUE_COLOR if self.true_color else None)
372 return (ColorDepth.TRUE_COLOR if self.true_color else None)
371
373
372 def _extra_prompt_options(self):
374 def _extra_prompt_options(self):
373 """
375 """
374 Return the current layout option for the current Terminal InteractiveShell
376 Return the current layout option for the current Terminal InteractiveShell
375 """
377 """
376 def get_message():
378 def get_message():
377 return PygmentsTokens(self.prompts.in_prompt_tokens())
379 return PygmentsTokens(self.prompts.in_prompt_tokens())
378
380
379 return {
381 return {
380 'complete_in_thread': False,
382 'complete_in_thread': False,
381 'lexer':IPythonPTLexer(),
383 'lexer':IPythonPTLexer(),
382 'reserve_space_for_menu':self.space_for_menu,
384 'reserve_space_for_menu':self.space_for_menu,
383 'message': get_message,
385 'message': get_message,
384 'prompt_continuation': (
386 'prompt_continuation': (
385 lambda width, lineno, is_soft_wrap:
387 lambda width, lineno, is_soft_wrap:
386 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
388 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
387 'multiline': True,
389 'multiline': True,
388 'complete_style': self.pt_complete_style,
390 'complete_style': self.pt_complete_style,
389
391
390 # Highlight matching brackets, but only when this setting is
392 # Highlight matching brackets, but only when this setting is
391 # enabled, and only when the DEFAULT_BUFFER has the focus.
393 # enabled, and only when the DEFAULT_BUFFER has the focus.
392 'input_processors': [ConditionalProcessor(
394 'input_processors': [ConditionalProcessor(
393 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
395 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
394 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
396 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
395 Condition(lambda: self.highlight_matching_brackets))],
397 Condition(lambda: self.highlight_matching_brackets))],
396 'inputhook': self.inputhook,
398 'inputhook': self.inputhook,
397 }
399 }
398
400
399 def prompt_for_code(self):
401 def prompt_for_code(self):
400 if self.rl_next_input:
402 if self.rl_next_input:
401 default = self.rl_next_input
403 default = self.rl_next_input
402 self.rl_next_input = None
404 self.rl_next_input = None
403 else:
405 else:
404 default = ''
406 default = ''
405
407
406 with patch_stdout(raw=True):
408 with patch_stdout(raw=True):
407 text = self.pt_app.prompt(
409 text = self.pt_app.prompt(
408 default=default,
410 default=default,
409 # pre_run=self.pre_prompt,# reset_current_buffer=True,
411 # pre_run=self.pre_prompt,# reset_current_buffer=True,
410 **self._extra_prompt_options())
412 **self._extra_prompt_options())
411 return text
413 return text
412
414
413 def enable_win_unicode_console(self):
415 def enable_win_unicode_console(self):
414 if sys.version_info >= (3, 6):
416 if sys.version_info >= (3, 6):
415 # Since PEP 528, Python uses the unicode APIs for the Windows
417 # Since PEP 528, Python uses the unicode APIs for the Windows
416 # console by default, so WUC shouldn't be needed.
418 # console by default, so WUC shouldn't be needed.
417 return
419 return
418
420
419 import win_unicode_console
421 import win_unicode_console
420 win_unicode_console.enable()
422 win_unicode_console.enable()
421
423
422 def init_io(self):
424 def init_io(self):
423 if sys.platform not in {'win32', 'cli'}:
425 if sys.platform not in {'win32', 'cli'}:
424 return
426 return
425
427
426 self.enable_win_unicode_console()
428 self.enable_win_unicode_console()
427
429
428 import colorama
430 import colorama
429 colorama.init()
431 colorama.init()
430
432
431 # For some reason we make these wrappers around stdout/stderr.
433 # For some reason we make these wrappers around stdout/stderr.
432 # For now, we need to reset them so all output gets coloured.
434 # For now, we need to reset them so all output gets coloured.
433 # https://github.com/ipython/ipython/issues/8669
435 # https://github.com/ipython/ipython/issues/8669
434 # io.std* are deprecated, but don't show our own deprecation warnings
436 # io.std* are deprecated, but don't show our own deprecation warnings
435 # during initialization of the deprecated API.
437 # during initialization of the deprecated API.
436 with warnings.catch_warnings():
438 with warnings.catch_warnings():
437 warnings.simplefilter('ignore', DeprecationWarning)
439 warnings.simplefilter('ignore', DeprecationWarning)
438 io.stdout = io.IOStream(sys.stdout)
440 io.stdout = io.IOStream(sys.stdout)
439 io.stderr = io.IOStream(sys.stderr)
441 io.stderr = io.IOStream(sys.stderr)
440
442
441 def init_magics(self):
443 def init_magics(self):
442 super(TerminalInteractiveShell, self).init_magics()
444 super(TerminalInteractiveShell, self).init_magics()
443 self.register_magics(TerminalMagics)
445 self.register_magics(TerminalMagics)
444
446
445 def init_alias(self):
447 def init_alias(self):
446 # The parent class defines aliases that can be safely used with any
448 # The parent class defines aliases that can be safely used with any
447 # frontend.
449 # frontend.
448 super(TerminalInteractiveShell, self).init_alias()
450 super(TerminalInteractiveShell, self).init_alias()
449
451
450 # Now define aliases that only make sense on the terminal, because they
452 # Now define aliases that only make sense on the terminal, because they
451 # need direct access to the console in a way that we can't emulate in
453 # need direct access to the console in a way that we can't emulate in
452 # GUI or web frontend
454 # GUI or web frontend
453 if os.name == 'posix':
455 if os.name == 'posix':
454 for cmd in ('clear', 'more', 'less', 'man'):
456 for cmd in ('clear', 'more', 'less', 'man'):
455 self.alias_manager.soft_define_alias(cmd, cmd)
457 self.alias_manager.soft_define_alias(cmd, cmd)
456
458
457
459
458 def __init__(self, *args, **kwargs):
460 def __init__(self, *args, **kwargs):
459 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
461 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
460 self.init_prompt_toolkit_cli()
462 self.init_prompt_toolkit_cli()
461 self.init_term_title()
463 self.init_term_title()
462 self.keep_running = True
464 self.keep_running = True
463
465
464 self.debugger_history = InMemoryHistory()
466 self.debugger_history = InMemoryHistory()
465
467
466 def ask_exit(self):
468 def ask_exit(self):
467 self.keep_running = False
469 self.keep_running = False
468
470
469 rl_next_input = None
471 rl_next_input = None
470
472
471 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
473 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
472
474
473 if display_banner is not DISPLAY_BANNER_DEPRECATED:
475 if display_banner is not DISPLAY_BANNER_DEPRECATED:
474 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
476 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
475
477
476 self.keep_running = True
478 self.keep_running = True
477 while self.keep_running:
479 while self.keep_running:
478 print(self.separate_in, end='')
480 print(self.separate_in, end='')
479
481
480 try:
482 try:
481 code = self.prompt_for_code()
483 code = self.prompt_for_code()
482 except EOFError:
484 except EOFError:
483 if (not self.confirm_exit) \
485 if (not self.confirm_exit) \
484 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
486 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
485 self.ask_exit()
487 self.ask_exit()
486
488
487 else:
489 else:
488 if code:
490 if code:
489 self.run_cell(code, store_history=True)
491 self.run_cell(code, store_history=True)
490
492
491 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
493 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
492 # An extra layer of protection in case someone mashing Ctrl-C breaks
494 # An extra layer of protection in case someone mashing Ctrl-C breaks
493 # out of our internal code.
495 # out of our internal code.
494 if display_banner is not DISPLAY_BANNER_DEPRECATED:
496 if display_banner is not DISPLAY_BANNER_DEPRECATED:
495 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
497 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
496 while True:
498 while True:
497 try:
499 try:
498 self.interact()
500 self.interact()
499 break
501 break
500 except KeyboardInterrupt as e:
502 except KeyboardInterrupt as e:
501 print("\n%s escaped interact()\n" % type(e).__name__)
503 print("\n%s escaped interact()\n" % type(e).__name__)
502 finally:
504 finally:
503 # An interrupt during the eventloop will mess up the
505 # An interrupt during the eventloop will mess up the
504 # internal state of the prompt_toolkit library.
506 # internal state of the prompt_toolkit library.
505 # Stopping the eventloop fixes this, see
507 # Stopping the eventloop fixes this, see
506 # https://github.com/ipython/ipython/pull/9867
508 # https://github.com/ipython/ipython/pull/9867
507 if hasattr(self, '_eventloop'):
509 if hasattr(self, '_eventloop'):
508 self._eventloop.stop()
510 self._eventloop.stop()
509
511
510 _inputhook = None
512 _inputhook = None
511 def inputhook(self, context):
513 def inputhook(self, context):
512 if self._inputhook is not None:
514 if self._inputhook is not None:
513 self._inputhook(context)
515 self._inputhook(context)
514
516
515 active_eventloop = None
517 active_eventloop = None
516 def enable_gui(self, gui=None):
518 def enable_gui(self, gui=None):
517 if gui:
519 if gui:
518 self.active_eventloop, self._inputhook =\
520 self.active_eventloop, self._inputhook =\
519 get_inputhook_name_and_func(gui)
521 get_inputhook_name_and_func(gui)
520 else:
522 else:
521 self.active_eventloop = self._inputhook = None
523 self.active_eventloop = self._inputhook = None
522
524
523 # Run !system commands directly, not through pipes, so terminal programs
525 # Run !system commands directly, not through pipes, so terminal programs
524 # work correctly.
526 # work correctly.
525 system = InteractiveShell.system_raw
527 system = InteractiveShell.system_raw
526
528
527 def auto_rewrite_input(self, cmd):
529 def auto_rewrite_input(self, cmd):
528 """Overridden from the parent class to use fancy rewriting prompt"""
530 """Overridden from the parent class to use fancy rewriting prompt"""
529 if not self.show_rewritten_input:
531 if not self.show_rewritten_input:
530 return
532 return
531
533
532 tokens = self.prompts.rewrite_prompt_tokens()
534 tokens = self.prompts.rewrite_prompt_tokens()
533 if self.pt_app:
535 if self.pt_app:
534 print_formatted_text(PygmentsTokens(tokens), end='',
536 print_formatted_text(PygmentsTokens(tokens), end='',
535 style=self.pt_app.app.style)
537 style=self.pt_app.app.style)
536 print(cmd)
538 print(cmd)
537 else:
539 else:
538 prompt = ''.join(s for t, s in tokens)
540 prompt = ''.join(s for t, s in tokens)
539 print(prompt, cmd, sep='')
541 print(prompt, cmd, sep='')
540
542
541 _prompts_before = None
543 _prompts_before = None
542 def switch_doctest_mode(self, mode):
544 def switch_doctest_mode(self, mode):
543 """Switch prompts to classic for %doctest_mode"""
545 """Switch prompts to classic for %doctest_mode"""
544 if mode:
546 if mode:
545 self._prompts_before = self.prompts
547 self._prompts_before = self.prompts
546 self.prompts = ClassicPrompts(self)
548 self.prompts = ClassicPrompts(self)
547 elif self._prompts_before:
549 elif self._prompts_before:
548 self.prompts = self._prompts_before
550 self.prompts = self._prompts_before
549 self._prompts_before = None
551 self._prompts_before = None
550 # self._update_layout()
552 # self._update_layout()
551
553
552
554
553 InteractiveShellABC.register(TerminalInteractiveShell)
555 InteractiveShellABC.register(TerminalInteractiveShell)
554
556
555 if __name__ == '__main__':
557 if __name__ == '__main__':
556 TerminalInteractiveShell.instance().interact()
558 TerminalInteractiveShell.instance().interact()
@@ -1,91 +1,102 b''
1 """Terminal input and output prompts."""
1 """Terminal input and output prompts."""
2
2
3 from pygments.token import Token
3 from pygments.token import Token
4 import sys
4 import sys
5
5
6 from IPython.core.displayhook import DisplayHook
6 from IPython.core.displayhook import DisplayHook
7
7
8 from prompt_toolkit.formatted_text import fragment_list_width, PygmentsTokens
8 from prompt_toolkit.formatted_text import fragment_list_width, PygmentsTokens
9 from prompt_toolkit.shortcuts import print_formatted_text
9 from prompt_toolkit.shortcuts import print_formatted_text
10
10
11
11
12 class Prompts(object):
12 class Prompts(object):
13 def __init__(self, shell):
13 def __init__(self, shell):
14 self.shell = shell
14 self.shell = shell
15
15
16 def vi_mode(self):
16 def vi_mode(self):
17 if (getattr(self.shell.pt_app, 'editing_mode', None) == 'VI'
17 if (getattr(self.shell.pt_app, 'editing_mode', None) == 'VI'
18 and self.shell.prompt_includes_vi_mode):
18 and self.shell.prompt_includes_vi_mode):
19 return '['+str(self.shell.pt_app.app.vi_state.input_mode)[3:6]+'] '
19 return '['+str(self.shell.pt_app.app.vi_state.input_mode)[3:6]+'] '
20 return ''
20 return ''
21
21
22
22
23 def in_prompt_tokens(self):
23 def in_prompt_tokens(self):
24 return [
24 return [
25 (Token.Prompt, self.vi_mode() ),
25 (Token.Prompt, self.vi_mode() ),
26 (Token.Prompt, 'In ['),
26 (Token.Prompt, 'In ['),
27 (Token.PromptNum, str(self.shell.execution_count)),
27 (Token.PromptNum, str(self.shell.execution_count)),
28 (Token.Prompt, ']: '),
28 (Token.Prompt, ']: '),
29 ]
29 ]
30
30
31 def _width(self):
31 def _width(self):
32 return fragment_list_width(self.in_prompt_tokens())
32 return fragment_list_width(self.in_prompt_tokens())
33
33
34 def continuation_prompt_tokens(self, width=None):
34 def continuation_prompt_tokens(self, width=None):
35 if width is None:
35 if width is None:
36 width = self._width()
36 width = self._width()
37 return [
37 return [
38 (Token.Prompt, (' ' * (width - 5)) + '...: '),
38 (Token.Prompt, (' ' * (width - 5)) + '...: '),
39 ]
39 ]
40
40
41 def rewrite_prompt_tokens(self):
41 def rewrite_prompt_tokens(self):
42 width = self._width()
42 width = self._width()
43 return [
43 return [
44 (Token.Prompt, ('-' * (width - 2)) + '> '),
44 (Token.Prompt, ('-' * (width - 2)) + '> '),
45 ]
45 ]
46
46
47 def out_prompt_tokens(self):
47 def out_prompt_tokens(self):
48 return [
48 return [
49 (Token.OutPrompt, 'Out['),
49 (Token.OutPrompt, 'Out['),
50 (Token.OutPromptNum, str(self.shell.execution_count)),
50 (Token.OutPromptNum, str(self.shell.execution_count)),
51 (Token.OutPrompt, ']: '),
51 (Token.OutPrompt, ']: '),
52 ]
52 ]
53
53
54 class ClassicPrompts(Prompts):
54 class ClassicPrompts(Prompts):
55 def in_prompt_tokens(self):
55 def in_prompt_tokens(self):
56 return [
56 return [
57 (Token.Prompt, '>>> '),
57 (Token.Prompt, '>>> '),
58 ]
58 ]
59
59
60 def continuation_prompt_tokens(self, width=None):
60 def continuation_prompt_tokens(self, width=None):
61 return [
61 return [
62 (Token.Prompt, '... ')
62 (Token.Prompt, '... ')
63 ]
63 ]
64
64
65 def rewrite_prompt_tokens(self):
65 def rewrite_prompt_tokens(self):
66 return []
66 return []
67
67
68 def out_prompt_tokens(self):
68 def out_prompt_tokens(self):
69 return []
69 return []
70
70
71 class RichPromptDisplayHook(DisplayHook):
71 class RichPromptDisplayHook(DisplayHook):
72 """Subclass of base display hook using coloured prompt"""
72 """Subclass of base display hook using coloured prompt"""
73 def write_output_prompt(self):
73 def write_output_prompt(self):
74 sys.stdout.write(self.shell.separate_out)
74 sys.stdout.write(self.shell.separate_out)
75 # If we're not displaying a prompt, it effectively ends with a newline,
75 # If we're not displaying a prompt, it effectively ends with a newline,
76 # because the output will be left-aligned.
76 # because the output will be left-aligned.
77 self.prompt_end_newline = True
77 self.prompt_end_newline = True
78
78
79 if self.do_full_cache:
79 if self.do_full_cache:
80 tokens = self.shell.prompts.out_prompt_tokens()
80 tokens = self.shell.prompts.out_prompt_tokens()
81 prompt_txt = ''.join(s for t, s in tokens)
81 prompt_txt = ''.join(s for t, s in tokens)
82 if prompt_txt and not prompt_txt.endswith('\n'):
82 if prompt_txt and not prompt_txt.endswith('\n'):
83 # Ask for a newline before multiline output
83 # Ask for a newline before multiline output
84 self.prompt_end_newline = False
84 self.prompt_end_newline = False
85
85
86 if self.shell.pt_app:
86 if self.shell.pt_app:
87 print_formatted_text(PygmentsTokens(tokens),
87 print_formatted_text(PygmentsTokens(tokens),
88 style=self.shell.pt_app.app.style, end='',
88 style=self.shell.pt_app.app.style, end='',
89 )
89 )
90 else:
90 else:
91 sys.stdout.write(prompt_txt)
91 sys.stdout.write(prompt_txt)
92
93 def write_format_data(self, format_dict, md_dict=None) -> None:
94 if self.shell.mime_renderers:
95
96 for mime, handler in self.shell.mime_renderers.items():
97 if mime in format_dict:
98 handler(format_dict[mime], None)
99 return
100
101 super().write_format_data(format_dict, md_dict)
102
General Comments 0
You need to be logged in to leave comments. Login now