##// 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 # 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