##// END OF EJS Templates
Merge pull request #11848 from Carreau/mime-repr-hook...
Matthias Bussonnier -
r25255:fb277ea0 merge
parent child Browse files
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
@@ -12,7 +12,7 b' backends = ['
12 12 'tk',
13 13 'wx',
14 14 'pyglet', 'glut',
15 'osx',
15 'osx'
16 16 ]
17 17
18 18 registered = {}
@@ -29,6 +29,7 b' Extending and integrating with IPython'
29 29 extensions/index
30 30 integrating
31 31 custommagics
32 shell_mimerenderer
32 33 inputtransforms
33 34 callbacks
34 35 eventloops
General Comments 0
You need to be logged in to leave comments. Login now