##// END OF EJS Templates
Provide hooks for arbitrary mimetypes handling....
Matthias Bussonnier -
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 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
@@ -863,7 +863,7 b' class InteractiveShell(SingletonConfigurable):'
863 863 self.configurables.append(self.display_formatter)
864 864
865 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 867 self.configurables.append(self.display_pub)
868 868
869 869 def init_data_pub(self):
@@ -88,6 +88,8 b' else:'
88 88 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
89 89
90 90 class TerminalInteractiveShell(InteractiveShell):
91 mime_renderers = Dict().tag(config=True)
92
91 93 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
92 94 'to reserve for the completion menu'
93 95 ).tag(config=True)
@@ -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