##// END OF EJS Templates
Update documentation, and prepare inine matplotlib.
Matthias Bussonnier -
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 ``subporcess.run()`` and a
60 temporary file, which is left as an exercise.
@@ -1,558 +1,558 b''
1 """IPython terminal interface using prompt_toolkit"""
1 """IPython terminal interface using prompt_toolkit"""
2
2
3 import os
3 import os
4 import sys
4 import sys
5 import warnings
5 import warnings
6 from warnings import warn
6 from warnings import warn
7
7
8 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
8 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
9 from IPython.utils import io
9 from IPython.utils import io
10 from IPython.utils.py3compat import input
10 from IPython.utils.py3compat import input
11 from IPython.utils.terminal import toggle_set_term_title, set_term_title
11 from IPython.utils.terminal import toggle_set_term_title, set_term_title
12 from IPython.utils.process import abbrev_cwd
12 from IPython.utils.process import abbrev_cwd
13 from traitlets import (
13 from traitlets import (
14 Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union,
14 Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union,
15 Any, validate
15 Any, validate
16 )
16 )
17
17
18 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
18 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
19 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
19 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
20 from prompt_toolkit.formatted_text import PygmentsTokens
20 from prompt_toolkit.formatted_text import PygmentsTokens
21 from prompt_toolkit.history import InMemoryHistory
21 from prompt_toolkit.history import InMemoryHistory
22 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
22 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
23 from prompt_toolkit.output import ColorDepth
23 from prompt_toolkit.output import ColorDepth
24 from prompt_toolkit.patch_stdout import patch_stdout
24 from prompt_toolkit.patch_stdout import patch_stdout
25 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
25 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
26 from prompt_toolkit.styles import DynamicStyle, merge_styles
26 from prompt_toolkit.styles import DynamicStyle, merge_styles
27 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
27 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
28
28
29 from pygments.styles import get_style_by_name
29 from pygments.styles import get_style_by_name
30 from pygments.style import Style
30 from pygments.style import Style
31 from pygments.token import Token
31 from pygments.token import Token
32
32
33 from .debugger import TerminalPdb, Pdb
33 from .debugger import TerminalPdb, Pdb
34 from .magics import TerminalMagics
34 from .magics import TerminalMagics
35 from .pt_inputhooks import get_inputhook_name_and_func
35 from .pt_inputhooks import get_inputhook_name_and_func
36 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
36 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
37 from .ptutils import IPythonPTCompleter, IPythonPTLexer
37 from .ptutils import IPythonPTCompleter, IPythonPTLexer
38 from .shortcuts import create_ipython_shortcuts
38 from .shortcuts import create_ipython_shortcuts
39
39
40 DISPLAY_BANNER_DEPRECATED = object()
40 DISPLAY_BANNER_DEPRECATED = object()
41
41
42
42
43 class _NoStyle(Style): pass
43 class _NoStyle(Style): pass
44
44
45
45
46
46
47 _style_overrides_light_bg = {
47 _style_overrides_light_bg = {
48 Token.Prompt: '#0000ff',
48 Token.Prompt: '#0000ff',
49 Token.PromptNum: '#0000ee bold',
49 Token.PromptNum: '#0000ee bold',
50 Token.OutPrompt: '#cc0000',
50 Token.OutPrompt: '#cc0000',
51 Token.OutPromptNum: '#bb0000 bold',
51 Token.OutPromptNum: '#bb0000 bold',
52 }
52 }
53
53
54 _style_overrides_linux = {
54 _style_overrides_linux = {
55 Token.Prompt: '#00cc00',
55 Token.Prompt: '#00cc00',
56 Token.PromptNum: '#00bb00 bold',
56 Token.PromptNum: '#00bb00 bold',
57 Token.OutPrompt: '#cc0000',
57 Token.OutPrompt: '#cc0000',
58 Token.OutPromptNum: '#bb0000 bold',
58 Token.OutPromptNum: '#bb0000 bold',
59 }
59 }
60
60
61 def get_default_editor():
61 def get_default_editor():
62 try:
62 try:
63 return os.environ['EDITOR']
63 return os.environ['EDITOR']
64 except KeyError:
64 except KeyError:
65 pass
65 pass
66 except UnicodeError:
66 except UnicodeError:
67 warn("$EDITOR environment variable is not pure ASCII. Using platform "
67 warn("$EDITOR environment variable is not pure ASCII. Using platform "
68 "default editor.")
68 "default editor.")
69
69
70 if os.name == 'posix':
70 if os.name == 'posix':
71 return 'vi' # the only one guaranteed to be there!
71 return 'vi' # the only one guaranteed to be there!
72 else:
72 else:
73 return 'notepad' # same in Windows!
73 return 'notepad' # same in Windows!
74
74
75 # conservatively check for tty
75 # conservatively check for tty
76 # overridden streams can result in things like:
76 # overridden streams can result in things like:
77 # - sys.stdin = None
77 # - sys.stdin = None
78 # - no isatty method
78 # - no isatty method
79 for _name in ('stdin', 'stdout', 'stderr'):
79 for _name in ('stdin', 'stdout', 'stderr'):
80 _stream = getattr(sys, _name)
80 _stream = getattr(sys, _name)
81 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
81 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
82 _is_tty = False
82 _is_tty = False
83 break
83 break
84 else:
84 else:
85 _is_tty = True
85 _is_tty = True
86
86
87
87
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)
91 mime_renderers = Dict().tag(config=True)
92
92
93 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 '
94 'to reserve for the completion menu'
94 'to reserve for the completion menu'
95 ).tag(config=True)
95 ).tag(config=True)
96
96
97 pt_app = None
97 pt_app = None
98 debugger_history = None
98 debugger_history = None
99
99
100 simple_prompt = Bool(_use_simple_prompt,
100 simple_prompt = Bool(_use_simple_prompt,
101 help="""Use `raw_input` for the REPL, without completion and prompt colors.
101 help="""Use `raw_input` for the REPL, without completion and prompt colors.
102
102
103 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
103 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
104 IPython own testing machinery, and emacs inferior-shell integration through elpy.
104 IPython own testing machinery, and emacs inferior-shell integration through elpy.
105
105
106 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
106 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
107 environment variable is set, or the current terminal is not a tty."""
107 environment variable is set, or the current terminal is not a tty."""
108 ).tag(config=True)
108 ).tag(config=True)
109
109
110 @property
110 @property
111 def debugger_cls(self):
111 def debugger_cls(self):
112 return Pdb if self.simple_prompt else TerminalPdb
112 return Pdb if self.simple_prompt else TerminalPdb
113
113
114 confirm_exit = Bool(True,
114 confirm_exit = Bool(True,
115 help="""
115 help="""
116 Set to confirm when you try to exit IPython with an EOF (Control-D
116 Set to confirm when you try to exit IPython with an EOF (Control-D
117 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
117 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
118 you can force a direct exit without any confirmation.""",
118 you can force a direct exit without any confirmation.""",
119 ).tag(config=True)
119 ).tag(config=True)
120
120
121 editing_mode = Unicode('emacs',
121 editing_mode = Unicode('emacs',
122 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
122 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
123 ).tag(config=True)
123 ).tag(config=True)
124
124
125 mouse_support = Bool(False,
125 mouse_support = Bool(False,
126 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
126 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
127 ).tag(config=True)
127 ).tag(config=True)
128
128
129 # We don't load the list of styles for the help string, because loading
129 # We don't load the list of styles for the help string, because loading
130 # Pygments plugins takes time and can cause unexpected errors.
130 # Pygments plugins takes time and can cause unexpected errors.
131 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
131 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
132 help="""The name or class of a Pygments style to use for syntax
132 help="""The name or class of a Pygments style to use for syntax
133 highlighting. To see available styles, run `pygmentize -L styles`."""
133 highlighting. To see available styles, run `pygmentize -L styles`."""
134 ).tag(config=True)
134 ).tag(config=True)
135
135
136 @validate('editing_mode')
136 @validate('editing_mode')
137 def _validate_editing_mode(self, proposal):
137 def _validate_editing_mode(self, proposal):
138 if proposal['value'].lower() == 'vim':
138 if proposal['value'].lower() == 'vim':
139 proposal['value']= 'vi'
139 proposal['value']= 'vi'
140 elif proposal['value'].lower() == 'default':
140 elif proposal['value'].lower() == 'default':
141 proposal['value']= 'emacs'
141 proposal['value']= 'emacs'
142
142
143 if hasattr(EditingMode, proposal['value'].upper()):
143 if hasattr(EditingMode, proposal['value'].upper()):
144 return proposal['value'].lower()
144 return proposal['value'].lower()
145
145
146 return self.editing_mode
146 return self.editing_mode
147
147
148
148
149 @observe('editing_mode')
149 @observe('editing_mode')
150 def _editing_mode(self, change):
150 def _editing_mode(self, change):
151 u_mode = change.new.upper()
151 u_mode = change.new.upper()
152 if self.pt_app:
152 if self.pt_app:
153 self.pt_app.editing_mode = u_mode
153 self.pt_app.editing_mode = u_mode
154
154
155 @observe('highlighting_style')
155 @observe('highlighting_style')
156 @observe('colors')
156 @observe('colors')
157 def _highlighting_style_changed(self, change):
157 def _highlighting_style_changed(self, change):
158 self.refresh_style()
158 self.refresh_style()
159
159
160 def refresh_style(self):
160 def refresh_style(self):
161 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
161 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
162
162
163
163
164 highlighting_style_overrides = Dict(
164 highlighting_style_overrides = Dict(
165 help="Override highlighting format for specific tokens"
165 help="Override highlighting format for specific tokens"
166 ).tag(config=True)
166 ).tag(config=True)
167
167
168 true_color = Bool(False,
168 true_color = Bool(False,
169 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
169 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
170 "If your terminal supports true color, the following command "
170 "If your terminal supports true color, the following command "
171 "should print 'TRUECOLOR' in orange: "
171 "should print 'TRUECOLOR' in orange: "
172 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
172 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
173 ).tag(config=True)
173 ).tag(config=True)
174
174
175 editor = Unicode(get_default_editor(),
175 editor = Unicode(get_default_editor(),
176 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
176 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
177 ).tag(config=True)
177 ).tag(config=True)
178
178
179 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
179 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
180
180
181 prompts = Instance(Prompts)
181 prompts = Instance(Prompts)
182
182
183 @default('prompts')
183 @default('prompts')
184 def _prompts_default(self):
184 def _prompts_default(self):
185 return self.prompts_class(self)
185 return self.prompts_class(self)
186
186
187 # @observe('prompts')
187 # @observe('prompts')
188 # def _(self, change):
188 # def _(self, change):
189 # self._update_layout()
189 # self._update_layout()
190
190
191 @default('displayhook_class')
191 @default('displayhook_class')
192 def _displayhook_class_default(self):
192 def _displayhook_class_default(self):
193 return RichPromptDisplayHook
193 return RichPromptDisplayHook
194
194
195 term_title = Bool(True,
195 term_title = Bool(True,
196 help="Automatically set the terminal title"
196 help="Automatically set the terminal title"
197 ).tag(config=True)
197 ).tag(config=True)
198
198
199 term_title_format = Unicode("IPython: {cwd}",
199 term_title_format = Unicode("IPython: {cwd}",
200 help="Customize the terminal title format. This is a python format string. " +
200 help="Customize the terminal title format. This is a python format string. " +
201 "Available substitutions are: {cwd}."
201 "Available substitutions are: {cwd}."
202 ).tag(config=True)
202 ).tag(config=True)
203
203
204 display_completions = Enum(('column', 'multicolumn','readlinelike'),
204 display_completions = Enum(('column', 'multicolumn','readlinelike'),
205 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
205 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
206 "'readlinelike'. These options are for `prompt_toolkit`, see "
206 "'readlinelike'. These options are for `prompt_toolkit`, see "
207 "`prompt_toolkit` documentation for more information."
207 "`prompt_toolkit` documentation for more information."
208 ),
208 ),
209 default_value='multicolumn').tag(config=True)
209 default_value='multicolumn').tag(config=True)
210
210
211 highlight_matching_brackets = Bool(True,
211 highlight_matching_brackets = Bool(True,
212 help="Highlight matching brackets.",
212 help="Highlight matching brackets.",
213 ).tag(config=True)
213 ).tag(config=True)
214
214
215 extra_open_editor_shortcuts = Bool(False,
215 extra_open_editor_shortcuts = Bool(False,
216 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
216 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
217 "This is in addition to the F2 binding, which is always enabled."
217 "This is in addition to the F2 binding, which is always enabled."
218 ).tag(config=True)
218 ).tag(config=True)
219
219
220 handle_return = Any(None,
220 handle_return = Any(None,
221 help="Provide an alternative handler to be called when the user presses "
221 help="Provide an alternative handler to be called when the user presses "
222 "Return. This is an advanced option intended for debugging, which "
222 "Return. This is an advanced option intended for debugging, which "
223 "may be changed or removed in later releases."
223 "may be changed or removed in later releases."
224 ).tag(config=True)
224 ).tag(config=True)
225
225
226 enable_history_search = Bool(True,
226 enable_history_search = Bool(True,
227 help="Allows to enable/disable the prompt toolkit history search"
227 help="Allows to enable/disable the prompt toolkit history search"
228 ).tag(config=True)
228 ).tag(config=True)
229
229
230 prompt_includes_vi_mode = Bool(True,
230 prompt_includes_vi_mode = Bool(True,
231 help="Display the current vi mode (when using vi editing mode)."
231 help="Display the current vi mode (when using vi editing mode)."
232 ).tag(config=True)
232 ).tag(config=True)
233
233
234 @observe('term_title')
234 @observe('term_title')
235 def init_term_title(self, change=None):
235 def init_term_title(self, change=None):
236 # Enable or disable the terminal title.
236 # Enable or disable the terminal title.
237 if self.term_title:
237 if self.term_title:
238 toggle_set_term_title(True)
238 toggle_set_term_title(True)
239 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
239 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
240 else:
240 else:
241 toggle_set_term_title(False)
241 toggle_set_term_title(False)
242
242
243 def init_display_formatter(self):
243 def init_display_formatter(self):
244 super(TerminalInteractiveShell, self).init_display_formatter()
244 super(TerminalInteractiveShell, self).init_display_formatter()
245 # terminal only supports plain text
245 # terminal only supports plain text
246 self.display_formatter.active_types = ['text/plain']
246 self.display_formatter.active_types = ['text/plain']
247 # disable `_ipython_display_`
247 # disable `_ipython_display_`
248 self.display_formatter.ipython_display_formatter.enabled = False
248 self.display_formatter.ipython_display_formatter.enabled = False
249
249
250 def init_prompt_toolkit_cli(self):
250 def init_prompt_toolkit_cli(self):
251 if self.simple_prompt:
251 if self.simple_prompt:
252 # Fall back to plain non-interactive output for tests.
252 # Fall back to plain non-interactive output for tests.
253 # This is very limited.
253 # This is very limited.
254 def prompt():
254 def prompt():
255 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
255 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
256 lines = [input(prompt_text)]
256 lines = [input(prompt_text)]
257 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
257 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
258 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
258 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
259 lines.append( input(prompt_continuation) )
259 lines.append( input(prompt_continuation) )
260 return '\n'.join(lines)
260 return '\n'.join(lines)
261 self.prompt_for_code = prompt
261 self.prompt_for_code = prompt
262 return
262 return
263
263
264 # Set up keyboard shortcuts
264 # Set up keyboard shortcuts
265 key_bindings = create_ipython_shortcuts(self)
265 key_bindings = create_ipython_shortcuts(self)
266
266
267 # Pre-populate history from IPython's history database
267 # Pre-populate history from IPython's history database
268 history = InMemoryHistory()
268 history = InMemoryHistory()
269 last_cell = u""
269 last_cell = u""
270 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
270 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
271 include_latest=True):
271 include_latest=True):
272 # Ignore blank lines and consecutive duplicates
272 # Ignore blank lines and consecutive duplicates
273 cell = cell.rstrip()
273 cell = cell.rstrip()
274 if cell and (cell != last_cell):
274 if cell and (cell != last_cell):
275 history.append_string(cell)
275 history.append_string(cell)
276 last_cell = cell
276 last_cell = cell
277
277
278 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
278 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
279 self.style = DynamicStyle(lambda: self._style)
279 self.style = DynamicStyle(lambda: self._style)
280
280
281 editing_mode = getattr(EditingMode, self.editing_mode.upper())
281 editing_mode = getattr(EditingMode, self.editing_mode.upper())
282
282
283 self.pt_app = PromptSession(
283 self.pt_app = PromptSession(
284 editing_mode=editing_mode,
284 editing_mode=editing_mode,
285 key_bindings=key_bindings,
285 key_bindings=key_bindings,
286 history=history,
286 history=history,
287 completer=IPythonPTCompleter(shell=self),
287 completer=IPythonPTCompleter(shell=self),
288 enable_history_search = self.enable_history_search,
288 enable_history_search = self.enable_history_search,
289 style=self.style,
289 style=self.style,
290 include_default_pygments_style=False,
290 include_default_pygments_style=False,
291 mouse_support=self.mouse_support,
291 mouse_support=self.mouse_support,
292 enable_open_in_editor=self.extra_open_editor_shortcuts,
292 enable_open_in_editor=self.extra_open_editor_shortcuts,
293 color_depth=self.color_depth,
293 color_depth=self.color_depth,
294 **self._extra_prompt_options())
294 **self._extra_prompt_options())
295
295
296 def _make_style_from_name_or_cls(self, name_or_cls):
296 def _make_style_from_name_or_cls(self, name_or_cls):
297 """
297 """
298 Small wrapper that make an IPython compatible style from a style name
298 Small wrapper that make an IPython compatible style from a style name
299
299
300 We need that to add style for prompt ... etc.
300 We need that to add style for prompt ... etc.
301 """
301 """
302 style_overrides = {}
302 style_overrides = {}
303 if name_or_cls == 'legacy':
303 if name_or_cls == 'legacy':
304 legacy = self.colors.lower()
304 legacy = self.colors.lower()
305 if legacy == 'linux':
305 if legacy == 'linux':
306 style_cls = get_style_by_name('monokai')
306 style_cls = get_style_by_name('monokai')
307 style_overrides = _style_overrides_linux
307 style_overrides = _style_overrides_linux
308 elif legacy == 'lightbg':
308 elif legacy == 'lightbg':
309 style_overrides = _style_overrides_light_bg
309 style_overrides = _style_overrides_light_bg
310 style_cls = get_style_by_name('pastie')
310 style_cls = get_style_by_name('pastie')
311 elif legacy == 'neutral':
311 elif legacy == 'neutral':
312 # The default theme needs to be visible on both a dark background
312 # The default theme needs to be visible on both a dark background
313 # and a light background, because we can't tell what the terminal
313 # and a light background, because we can't tell what the terminal
314 # looks like. These tweaks to the default theme help with that.
314 # looks like. These tweaks to the default theme help with that.
315 style_cls = get_style_by_name('default')
315 style_cls = get_style_by_name('default')
316 style_overrides.update({
316 style_overrides.update({
317 Token.Number: '#007700',
317 Token.Number: '#007700',
318 Token.Operator: 'noinherit',
318 Token.Operator: 'noinherit',
319 Token.String: '#BB6622',
319 Token.String: '#BB6622',
320 Token.Name.Function: '#2080D0',
320 Token.Name.Function: '#2080D0',
321 Token.Name.Class: 'bold #2080D0',
321 Token.Name.Class: 'bold #2080D0',
322 Token.Name.Namespace: 'bold #2080D0',
322 Token.Name.Namespace: 'bold #2080D0',
323 Token.Prompt: '#009900',
323 Token.Prompt: '#009900',
324 Token.PromptNum: '#ansibrightgreen bold',
324 Token.PromptNum: '#ansibrightgreen bold',
325 Token.OutPrompt: '#990000',
325 Token.OutPrompt: '#990000',
326 Token.OutPromptNum: '#ansibrightred bold',
326 Token.OutPromptNum: '#ansibrightred bold',
327 })
327 })
328
328
329 # Hack: Due to limited color support on the Windows console
329 # Hack: Due to limited color support on the Windows console
330 # the prompt colors will be wrong without this
330 # the prompt colors will be wrong without this
331 if os.name == 'nt':
331 if os.name == 'nt':
332 style_overrides.update({
332 style_overrides.update({
333 Token.Prompt: '#ansidarkgreen',
333 Token.Prompt: '#ansidarkgreen',
334 Token.PromptNum: '#ansigreen bold',
334 Token.PromptNum: '#ansigreen bold',
335 Token.OutPrompt: '#ansidarkred',
335 Token.OutPrompt: '#ansidarkred',
336 Token.OutPromptNum: '#ansired bold',
336 Token.OutPromptNum: '#ansired bold',
337 })
337 })
338 elif legacy =='nocolor':
338 elif legacy =='nocolor':
339 style_cls=_NoStyle
339 style_cls=_NoStyle
340 style_overrides = {}
340 style_overrides = {}
341 else :
341 else :
342 raise ValueError('Got unknown colors: ', legacy)
342 raise ValueError('Got unknown colors: ', legacy)
343 else :
343 else :
344 if isinstance(name_or_cls, str):
344 if isinstance(name_or_cls, str):
345 style_cls = get_style_by_name(name_or_cls)
345 style_cls = get_style_by_name(name_or_cls)
346 else:
346 else:
347 style_cls = name_or_cls
347 style_cls = name_or_cls
348 style_overrides = {
348 style_overrides = {
349 Token.Prompt: '#009900',
349 Token.Prompt: '#009900',
350 Token.PromptNum: '#ansibrightgreen bold',
350 Token.PromptNum: '#ansibrightgreen bold',
351 Token.OutPrompt: '#990000',
351 Token.OutPrompt: '#990000',
352 Token.OutPromptNum: '#ansibrightred bold',
352 Token.OutPromptNum: '#ansibrightred bold',
353 }
353 }
354 style_overrides.update(self.highlighting_style_overrides)
354 style_overrides.update(self.highlighting_style_overrides)
355 style = merge_styles([
355 style = merge_styles([
356 style_from_pygments_cls(style_cls),
356 style_from_pygments_cls(style_cls),
357 style_from_pygments_dict(style_overrides),
357 style_from_pygments_dict(style_overrides),
358 ])
358 ])
359
359
360 return style
360 return style
361
361
362 @property
362 @property
363 def pt_complete_style(self):
363 def pt_complete_style(self):
364 return {
364 return {
365 'multicolumn': CompleteStyle.MULTI_COLUMN,
365 'multicolumn': CompleteStyle.MULTI_COLUMN,
366 'column': CompleteStyle.COLUMN,
366 'column': CompleteStyle.COLUMN,
367 'readlinelike': CompleteStyle.READLINE_LIKE,
367 'readlinelike': CompleteStyle.READLINE_LIKE,
368 }[self.display_completions]
368 }[self.display_completions]
369
369
370 @property
370 @property
371 def color_depth(self):
371 def color_depth(self):
372 return (ColorDepth.TRUE_COLOR if self.true_color else None)
372 return (ColorDepth.TRUE_COLOR if self.true_color else None)
373
373
374 def _extra_prompt_options(self):
374 def _extra_prompt_options(self):
375 """
375 """
376 Return the current layout option for the current Terminal InteractiveShell
376 Return the current layout option for the current Terminal InteractiveShell
377 """
377 """
378 def get_message():
378 def get_message():
379 return PygmentsTokens(self.prompts.in_prompt_tokens())
379 return PygmentsTokens(self.prompts.in_prompt_tokens())
380
380
381 return {
381 return {
382 'complete_in_thread': False,
382 'complete_in_thread': False,
383 'lexer':IPythonPTLexer(),
383 'lexer':IPythonPTLexer(),
384 'reserve_space_for_menu':self.space_for_menu,
384 'reserve_space_for_menu':self.space_for_menu,
385 'message': get_message,
385 'message': get_message,
386 'prompt_continuation': (
386 'prompt_continuation': (
387 lambda width, lineno, is_soft_wrap:
387 lambda width, lineno, is_soft_wrap:
388 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
388 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
389 'multiline': True,
389 'multiline': True,
390 'complete_style': self.pt_complete_style,
390 'complete_style': self.pt_complete_style,
391
391
392 # Highlight matching brackets, but only when this setting is
392 # Highlight matching brackets, but only when this setting is
393 # enabled, and only when the DEFAULT_BUFFER has the focus.
393 # enabled, and only when the DEFAULT_BUFFER has the focus.
394 'input_processors': [ConditionalProcessor(
394 'input_processors': [ConditionalProcessor(
395 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
395 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
396 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
396 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
397 Condition(lambda: self.highlight_matching_brackets))],
397 Condition(lambda: self.highlight_matching_brackets))],
398 'inputhook': self.inputhook,
398 'inputhook': self.inputhook,
399 }
399 }
400
400
401 def prompt_for_code(self):
401 def prompt_for_code(self):
402 if self.rl_next_input:
402 if self.rl_next_input:
403 default = self.rl_next_input
403 default = self.rl_next_input
404 self.rl_next_input = None
404 self.rl_next_input = None
405 else:
405 else:
406 default = ''
406 default = ''
407
407
408 with patch_stdout(raw=True):
408 with patch_stdout(raw=True):
409 text = self.pt_app.prompt(
409 text = self.pt_app.prompt(
410 default=default,
410 default=default,
411 # pre_run=self.pre_prompt,# reset_current_buffer=True,
411 # pre_run=self.pre_prompt,# reset_current_buffer=True,
412 **self._extra_prompt_options())
412 **self._extra_prompt_options())
413 return text
413 return text
414
414
415 def enable_win_unicode_console(self):
415 def enable_win_unicode_console(self):
416 if sys.version_info >= (3, 6):
416 if sys.version_info >= (3, 6):
417 # Since PEP 528, Python uses the unicode APIs for the Windows
417 # Since PEP 528, Python uses the unicode APIs for the Windows
418 # console by default, so WUC shouldn't be needed.
418 # console by default, so WUC shouldn't be needed.
419 return
419 return
420
420
421 import win_unicode_console
421 import win_unicode_console
422 win_unicode_console.enable()
422 win_unicode_console.enable()
423
423
424 def init_io(self):
424 def init_io(self):
425 if sys.platform not in {'win32', 'cli'}:
425 if sys.platform not in {'win32', 'cli'}:
426 return
426 return
427
427
428 self.enable_win_unicode_console()
428 self.enable_win_unicode_console()
429
429
430 import colorama
430 import colorama
431 colorama.init()
431 colorama.init()
432
432
433 # For some reason we make these wrappers around stdout/stderr.
433 # For some reason we make these wrappers around stdout/stderr.
434 # For now, we need to reset them so all output gets coloured.
434 # For now, we need to reset them so all output gets coloured.
435 # https://github.com/ipython/ipython/issues/8669
435 # https://github.com/ipython/ipython/issues/8669
436 # io.std* are deprecated, but don't show our own deprecation warnings
436 # io.std* are deprecated, but don't show our own deprecation warnings
437 # during initialization of the deprecated API.
437 # during initialization of the deprecated API.
438 with warnings.catch_warnings():
438 with warnings.catch_warnings():
439 warnings.simplefilter('ignore', DeprecationWarning)
439 warnings.simplefilter('ignore', DeprecationWarning)
440 io.stdout = io.IOStream(sys.stdout)
440 io.stdout = io.IOStream(sys.stdout)
441 io.stderr = io.IOStream(sys.stderr)
441 io.stderr = io.IOStream(sys.stderr)
442
442
443 def init_magics(self):
443 def init_magics(self):
444 super(TerminalInteractiveShell, self).init_magics()
444 super(TerminalInteractiveShell, self).init_magics()
445 self.register_magics(TerminalMagics)
445 self.register_magics(TerminalMagics)
446
446
447 def init_alias(self):
447 def init_alias(self):
448 # The parent class defines aliases that can be safely used with any
448 # The parent class defines aliases that can be safely used with any
449 # frontend.
449 # frontend.
450 super(TerminalInteractiveShell, self).init_alias()
450 super(TerminalInteractiveShell, self).init_alias()
451
451
452 # Now define aliases that only make sense on the terminal, because they
452 # Now define aliases that only make sense on the terminal, because they
453 # need direct access to the console in a way that we can't emulate in
453 # need direct access to the console in a way that we can't emulate in
454 # GUI or web frontend
454 # GUI or web frontend
455 if os.name == 'posix':
455 if os.name == 'posix':
456 for cmd in ('clear', 'more', 'less', 'man'):
456 for cmd in ('clear', 'more', 'less', 'man'):
457 self.alias_manager.soft_define_alias(cmd, cmd)
457 self.alias_manager.soft_define_alias(cmd, cmd)
458
458
459
459
460 def __init__(self, *args, **kwargs):
460 def __init__(self, *args, **kwargs):
461 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
461 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
462 self.init_prompt_toolkit_cli()
462 self.init_prompt_toolkit_cli()
463 self.init_term_title()
463 self.init_term_title()
464 self.keep_running = True
464 self.keep_running = True
465
465
466 self.debugger_history = InMemoryHistory()
466 self.debugger_history = InMemoryHistory()
467
467
468 def ask_exit(self):
468 def ask_exit(self):
469 self.keep_running = False
469 self.keep_running = False
470
470
471 rl_next_input = None
471 rl_next_input = None
472
472
473 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
473 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
474
474
475 if display_banner is not DISPLAY_BANNER_DEPRECATED:
475 if display_banner is not DISPLAY_BANNER_DEPRECATED:
476 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
476 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
477
477
478 self.keep_running = True
478 self.keep_running = True
479 while self.keep_running:
479 while self.keep_running:
480 print(self.separate_in, end='')
480 print(self.separate_in, end='')
481
481
482 try:
482 try:
483 code = self.prompt_for_code()
483 code = self.prompt_for_code()
484 except EOFError:
484 except EOFError:
485 if (not self.confirm_exit) \
485 if (not self.confirm_exit) \
486 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
486 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
487 self.ask_exit()
487 self.ask_exit()
488
488
489 else:
489 else:
490 if code:
490 if code:
491 self.run_cell(code, store_history=True)
491 self.run_cell(code, store_history=True)
492
492
493 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
493 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
494 # An extra layer of protection in case someone mashing Ctrl-C breaks
494 # An extra layer of protection in case someone mashing Ctrl-C breaks
495 # out of our internal code.
495 # out of our internal code.
496 if display_banner is not DISPLAY_BANNER_DEPRECATED:
496 if display_banner is not DISPLAY_BANNER_DEPRECATED:
497 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
497 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
498 while True:
498 while True:
499 try:
499 try:
500 self.interact()
500 self.interact()
501 break
501 break
502 except KeyboardInterrupt as e:
502 except KeyboardInterrupt as e:
503 print("\n%s escaped interact()\n" % type(e).__name__)
503 print("\n%s escaped interact()\n" % type(e).__name__)
504 finally:
504 finally:
505 # An interrupt during the eventloop will mess up the
505 # An interrupt during the eventloop will mess up the
506 # internal state of the prompt_toolkit library.
506 # internal state of the prompt_toolkit library.
507 # Stopping the eventloop fixes this, see
507 # Stopping the eventloop fixes this, see
508 # https://github.com/ipython/ipython/pull/9867
508 # https://github.com/ipython/ipython/pull/9867
509 if hasattr(self, '_eventloop'):
509 if hasattr(self, '_eventloop'):
510 self._eventloop.stop()
510 self._eventloop.stop()
511
511
512 _inputhook = None
512 _inputhook = None
513 def inputhook(self, context):
513 def inputhook(self, context):
514 if self._inputhook is not None:
514 if self._inputhook is not None:
515 self._inputhook(context)
515 self._inputhook(context)
516
516
517 active_eventloop = None
517 active_eventloop = None
518 def enable_gui(self, gui=None):
518 def enable_gui(self, gui=None):
519 if gui:
519 if gui and (gui != 'inline') :
520 self.active_eventloop, self._inputhook =\
520 self.active_eventloop, self._inputhook =\
521 get_inputhook_name_and_func(gui)
521 get_inputhook_name_and_func(gui)
522 else:
522 else:
523 self.active_eventloop = self._inputhook = None
523 self.active_eventloop = self._inputhook = None
524
524
525 # Run !system commands directly, not through pipes, so terminal programs
525 # Run !system commands directly, not through pipes, so terminal programs
526 # work correctly.
526 # work correctly.
527 system = InteractiveShell.system_raw
527 system = InteractiveShell.system_raw
528
528
529 def auto_rewrite_input(self, cmd):
529 def auto_rewrite_input(self, cmd):
530 """Overridden from the parent class to use fancy rewriting prompt"""
530 """Overridden from the parent class to use fancy rewriting prompt"""
531 if not self.show_rewritten_input:
531 if not self.show_rewritten_input:
532 return
532 return
533
533
534 tokens = self.prompts.rewrite_prompt_tokens()
534 tokens = self.prompts.rewrite_prompt_tokens()
535 if self.pt_app:
535 if self.pt_app:
536 print_formatted_text(PygmentsTokens(tokens), end='',
536 print_formatted_text(PygmentsTokens(tokens), end='',
537 style=self.pt_app.app.style)
537 style=self.pt_app.app.style)
538 print(cmd)
538 print(cmd)
539 else:
539 else:
540 prompt = ''.join(s for t, s in tokens)
540 prompt = ''.join(s for t, s in tokens)
541 print(prompt, cmd, sep='')
541 print(prompt, cmd, sep='')
542
542
543 _prompts_before = None
543 _prompts_before = None
544 def switch_doctest_mode(self, mode):
544 def switch_doctest_mode(self, mode):
545 """Switch prompts to classic for %doctest_mode"""
545 """Switch prompts to classic for %doctest_mode"""
546 if mode:
546 if mode:
547 self._prompts_before = self.prompts
547 self._prompts_before = self.prompts
548 self.prompts = ClassicPrompts(self)
548 self.prompts = ClassicPrompts(self)
549 elif self._prompts_before:
549 elif self._prompts_before:
550 self.prompts = self._prompts_before
550 self.prompts = self._prompts_before
551 self._prompts_before = None
551 self._prompts_before = None
552 # self._update_layout()
552 # self._update_layout()
553
553
554
554
555 InteractiveShellABC.register(TerminalInteractiveShell)
555 InteractiveShellABC.register(TerminalInteractiveShell)
556
556
557 if __name__ == '__main__':
557 if __name__ == '__main__':
558 TerminalInteractiveShell.instance().interact()
558 TerminalInteractiveShell.instance().interact()
@@ -1,49 +1,49 b''
1 import importlib
1 import importlib
2 import os
2 import os
3
3
4 aliases = {
4 aliases = {
5 'qt4': 'qt',
5 'qt4': 'qt',
6 'gtk2': 'gtk',
6 'gtk2': 'gtk',
7 }
7 }
8
8
9 backends = [
9 backends = [
10 'qt', 'qt4', 'qt5',
10 'qt', 'qt4', 'qt5',
11 'gtk', 'gtk2', 'gtk3',
11 'gtk', 'gtk2', 'gtk3',
12 'tk',
12 'tk',
13 'wx',
13 'wx',
14 'pyglet', 'glut',
14 'pyglet', 'glut',
15 'osx',
15 'osx'
16 ]
16 ]
17
17
18 registered = {}
18 registered = {}
19
19
20 def register(name, inputhook):
20 def register(name, inputhook):
21 """Register the function *inputhook* as an event loop integration."""
21 """Register the function *inputhook* as an event loop integration."""
22 registered[name] = inputhook
22 registered[name] = inputhook
23
23
24 class UnknownBackend(KeyError):
24 class UnknownBackend(KeyError):
25 def __init__(self, name):
25 def __init__(self, name):
26 self.name = name
26 self.name = name
27
27
28 def __str__(self):
28 def __str__(self):
29 return ("No event loop integration for {!r}. "
29 return ("No event loop integration for {!r}. "
30 "Supported event loops are: {}").format(self.name,
30 "Supported event loops are: {}").format(self.name,
31 ', '.join(backends + sorted(registered)))
31 ', '.join(backends + sorted(registered)))
32
32
33 def get_inputhook_name_and_func(gui):
33 def get_inputhook_name_and_func(gui):
34 if gui in registered:
34 if gui in registered:
35 return gui, registered[gui]
35 return gui, registered[gui]
36
36
37 if gui not in backends:
37 if gui not in backends:
38 raise UnknownBackend(gui)
38 raise UnknownBackend(gui)
39
39
40 if gui in aliases:
40 if gui in aliases:
41 return get_inputhook_name_and_func(aliases[gui])
41 return get_inputhook_name_and_func(aliases[gui])
42
42
43 gui_mod = gui
43 gui_mod = gui
44 if gui == 'qt5':
44 if gui == 'qt5':
45 os.environ['QT_API'] = 'pyqt5'
45 os.environ['QT_API'] = 'pyqt5'
46 gui_mod = 'qt'
46 gui_mod = 'qt'
47
47
48 mod = importlib.import_module('IPython.terminal.pt_inputhooks.'+gui_mod)
48 mod = importlib.import_module('IPython.terminal.pt_inputhooks.'+gui_mod)
49 return gui, mod.inputhook
49 return gui, mod.inputhook
@@ -1,34 +1,35 b''
1 .. _config_index:
1 .. _config_index:
2
2
3 ===============================
3 ===============================
4 Configuration and customization
4 Configuration and customization
5 ===============================
5 ===============================
6
6
7 Configuring IPython
7 Configuring IPython
8 -------------------
8 -------------------
9
9
10 .. toctree::
10 .. toctree::
11 :maxdepth: 2
11 :maxdepth: 2
12
12
13 intro
13 intro
14 options/index
14 options/index
15 shortcuts/index
15 shortcuts/index
16 details
16 details
17
17
18 .. seealso::
18 .. seealso::
19
19
20 :doc:`/development/config`
20 :doc:`/development/config`
21 Technical details of the config system.
21 Technical details of the config system.
22
22
23 Extending and integrating with IPython
23 Extending and integrating with IPython
24 --------------------------------------
24 --------------------------------------
25
25
26 .. toctree::
26 .. toctree::
27 :maxdepth: 2
27 :maxdepth: 2
28
28
29 extensions/index
29 extensions/index
30 integrating
30 integrating
31 custommagics
31 custommagics
32 shell_mimerenderer
32 inputtransforms
33 inputtransforms
33 callbacks
34 callbacks
34 eventloops
35 eventloops
@@ -1,55 +1,19 b''
1 Arbitrary Mimetypes Handing in Terminal
1 Arbitrary Mimetypes Handing in Terminal
2 =======================================
2 =======================================
3
3
4 When using IPython terminal it is now possible to register function to handle
4 When using IPython terminal it is now possible to register function to handle
5 arbitrary mimetypes (``TerminalInteractiveShell.mime_renderers`` ``Dict``
5 arbitrary mimetypes. While rendering non-text based representation was possible in
6 configurable). While rendering non-text based representation was possible in
7 many jupyter frontend; it was not possible in terminal IPython, as usually
6 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
7 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
8 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
9 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
10 allow opening of external program; for example ``mplayer`` to "display" sound
12 files.
11 files.
13
12
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
13 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
14 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
15 progressively enable these features by default in the next few releases, and
52 contribution is welcomed.
16 contribution is welcomed.
53
17
54
18 We welcome any feedback on the API. See :ref:`shell_mimerenderer` for more
55
19 informations.
General Comments 0
You need to be logged in to leave comments. Login now