Show More
@@ -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 |
@@ -153,7 +153,7 b' class DisplayHook(Configurable):' | |||||
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 |
@@ -19,7 +19,7 b' spec.' | |||||
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 | |
@@ -28,6 +28,7 b' from .display import publish_display_data' | |||||
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 | |||
@@ -35,6 +36,10 b' class DisplayPublisher(Configurable):' | |||||
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 | |||
@@ -53,7 +58,7 b' class DisplayPublisher(Configurable):' | |||||
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 | |
@@ -98,7 +103,15 b' class DisplayPublisher(Configurable):' | |||||
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 |
@@ -863,7 +863,7 b' class InteractiveShell(SingletonConfigurable):' | |||||
863 | self.configurables.append(self.display_formatter) |
|
863 | self.configurables.append(self.display_formatter) | |
864 |
|
864 | |||
865 | def init_display_pub(self): |
|
865 | def init_display_pub(self): | |
866 | self.display_pub = self.display_pub_class(parent=self) |
|
866 | self.display_pub = self.display_pub_class(parent=self, shell=self) | |
867 | self.configurables.append(self.display_pub) |
|
867 | self.configurables.append(self.display_pub) | |
868 |
|
868 | |||
869 | def init_data_pub(self): |
|
869 | def init_data_pub(self): |
@@ -88,6 +88,8 b' else:' | |||||
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) |
@@ -89,3 +89,14 b' class RichPromptDisplayHook(DisplayHook):' | |||||
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