Show More
@@ -0,0 +1,60 b'' | |||
|
1 | ||
|
2 | .. _shell_mimerenderer: | |
|
3 | ||
|
4 | ||
|
5 | Mime Renderer Extensions | |
|
6 | ======================== | |
|
7 | ||
|
8 | Like it's cousins, Jupyter Notebooks and JupyterLab, Terminal IPython can be | |
|
9 | thought to render a number of mimetypes in the shell. This can be used to either | |
|
10 | display inline images if your terminal emulator supports it; or open some | |
|
11 | display results with external file viewers. | |
|
12 | ||
|
13 | Registering new mimetype handlers can so far only be done my extensions and | |
|
14 | requires 4 steps: | |
|
15 | ||
|
16 | - Define a callable that takes 2 parameters:``data`` and ``metadata``; return | |
|
17 | value of the callable is so far ignored. This callable is responsible for | |
|
18 | "displaying" the given mimetype. Which can be sending the right escape | |
|
19 | sequences and bytes to the current terminal; or open an external program. - | |
|
20 | - Appending the right mimetype to ``ipython.display_formatter.active_types`` | |
|
21 | for IPython to know it should not ignore those mimetypes. | |
|
22 | - Enabling the given mimetype: ``ipython.display_formatter.formatters[mime].enabled = True`` | |
|
23 | - Registering above callable with mimetype handler: | |
|
24 | ``ipython.mime_renderers[mime] = handler`` | |
|
25 | ||
|
26 | ||
|
27 | Here is a complete IPython extension to display images inline and convert math | |
|
28 | to png, before displaying it inline for iterm2 on macOS :: | |
|
29 | ||
|
30 | ||
|
31 | from base64 import encodebytes | |
|
32 | from IPython.lib.latextools import latex_to_png | |
|
33 | ||
|
34 | ||
|
35 | def mathcat(data, meta): | |
|
36 | png = latex_to_png(f'$${data}$$'.replace('\displaystyle', '').replace('$$$', '$$')) | |
|
37 | imcat(png, meta) | |
|
38 | ||
|
39 | IMAGE_CODE = '\033]1337;File=name=name;inline=true;:{}\a' | |
|
40 | ||
|
41 | def imcat(image_data, metadata): | |
|
42 | try: | |
|
43 | print(IMAGE_CODE.format(encodebytes(image_data).decode())) | |
|
44 | # bug workaround | |
|
45 | except: | |
|
46 | print(IMAGE_CODE.format(image_data)) | |
|
47 | ||
|
48 | def register_mimerenderer(ipython, mime, handler): | |
|
49 | ipython.display_formatter.active_types.append(mime) | |
|
50 | ipython.display_formatter.formatters[mime].enabled = True | |
|
51 | ipython.mime_renderers[mime] = handler | |
|
52 | ||
|
53 | def load_ipython_extension(ipython): | |
|
54 | register_mimerenderer(ipython, 'image/png', imcat) | |
|
55 | register_mimerenderer(ipython, 'image/jpeg', imcat) | |
|
56 | register_mimerenderer(ipython, 'text/latex', mathcat) | |
|
57 | ||
|
58 | This example only work for iterm2 on macOS and skip error handling for brevity. | |
|
59 | One could also invoke an external viewer with ``subprocess.run()`` and a | |
|
60 | temporary file, which is left as an exercise. |
@@ -0,0 +1,22 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. While rendering non-text based representation was possible in | |
|
6 | many jupyter frontend; it was not possible in terminal IPython, as usually | |
|
7 | terminal are limited to displaying text. As many terminal these days provide | |
|
8 | escape sequences to display non-text; bringing this loved feature to IPython CLI | |
|
9 | made a lot of sens. This functionality will not only allow inline images; but | |
|
10 | allow opening of external program; for example ``mplayer`` to "display" sound | |
|
11 | files. | |
|
12 | ||
|
13 | So far only the hooks necessary for this are in place, but no default mime | |
|
14 | renderers added; so inline images will only be available via extensions. We will | |
|
15 | progressively enable these features by default in the next few releases, and | |
|
16 | contribution is welcomed. | |
|
17 | ||
|
18 | We welcome any feedback on the API. See :ref:`shell_mimerenderer` for more | |
|
19 | informations. | |
|
20 | ||
|
21 | This is originally based on work form in :ghpull:`10610` from stephanh42 | |
|
22 | started over two years ago, and still a lot need to be done. |
@@ -153,7 +153,7 b' class DisplayHook(Configurable):' | |||
|
153 | 153 | # This can be set to True by the write_output_prompt method in a subclass |
|
154 | 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 | 157 | """Write the format data dict to the frontend. |
|
158 | 158 | |
|
159 | 159 | This default version of this method simply writes the plain text |
@@ -19,7 +19,7 b' spec.' | |||
|
19 | 19 | import sys |
|
20 | 20 | |
|
21 | 21 | from traitlets.config.configurable import Configurable |
|
22 | from traitlets import List | |
|
22 | from traitlets import List, Dict | |
|
23 | 23 | |
|
24 | 24 | # This used to be defined here - it is imported for backwards compatibility |
|
25 | 25 | from .display import publish_display_data |
@@ -28,6 +28,7 b' from .display import publish_display_data' | |||
|
28 | 28 | # Main payload class |
|
29 | 29 | #----------------------------------------------------------------------------- |
|
30 | 30 | |
|
31 | ||
|
31 | 32 | class DisplayPublisher(Configurable): |
|
32 | 33 | """A traited class that publishes display data to frontends. |
|
33 | 34 | |
@@ -35,6 +36,10 b' class DisplayPublisher(Configurable):' | |||
|
35 | 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 | 43 | def _validate_data(self, data, metadata=None): |
|
39 | 44 | """Validate the display data. |
|
40 | 45 | |
@@ -53,7 +58,7 b' class DisplayPublisher(Configurable):' | |||
|
53 | 58 | raise TypeError('metadata must be a dict, got: %r' % data) |
|
54 | 59 | |
|
55 | 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 | 62 | """Publish data and metadata to all frontends. |
|
58 | 63 | |
|
59 | 64 | See the ``display_data`` message in the messaging documentation for |
@@ -98,7 +103,15 b' class DisplayPublisher(Configurable):' | |||
|
98 | 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 | 115 | if 'text/plain' in data: |
|
103 | 116 | print(data['text/plain']) |
|
104 | 117 |
@@ -857,7 +857,7 b' class InteractiveShell(SingletonConfigurable):' | |||
|
857 | 857 | self.configurables.append(self.display_formatter) |
|
858 | 858 | |
|
859 | 859 | def init_display_pub(self): |
|
860 | self.display_pub = self.display_pub_class(parent=self) | |
|
860 | self.display_pub = self.display_pub_class(parent=self, shell=self) | |
|
861 | 861 | self.configurables.append(self.display_pub) |
|
862 | 862 | |
|
863 | 863 | def init_data_pub(self): |
@@ -96,6 +96,8 b' def black_reformat_handler(text_before_cursor):' | |||
|
96 | 96 | |
|
97 | 97 | |
|
98 | 98 | class TerminalInteractiveShell(InteractiveShell): |
|
99 | mime_renderers = Dict().tag(config=True) | |
|
100 | ||
|
99 | 101 | space_for_menu = Integer(6, help='Number of line at the bottom of the screen ' |
|
100 | 102 | 'to reserve for the completion menu' |
|
101 | 103 | ).tag(config=True) |
@@ -551,7 +553,7 b' class TerminalInteractiveShell(InteractiveShell):' | |||
|
551 | 553 | |
|
552 | 554 | active_eventloop = None |
|
553 | 555 | def enable_gui(self, gui=None): |
|
554 | if gui: | |
|
556 | if gui and (gui != 'inline') : | |
|
555 | 557 | self.active_eventloop, self._inputhook =\ |
|
556 | 558 | get_inputhook_name_and_func(gui) |
|
557 | 559 | else: |
@@ -89,3 +89,14 b' class RichPromptDisplayHook(DisplayHook):' | |||
|
89 | 89 | ) |
|
90 | 90 | else: |
|
91 | 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