##// END OF EJS Templates
Provide hooks for arbitrary mimetypes handling....
Matthias Bussonnier -
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -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
@@ -1,325 +1,325 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Displayhook for IPython.
3 3
4 4 This defines a callable class that IPython uses for `sys.displayhook`.
5 5 """
6 6
7 7 # Copyright (c) IPython Development Team.
8 8 # Distributed under the terms of the Modified BSD License.
9 9
10 10 import builtins as builtin_mod
11 11 import sys
12 12 import io as _io
13 13 import tokenize
14 14
15 15 from traitlets.config.configurable import Configurable
16 16 from traitlets import Instance, Float
17 17 from warnings import warn
18 18
19 19 # TODO: Move the various attributes (cache_size, [others now moved]). Some
20 20 # of these are also attributes of InteractiveShell. They should be on ONE object
21 21 # only and the other objects should ask that one object for their values.
22 22
23 23 class DisplayHook(Configurable):
24 24 """The custom IPython displayhook to replace sys.displayhook.
25 25
26 26 This class does many things, but the basic idea is that it is a callable
27 27 that gets called anytime user code returns a value.
28 28 """
29 29
30 30 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
31 31 allow_none=True)
32 32 exec_result = Instance('IPython.core.interactiveshell.ExecutionResult',
33 33 allow_none=True)
34 34 cull_fraction = Float(0.2)
35 35
36 36 def __init__(self, shell=None, cache_size=1000, **kwargs):
37 37 super(DisplayHook, self).__init__(shell=shell, **kwargs)
38 38 cache_size_min = 3
39 39 if cache_size <= 0:
40 40 self.do_full_cache = 0
41 41 cache_size = 0
42 42 elif cache_size < cache_size_min:
43 43 self.do_full_cache = 0
44 44 cache_size = 0
45 45 warn('caching was disabled (min value for cache size is %s).' %
46 46 cache_size_min,stacklevel=3)
47 47 else:
48 48 self.do_full_cache = 1
49 49
50 50 self.cache_size = cache_size
51 51
52 52 # we need a reference to the user-level namespace
53 53 self.shell = shell
54 54
55 55 self._,self.__,self.___ = '','',''
56 56
57 57 # these are deliberately global:
58 58 to_user_ns = {'_':self._,'__':self.__,'___':self.___}
59 59 self.shell.user_ns.update(to_user_ns)
60 60
61 61 @property
62 62 def prompt_count(self):
63 63 return self.shell.execution_count
64 64
65 65 #-------------------------------------------------------------------------
66 66 # Methods used in __call__. Override these methods to modify the behavior
67 67 # of the displayhook.
68 68 #-------------------------------------------------------------------------
69 69
70 70 def check_for_underscore(self):
71 71 """Check if the user has set the '_' variable by hand."""
72 72 # If something injected a '_' variable in __builtin__, delete
73 73 # ipython's automatic one so we don't clobber that. gettext() in
74 74 # particular uses _, so we need to stay away from it.
75 75 if '_' in builtin_mod.__dict__:
76 76 try:
77 77 user_value = self.shell.user_ns['_']
78 78 if user_value is not self._:
79 79 return
80 80 del self.shell.user_ns['_']
81 81 except KeyError:
82 82 pass
83 83
84 84 def quiet(self):
85 85 """Should we silence the display hook because of ';'?"""
86 86 # do not print output if input ends in ';'
87 87
88 88 try:
89 89 cell = self.shell.history_manager.input_hist_parsed[-1]
90 90 except IndexError:
91 91 # some uses of ipshellembed may fail here
92 92 return False
93 93
94 94 sio = _io.StringIO(cell)
95 95 tokens = list(tokenize.generate_tokens(sio.readline))
96 96
97 97 for token in reversed(tokens):
98 98 if token[0] in (tokenize.ENDMARKER, tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT):
99 99 continue
100 100 if (token[0] == tokenize.OP) and (token[1] == ';'):
101 101 return True
102 102 else:
103 103 return False
104 104
105 105 def start_displayhook(self):
106 106 """Start the displayhook, initializing resources."""
107 107 pass
108 108
109 109 def write_output_prompt(self):
110 110 """Write the output prompt.
111 111
112 112 The default implementation simply writes the prompt to
113 113 ``sys.stdout``.
114 114 """
115 115 # Use write, not print which adds an extra space.
116 116 sys.stdout.write(self.shell.separate_out)
117 117 outprompt = 'Out[{}]: '.format(self.shell.execution_count)
118 118 if self.do_full_cache:
119 119 sys.stdout.write(outprompt)
120 120
121 121 def compute_format_data(self, result):
122 122 """Compute format data of the object to be displayed.
123 123
124 124 The format data is a generalization of the :func:`repr` of an object.
125 125 In the default implementation the format data is a :class:`dict` of
126 126 key value pair where the keys are valid MIME types and the values
127 127 are JSON'able data structure containing the raw data for that MIME
128 128 type. It is up to frontends to determine pick a MIME to to use and
129 129 display that data in an appropriate manner.
130 130
131 131 This method only computes the format data for the object and should
132 132 NOT actually print or write that to a stream.
133 133
134 134 Parameters
135 135 ----------
136 136 result : object
137 137 The Python object passed to the display hook, whose format will be
138 138 computed.
139 139
140 140 Returns
141 141 -------
142 142 (format_dict, md_dict) : dict
143 143 format_dict is a :class:`dict` whose keys are valid MIME types and values are
144 144 JSON'able raw data for that MIME type. It is recommended that
145 145 all return values of this should always include the "text/plain"
146 146 MIME type representation of the object.
147 147 md_dict is a :class:`dict` with the same MIME type keys
148 148 of metadata associated with each output.
149 149
150 150 """
151 151 return self.shell.display_formatter.format(result)
152 152
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
160 160 representation of the object to ``sys.stdout``. Subclasses should
161 161 override this method to send the entire `format_dict` to the
162 162 frontends.
163 163
164 164 Parameters
165 165 ----------
166 166 format_dict : dict
167 167 The format dict for the object passed to `sys.displayhook`.
168 168 md_dict : dict (optional)
169 169 The metadata dict to be associated with the display data.
170 170 """
171 171 if 'text/plain' not in format_dict:
172 172 # nothing to do
173 173 return
174 174 # We want to print because we want to always make sure we have a
175 175 # newline, even if all the prompt separators are ''. This is the
176 176 # standard IPython behavior.
177 177 result_repr = format_dict['text/plain']
178 178 if '\n' in result_repr:
179 179 # So that multi-line strings line up with the left column of
180 180 # the screen, instead of having the output prompt mess up
181 181 # their first line.
182 182 # We use the prompt template instead of the expanded prompt
183 183 # because the expansion may add ANSI escapes that will interfere
184 184 # with our ability to determine whether or not we should add
185 185 # a newline.
186 186 if not self.prompt_end_newline:
187 187 # But avoid extraneous empty lines.
188 188 result_repr = '\n' + result_repr
189 189
190 190 try:
191 191 print(result_repr)
192 192 except UnicodeEncodeError:
193 193 # If a character is not supported by the terminal encoding replace
194 194 # it with its \u or \x representation
195 195 print(result_repr.encode(sys.stdout.encoding,'backslashreplace').decode(sys.stdout.encoding))
196 196
197 197 def update_user_ns(self, result):
198 198 """Update user_ns with various things like _, __, _1, etc."""
199 199
200 200 # Avoid recursive reference when displaying _oh/Out
201 201 if result is not self.shell.user_ns['_oh']:
202 202 if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache:
203 203 self.cull_cache()
204 204
205 205 # Don't overwrite '_' and friends if '_' is in __builtin__
206 206 # (otherwise we cause buggy behavior for things like gettext). and
207 207 # do not overwrite _, __ or ___ if one of these has been assigned
208 208 # by the user.
209 209 update_unders = True
210 210 for unders in ['_'*i for i in range(1,4)]:
211 211 if not unders in self.shell.user_ns:
212 212 continue
213 213 if getattr(self, unders) is not self.shell.user_ns.get(unders):
214 214 update_unders = False
215 215
216 216 self.___ = self.__
217 217 self.__ = self._
218 218 self._ = result
219 219
220 220 if ('_' not in builtin_mod.__dict__) and (update_unders):
221 221 self.shell.push({'_':self._,
222 222 '__':self.__,
223 223 '___':self.___}, interactive=False)
224 224
225 225 # hackish access to top-level namespace to create _1,_2... dynamically
226 226 to_main = {}
227 227 if self.do_full_cache:
228 228 new_result = '_%s' % self.prompt_count
229 229 to_main[new_result] = result
230 230 self.shell.push(to_main, interactive=False)
231 231 self.shell.user_ns['_oh'][self.prompt_count] = result
232 232
233 233 def fill_exec_result(self, result):
234 234 if self.exec_result is not None:
235 235 self.exec_result.result = result
236 236
237 237 def log_output(self, format_dict):
238 238 """Log the output."""
239 239 if 'text/plain' not in format_dict:
240 240 # nothing to do
241 241 return
242 242 if self.shell.logger.log_output:
243 243 self.shell.logger.log_write(format_dict['text/plain'], 'output')
244 244 self.shell.history_manager.output_hist_reprs[self.prompt_count] = \
245 245 format_dict['text/plain']
246 246
247 247 def finish_displayhook(self):
248 248 """Finish up all displayhook activities."""
249 249 sys.stdout.write(self.shell.separate_out2)
250 250 sys.stdout.flush()
251 251
252 252 def __call__(self, result=None):
253 253 """Printing with history cache management.
254 254
255 255 This is invoked every time the interpreter needs to print, and is
256 256 activated by setting the variable sys.displayhook to it.
257 257 """
258 258 self.check_for_underscore()
259 259 if result is not None and not self.quiet():
260 260 self.start_displayhook()
261 261 self.write_output_prompt()
262 262 format_dict, md_dict = self.compute_format_data(result)
263 263 self.update_user_ns(result)
264 264 self.fill_exec_result(result)
265 265 if format_dict:
266 266 self.write_format_data(format_dict, md_dict)
267 267 self.log_output(format_dict)
268 268 self.finish_displayhook()
269 269
270 270 def cull_cache(self):
271 271 """Output cache is full, cull the oldest entries"""
272 272 oh = self.shell.user_ns.get('_oh', {})
273 273 sz = len(oh)
274 274 cull_count = max(int(sz * self.cull_fraction), 2)
275 275 warn('Output cache limit (currently {sz} entries) hit.\n'
276 276 'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count))
277 277
278 278 for i, n in enumerate(sorted(oh)):
279 279 if i >= cull_count:
280 280 break
281 281 self.shell.user_ns.pop('_%i' % n, None)
282 282 oh.pop(n, None)
283 283
284 284
285 285 def flush(self):
286 286 if not self.do_full_cache:
287 287 raise ValueError("You shouldn't have reached the cache flush "
288 288 "if full caching is not enabled!")
289 289 # delete auto-generated vars from global namespace
290 290
291 291 for n in range(1,self.prompt_count + 1):
292 292 key = '_'+repr(n)
293 293 try:
294 294 del self.shell.user_ns[key]
295 295 except: pass
296 296 # In some embedded circumstances, the user_ns doesn't have the
297 297 # '_oh' key set up.
298 298 oh = self.shell.user_ns.get('_oh', None)
299 299 if oh is not None:
300 300 oh.clear()
301 301
302 302 # Release our own references to objects:
303 303 self._, self.__, self.___ = '', '', ''
304 304
305 305 if '_' not in builtin_mod.__dict__:
306 306 self.shell.user_ns.update({'_':self._,'__':self.__,'___':self.___})
307 307 import gc
308 308 # TODO: Is this really needed?
309 309 # IronPython blocks here forever
310 310 if sys.platform != "cli":
311 311 gc.collect()
312 312
313 313
314 314 class CapturingDisplayHook(object):
315 315 def __init__(self, shell, outputs=None):
316 316 self.shell = shell
317 317 if outputs is None:
318 318 outputs = []
319 319 self.outputs = outputs
320 320
321 321 def __call__(self, result=None):
322 322 if result is None:
323 323 return
324 324 format_dict, md_dict = self.shell.display_formatter.format(result)
325 325 self.outputs.append({ 'data': format_dict, 'metadata': md_dict })
@@ -1,125 +1,138 b''
1 1 """An interface for publishing rich data to frontends.
2 2
3 3 There are two components of the display system:
4 4
5 5 * Display formatters, which take a Python object and compute the
6 6 representation of the object in various formats (text, HTML, SVG, etc.).
7 7 * The display publisher that is used to send the representation data to the
8 8 various frontends.
9 9
10 10 This module defines the logic display publishing. The display publisher uses
11 11 the ``display_data`` message type that is defined in the IPython messaging
12 12 spec.
13 13 """
14 14
15 15 # Copyright (c) IPython Development Team.
16 16 # Distributed under the terms of the Modified BSD License.
17 17
18 18
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
26 26
27 27 #-----------------------------------------------------------------------------
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
34 35 Instances of this class are created by the main IPython object and should
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
41 46 Parameters
42 47 ----------
43 48 data : dict
44 49 The formata data dictionary.
45 50 metadata : dict
46 51 Any metadata for the data.
47 52 """
48 53
49 54 if not isinstance(data, dict):
50 55 raise TypeError('data must be a dict, got: %r' % data)
51 56 if metadata is not None:
52 57 if not isinstance(metadata, dict):
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
60 65 more details about this message type.
61 66
62 67 The following MIME types are currently implemented:
63 68
64 69 * text/plain
65 70 * text/html
66 71 * text/markdown
67 72 * text/latex
68 73 * application/json
69 74 * application/javascript
70 75 * image/png
71 76 * image/jpeg
72 77 * image/svg+xml
73 78
74 79 Parameters
75 80 ----------
76 81 data : dict
77 82 A dictionary having keys that are valid MIME types (like
78 83 'text/plain' or 'image/svg+xml') and values that are the data for
79 84 that MIME type. The data itself must be a JSON'able data
80 85 structure. Minimally all data should have the 'text/plain' data,
81 86 which can be displayed by all frontends. If more than the plain
82 87 text is given, it is up to the frontend to decide which
83 88 representation to use.
84 89 metadata : dict
85 90 A dictionary for metadata related to the data. This can contain
86 91 arbitrary key, value pairs that frontends can use to interpret
87 92 the data. Metadata specific to each mime-type can be specified
88 93 in the metadata dict with the same mime-type keys as
89 94 the data itself.
90 95 source : str, deprecated
91 96 Unused.
92 97 transient: dict, keyword-only
93 98 A dictionary for transient data.
94 99 Data in this dictionary should not be persisted as part of saving this output.
95 100 Examples include 'display_id'.
96 101 update: bool, keyword-only, default: False
97 102 If True, only update existing outputs with the same display_id,
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
105 118 def clear_output(self, wait=False):
106 119 """Clear the output of the cell receiving output."""
107 120 print('\033[2K\r', end='')
108 121 sys.stdout.flush()
109 122 print('\033[2K\r', end='')
110 123 sys.stderr.flush()
111 124
112 125
113 126 class CapturingDisplayPublisher(DisplayPublisher):
114 127 """A DisplayPublisher that stores"""
115 128 outputs = List()
116 129
117 130 def publish(self, data, metadata=None, source=None, *, transient=None, update=False):
118 131 self.outputs.append({'data':data, 'metadata':metadata,
119 132 'transient':transient, 'update':update})
120 133
121 134 def clear_output(self, wait=False):
122 135 super(CapturingDisplayPublisher, self).clear_output(wait)
123 136
124 137 # empty the list, *do not* reassign a new list
125 138 self.outputs.clear()
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,556 +1,558 b''
1 1 """IPython terminal interface using prompt_toolkit"""
2 2
3 3 import os
4 4 import sys
5 5 import warnings
6 6 from warnings import warn
7 7
8 8 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
9 9 from IPython.utils import io
10 10 from IPython.utils.py3compat import input
11 11 from IPython.utils.terminal import toggle_set_term_title, set_term_title
12 12 from IPython.utils.process import abbrev_cwd
13 13 from traitlets import (
14 14 Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union,
15 15 Any, validate
16 16 )
17 17
18 18 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
19 19 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
20 20 from prompt_toolkit.formatted_text import PygmentsTokens
21 21 from prompt_toolkit.history import InMemoryHistory
22 22 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
23 23 from prompt_toolkit.output import ColorDepth
24 24 from prompt_toolkit.patch_stdout import patch_stdout
25 25 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
26 26 from prompt_toolkit.styles import DynamicStyle, merge_styles
27 27 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
28 28
29 29 from pygments.styles import get_style_by_name
30 30 from pygments.style import Style
31 31 from pygments.token import Token
32 32
33 33 from .debugger import TerminalPdb, Pdb
34 34 from .magics import TerminalMagics
35 35 from .pt_inputhooks import get_inputhook_name_and_func
36 36 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
37 37 from .ptutils import IPythonPTCompleter, IPythonPTLexer
38 38 from .shortcuts import create_ipython_shortcuts
39 39
40 40 DISPLAY_BANNER_DEPRECATED = object()
41 41
42 42
43 43 class _NoStyle(Style): pass
44 44
45 45
46 46
47 47 _style_overrides_light_bg = {
48 48 Token.Prompt: '#0000ff',
49 49 Token.PromptNum: '#0000ee bold',
50 50 Token.OutPrompt: '#cc0000',
51 51 Token.OutPromptNum: '#bb0000 bold',
52 52 }
53 53
54 54 _style_overrides_linux = {
55 55 Token.Prompt: '#00cc00',
56 56 Token.PromptNum: '#00bb00 bold',
57 57 Token.OutPrompt: '#cc0000',
58 58 Token.OutPromptNum: '#bb0000 bold',
59 59 }
60 60
61 61 def get_default_editor():
62 62 try:
63 63 return os.environ['EDITOR']
64 64 except KeyError:
65 65 pass
66 66 except UnicodeError:
67 67 warn("$EDITOR environment variable is not pure ASCII. Using platform "
68 68 "default editor.")
69 69
70 70 if os.name == 'posix':
71 71 return 'vi' # the only one guaranteed to be there!
72 72 else:
73 73 return 'notepad' # same in Windows!
74 74
75 75 # conservatively check for tty
76 76 # overridden streams can result in things like:
77 77 # - sys.stdin = None
78 78 # - no isatty method
79 79 for _name in ('stdin', 'stdout', 'stderr'):
80 80 _stream = getattr(sys, _name)
81 81 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
82 82 _is_tty = False
83 83 break
84 84 else:
85 85 _is_tty = True
86 86
87 87
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)
94 96
95 97 pt_app = None
96 98 debugger_history = None
97 99
98 100 simple_prompt = Bool(_use_simple_prompt,
99 101 help="""Use `raw_input` for the REPL, without completion and prompt colors.
100 102
101 103 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
102 104 IPython own testing machinery, and emacs inferior-shell integration through elpy.
103 105
104 106 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
105 107 environment variable is set, or the current terminal is not a tty."""
106 108 ).tag(config=True)
107 109
108 110 @property
109 111 def debugger_cls(self):
110 112 return Pdb if self.simple_prompt else TerminalPdb
111 113
112 114 confirm_exit = Bool(True,
113 115 help="""
114 116 Set to confirm when you try to exit IPython with an EOF (Control-D
115 117 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
116 118 you can force a direct exit without any confirmation.""",
117 119 ).tag(config=True)
118 120
119 121 editing_mode = Unicode('emacs',
120 122 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
121 123 ).tag(config=True)
122 124
123 125 mouse_support = Bool(False,
124 126 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
125 127 ).tag(config=True)
126 128
127 129 # We don't load the list of styles for the help string, because loading
128 130 # Pygments plugins takes time and can cause unexpected errors.
129 131 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
130 132 help="""The name or class of a Pygments style to use for syntax
131 133 highlighting. To see available styles, run `pygmentize -L styles`."""
132 134 ).tag(config=True)
133 135
134 136 @validate('editing_mode')
135 137 def _validate_editing_mode(self, proposal):
136 138 if proposal['value'].lower() == 'vim':
137 139 proposal['value']= 'vi'
138 140 elif proposal['value'].lower() == 'default':
139 141 proposal['value']= 'emacs'
140 142
141 143 if hasattr(EditingMode, proposal['value'].upper()):
142 144 return proposal['value'].lower()
143 145
144 146 return self.editing_mode
145 147
146 148
147 149 @observe('editing_mode')
148 150 def _editing_mode(self, change):
149 151 u_mode = change.new.upper()
150 152 if self.pt_app:
151 153 self.pt_app.editing_mode = u_mode
152 154
153 155 @observe('highlighting_style')
154 156 @observe('colors')
155 157 def _highlighting_style_changed(self, change):
156 158 self.refresh_style()
157 159
158 160 def refresh_style(self):
159 161 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
160 162
161 163
162 164 highlighting_style_overrides = Dict(
163 165 help="Override highlighting format for specific tokens"
164 166 ).tag(config=True)
165 167
166 168 true_color = Bool(False,
167 169 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
168 170 "If your terminal supports true color, the following command "
169 171 "should print 'TRUECOLOR' in orange: "
170 172 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
171 173 ).tag(config=True)
172 174
173 175 editor = Unicode(get_default_editor(),
174 176 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
175 177 ).tag(config=True)
176 178
177 179 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
178 180
179 181 prompts = Instance(Prompts)
180 182
181 183 @default('prompts')
182 184 def _prompts_default(self):
183 185 return self.prompts_class(self)
184 186
185 187 # @observe('prompts')
186 188 # def _(self, change):
187 189 # self._update_layout()
188 190
189 191 @default('displayhook_class')
190 192 def _displayhook_class_default(self):
191 193 return RichPromptDisplayHook
192 194
193 195 term_title = Bool(True,
194 196 help="Automatically set the terminal title"
195 197 ).tag(config=True)
196 198
197 199 term_title_format = Unicode("IPython: {cwd}",
198 200 help="Customize the terminal title format. This is a python format string. " +
199 201 "Available substitutions are: {cwd}."
200 202 ).tag(config=True)
201 203
202 204 display_completions = Enum(('column', 'multicolumn','readlinelike'),
203 205 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
204 206 "'readlinelike'. These options are for `prompt_toolkit`, see "
205 207 "`prompt_toolkit` documentation for more information."
206 208 ),
207 209 default_value='multicolumn').tag(config=True)
208 210
209 211 highlight_matching_brackets = Bool(True,
210 212 help="Highlight matching brackets.",
211 213 ).tag(config=True)
212 214
213 215 extra_open_editor_shortcuts = Bool(False,
214 216 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
215 217 "This is in addition to the F2 binding, which is always enabled."
216 218 ).tag(config=True)
217 219
218 220 handle_return = Any(None,
219 221 help="Provide an alternative handler to be called when the user presses "
220 222 "Return. This is an advanced option intended for debugging, which "
221 223 "may be changed or removed in later releases."
222 224 ).tag(config=True)
223 225
224 226 enable_history_search = Bool(True,
225 227 help="Allows to enable/disable the prompt toolkit history search"
226 228 ).tag(config=True)
227 229
228 230 prompt_includes_vi_mode = Bool(True,
229 231 help="Display the current vi mode (when using vi editing mode)."
230 232 ).tag(config=True)
231 233
232 234 @observe('term_title')
233 235 def init_term_title(self, change=None):
234 236 # Enable or disable the terminal title.
235 237 if self.term_title:
236 238 toggle_set_term_title(True)
237 239 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
238 240 else:
239 241 toggle_set_term_title(False)
240 242
241 243 def init_display_formatter(self):
242 244 super(TerminalInteractiveShell, self).init_display_formatter()
243 245 # terminal only supports plain text
244 246 self.display_formatter.active_types = ['text/plain']
245 247 # disable `_ipython_display_`
246 248 self.display_formatter.ipython_display_formatter.enabled = False
247 249
248 250 def init_prompt_toolkit_cli(self):
249 251 if self.simple_prompt:
250 252 # Fall back to plain non-interactive output for tests.
251 253 # This is very limited.
252 254 def prompt():
253 255 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
254 256 lines = [input(prompt_text)]
255 257 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
256 258 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
257 259 lines.append( input(prompt_continuation) )
258 260 return '\n'.join(lines)
259 261 self.prompt_for_code = prompt
260 262 return
261 263
262 264 # Set up keyboard shortcuts
263 265 key_bindings = create_ipython_shortcuts(self)
264 266
265 267 # Pre-populate history from IPython's history database
266 268 history = InMemoryHistory()
267 269 last_cell = u""
268 270 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
269 271 include_latest=True):
270 272 # Ignore blank lines and consecutive duplicates
271 273 cell = cell.rstrip()
272 274 if cell and (cell != last_cell):
273 275 history.append_string(cell)
274 276 last_cell = cell
275 277
276 278 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
277 279 self.style = DynamicStyle(lambda: self._style)
278 280
279 281 editing_mode = getattr(EditingMode, self.editing_mode.upper())
280 282
281 283 self.pt_app = PromptSession(
282 284 editing_mode=editing_mode,
283 285 key_bindings=key_bindings,
284 286 history=history,
285 287 completer=IPythonPTCompleter(shell=self),
286 288 enable_history_search = self.enable_history_search,
287 289 style=self.style,
288 290 include_default_pygments_style=False,
289 291 mouse_support=self.mouse_support,
290 292 enable_open_in_editor=self.extra_open_editor_shortcuts,
291 293 color_depth=self.color_depth,
292 294 **self._extra_prompt_options())
293 295
294 296 def _make_style_from_name_or_cls(self, name_or_cls):
295 297 """
296 298 Small wrapper that make an IPython compatible style from a style name
297 299
298 300 We need that to add style for prompt ... etc.
299 301 """
300 302 style_overrides = {}
301 303 if name_or_cls == 'legacy':
302 304 legacy = self.colors.lower()
303 305 if legacy == 'linux':
304 306 style_cls = get_style_by_name('monokai')
305 307 style_overrides = _style_overrides_linux
306 308 elif legacy == 'lightbg':
307 309 style_overrides = _style_overrides_light_bg
308 310 style_cls = get_style_by_name('pastie')
309 311 elif legacy == 'neutral':
310 312 # The default theme needs to be visible on both a dark background
311 313 # and a light background, because we can't tell what the terminal
312 314 # looks like. These tweaks to the default theme help with that.
313 315 style_cls = get_style_by_name('default')
314 316 style_overrides.update({
315 317 Token.Number: '#007700',
316 318 Token.Operator: 'noinherit',
317 319 Token.String: '#BB6622',
318 320 Token.Name.Function: '#2080D0',
319 321 Token.Name.Class: 'bold #2080D0',
320 322 Token.Name.Namespace: 'bold #2080D0',
321 323 Token.Prompt: '#009900',
322 324 Token.PromptNum: '#ansibrightgreen bold',
323 325 Token.OutPrompt: '#990000',
324 326 Token.OutPromptNum: '#ansibrightred bold',
325 327 })
326 328
327 329 # Hack: Due to limited color support on the Windows console
328 330 # the prompt colors will be wrong without this
329 331 if os.name == 'nt':
330 332 style_overrides.update({
331 333 Token.Prompt: '#ansidarkgreen',
332 334 Token.PromptNum: '#ansigreen bold',
333 335 Token.OutPrompt: '#ansidarkred',
334 336 Token.OutPromptNum: '#ansired bold',
335 337 })
336 338 elif legacy =='nocolor':
337 339 style_cls=_NoStyle
338 340 style_overrides = {}
339 341 else :
340 342 raise ValueError('Got unknown colors: ', legacy)
341 343 else :
342 344 if isinstance(name_or_cls, str):
343 345 style_cls = get_style_by_name(name_or_cls)
344 346 else:
345 347 style_cls = name_or_cls
346 348 style_overrides = {
347 349 Token.Prompt: '#009900',
348 350 Token.PromptNum: '#ansibrightgreen bold',
349 351 Token.OutPrompt: '#990000',
350 352 Token.OutPromptNum: '#ansibrightred bold',
351 353 }
352 354 style_overrides.update(self.highlighting_style_overrides)
353 355 style = merge_styles([
354 356 style_from_pygments_cls(style_cls),
355 357 style_from_pygments_dict(style_overrides),
356 358 ])
357 359
358 360 return style
359 361
360 362 @property
361 363 def pt_complete_style(self):
362 364 return {
363 365 'multicolumn': CompleteStyle.MULTI_COLUMN,
364 366 'column': CompleteStyle.COLUMN,
365 367 'readlinelike': CompleteStyle.READLINE_LIKE,
366 368 }[self.display_completions]
367 369
368 370 @property
369 371 def color_depth(self):
370 372 return (ColorDepth.TRUE_COLOR if self.true_color else None)
371 373
372 374 def _extra_prompt_options(self):
373 375 """
374 376 Return the current layout option for the current Terminal InteractiveShell
375 377 """
376 378 def get_message():
377 379 return PygmentsTokens(self.prompts.in_prompt_tokens())
378 380
379 381 return {
380 382 'complete_in_thread': False,
381 383 'lexer':IPythonPTLexer(),
382 384 'reserve_space_for_menu':self.space_for_menu,
383 385 'message': get_message,
384 386 'prompt_continuation': (
385 387 lambda width, lineno, is_soft_wrap:
386 388 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
387 389 'multiline': True,
388 390 'complete_style': self.pt_complete_style,
389 391
390 392 # Highlight matching brackets, but only when this setting is
391 393 # enabled, and only when the DEFAULT_BUFFER has the focus.
392 394 'input_processors': [ConditionalProcessor(
393 395 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
394 396 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
395 397 Condition(lambda: self.highlight_matching_brackets))],
396 398 'inputhook': self.inputhook,
397 399 }
398 400
399 401 def prompt_for_code(self):
400 402 if self.rl_next_input:
401 403 default = self.rl_next_input
402 404 self.rl_next_input = None
403 405 else:
404 406 default = ''
405 407
406 408 with patch_stdout(raw=True):
407 409 text = self.pt_app.prompt(
408 410 default=default,
409 411 # pre_run=self.pre_prompt,# reset_current_buffer=True,
410 412 **self._extra_prompt_options())
411 413 return text
412 414
413 415 def enable_win_unicode_console(self):
414 416 if sys.version_info >= (3, 6):
415 417 # Since PEP 528, Python uses the unicode APIs for the Windows
416 418 # console by default, so WUC shouldn't be needed.
417 419 return
418 420
419 421 import win_unicode_console
420 422 win_unicode_console.enable()
421 423
422 424 def init_io(self):
423 425 if sys.platform not in {'win32', 'cli'}:
424 426 return
425 427
426 428 self.enable_win_unicode_console()
427 429
428 430 import colorama
429 431 colorama.init()
430 432
431 433 # For some reason we make these wrappers around stdout/stderr.
432 434 # For now, we need to reset them so all output gets coloured.
433 435 # https://github.com/ipython/ipython/issues/8669
434 436 # io.std* are deprecated, but don't show our own deprecation warnings
435 437 # during initialization of the deprecated API.
436 438 with warnings.catch_warnings():
437 439 warnings.simplefilter('ignore', DeprecationWarning)
438 440 io.stdout = io.IOStream(sys.stdout)
439 441 io.stderr = io.IOStream(sys.stderr)
440 442
441 443 def init_magics(self):
442 444 super(TerminalInteractiveShell, self).init_magics()
443 445 self.register_magics(TerminalMagics)
444 446
445 447 def init_alias(self):
446 448 # The parent class defines aliases that can be safely used with any
447 449 # frontend.
448 450 super(TerminalInteractiveShell, self).init_alias()
449 451
450 452 # Now define aliases that only make sense on the terminal, because they
451 453 # need direct access to the console in a way that we can't emulate in
452 454 # GUI or web frontend
453 455 if os.name == 'posix':
454 456 for cmd in ('clear', 'more', 'less', 'man'):
455 457 self.alias_manager.soft_define_alias(cmd, cmd)
456 458
457 459
458 460 def __init__(self, *args, **kwargs):
459 461 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
460 462 self.init_prompt_toolkit_cli()
461 463 self.init_term_title()
462 464 self.keep_running = True
463 465
464 466 self.debugger_history = InMemoryHistory()
465 467
466 468 def ask_exit(self):
467 469 self.keep_running = False
468 470
469 471 rl_next_input = None
470 472
471 473 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
472 474
473 475 if display_banner is not DISPLAY_BANNER_DEPRECATED:
474 476 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
475 477
476 478 self.keep_running = True
477 479 while self.keep_running:
478 480 print(self.separate_in, end='')
479 481
480 482 try:
481 483 code = self.prompt_for_code()
482 484 except EOFError:
483 485 if (not self.confirm_exit) \
484 486 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
485 487 self.ask_exit()
486 488
487 489 else:
488 490 if code:
489 491 self.run_cell(code, store_history=True)
490 492
491 493 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
492 494 # An extra layer of protection in case someone mashing Ctrl-C breaks
493 495 # out of our internal code.
494 496 if display_banner is not DISPLAY_BANNER_DEPRECATED:
495 497 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
496 498 while True:
497 499 try:
498 500 self.interact()
499 501 break
500 502 except KeyboardInterrupt as e:
501 503 print("\n%s escaped interact()\n" % type(e).__name__)
502 504 finally:
503 505 # An interrupt during the eventloop will mess up the
504 506 # internal state of the prompt_toolkit library.
505 507 # Stopping the eventloop fixes this, see
506 508 # https://github.com/ipython/ipython/pull/9867
507 509 if hasattr(self, '_eventloop'):
508 510 self._eventloop.stop()
509 511
510 512 _inputhook = None
511 513 def inputhook(self, context):
512 514 if self._inputhook is not None:
513 515 self._inputhook(context)
514 516
515 517 active_eventloop = None
516 518 def enable_gui(self, gui=None):
517 519 if gui:
518 520 self.active_eventloop, self._inputhook =\
519 521 get_inputhook_name_and_func(gui)
520 522 else:
521 523 self.active_eventloop = self._inputhook = None
522 524
523 525 # Run !system commands directly, not through pipes, so terminal programs
524 526 # work correctly.
525 527 system = InteractiveShell.system_raw
526 528
527 529 def auto_rewrite_input(self, cmd):
528 530 """Overridden from the parent class to use fancy rewriting prompt"""
529 531 if not self.show_rewritten_input:
530 532 return
531 533
532 534 tokens = self.prompts.rewrite_prompt_tokens()
533 535 if self.pt_app:
534 536 print_formatted_text(PygmentsTokens(tokens), end='',
535 537 style=self.pt_app.app.style)
536 538 print(cmd)
537 539 else:
538 540 prompt = ''.join(s for t, s in tokens)
539 541 print(prompt, cmd, sep='')
540 542
541 543 _prompts_before = None
542 544 def switch_doctest_mode(self, mode):
543 545 """Switch prompts to classic for %doctest_mode"""
544 546 if mode:
545 547 self._prompts_before = self.prompts
546 548 self.prompts = ClassicPrompts(self)
547 549 elif self._prompts_before:
548 550 self.prompts = self._prompts_before
549 551 self._prompts_before = None
550 552 # self._update_layout()
551 553
552 554
553 555 InteractiveShellABC.register(TerminalInteractiveShell)
554 556
555 557 if __name__ == '__main__':
556 558 TerminalInteractiveShell.instance().interact()
@@ -1,91 +1,102 b''
1 1 """Terminal input and output prompts."""
2 2
3 3 from pygments.token import Token
4 4 import sys
5 5
6 6 from IPython.core.displayhook import DisplayHook
7 7
8 8 from prompt_toolkit.formatted_text import fragment_list_width, PygmentsTokens
9 9 from prompt_toolkit.shortcuts import print_formatted_text
10 10
11 11
12 12 class Prompts(object):
13 13 def __init__(self, shell):
14 14 self.shell = shell
15 15
16 16 def vi_mode(self):
17 17 if (getattr(self.shell.pt_app, 'editing_mode', None) == 'VI'
18 18 and self.shell.prompt_includes_vi_mode):
19 19 return '['+str(self.shell.pt_app.app.vi_state.input_mode)[3:6]+'] '
20 20 return ''
21 21
22 22
23 23 def in_prompt_tokens(self):
24 24 return [
25 25 (Token.Prompt, self.vi_mode() ),
26 26 (Token.Prompt, 'In ['),
27 27 (Token.PromptNum, str(self.shell.execution_count)),
28 28 (Token.Prompt, ']: '),
29 29 ]
30 30
31 31 def _width(self):
32 32 return fragment_list_width(self.in_prompt_tokens())
33 33
34 34 def continuation_prompt_tokens(self, width=None):
35 35 if width is None:
36 36 width = self._width()
37 37 return [
38 38 (Token.Prompt, (' ' * (width - 5)) + '...: '),
39 39 ]
40 40
41 41 def rewrite_prompt_tokens(self):
42 42 width = self._width()
43 43 return [
44 44 (Token.Prompt, ('-' * (width - 2)) + '> '),
45 45 ]
46 46
47 47 def out_prompt_tokens(self):
48 48 return [
49 49 (Token.OutPrompt, 'Out['),
50 50 (Token.OutPromptNum, str(self.shell.execution_count)),
51 51 (Token.OutPrompt, ']: '),
52 52 ]
53 53
54 54 class ClassicPrompts(Prompts):
55 55 def in_prompt_tokens(self):
56 56 return [
57 57 (Token.Prompt, '>>> '),
58 58 ]
59 59
60 60 def continuation_prompt_tokens(self, width=None):
61 61 return [
62 62 (Token.Prompt, '... ')
63 63 ]
64 64
65 65 def rewrite_prompt_tokens(self):
66 66 return []
67 67
68 68 def out_prompt_tokens(self):
69 69 return []
70 70
71 71 class RichPromptDisplayHook(DisplayHook):
72 72 """Subclass of base display hook using coloured prompt"""
73 73 def write_output_prompt(self):
74 74 sys.stdout.write(self.shell.separate_out)
75 75 # If we're not displaying a prompt, it effectively ends with a newline,
76 76 # because the output will be left-aligned.
77 77 self.prompt_end_newline = True
78 78
79 79 if self.do_full_cache:
80 80 tokens = self.shell.prompts.out_prompt_tokens()
81 81 prompt_txt = ''.join(s for t, s in tokens)
82 82 if prompt_txt and not prompt_txt.endswith('\n'):
83 83 # Ask for a newline before multiline output
84 84 self.prompt_end_newline = False
85 85
86 86 if self.shell.pt_app:
87 87 print_formatted_text(PygmentsTokens(tokens),
88 88 style=self.shell.pt_app.app.style, end='',
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